diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/svg | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/svg')
500 files changed, 68607 insertions, 0 deletions
diff --git a/dom/svg/DOMSVGAngle.cpp b/dom/svg/DOMSVGAngle.cpp new file mode 100644 index 0000000000..f2cc45fd87 --- /dev/null +++ b/dom/svg/DOMSVGAngle.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAngle.h" +#include "SVGAnimatedOrient.h" +#include "mozilla/dom/SVGAngleBinding.h" +#include "mozilla/dom/SVGSVGElement.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAngle, mSVGElement) + +DOMSVGAngle::DOMSVGAngle(SVGSVGElement* aSVGElement) + : mSVGElement(aSVGElement), mType(AngleType::CreatedValue) { + mVal = new SVGAnimatedOrient(); + mVal->Init(); +} + +JSObject* DOMSVGAngle::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAngle_Binding::Wrap(aCx, this, aGivenProto); +} + +uint16_t DOMSVGAngle::UnitType() const { + if (mType == AngleType::AnimValue) { + mSVGElement->FlushAnimations(); + return mVal->mAnimValUnit; + } + return mVal->mBaseValUnit; +} + +float DOMSVGAngle::Value() const { + if (mType == AngleType::AnimValue) { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(); + } + return mVal->GetBaseValue(); +} + +void DOMSVGAngle::SetValue(float aValue, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + bool isBaseVal = mType == AngleType::BaseValue; + mVal->SetBaseValue(aValue, mVal->mBaseValUnit, + isBaseVal ? mSVGElement.get() : nullptr, isBaseVal); +} + +float DOMSVGAngle::ValueInSpecifiedUnits() const { + if (mType == AngleType::AnimValue) { + return mVal->mAnimVal; + } + return mVal->mBaseVal; +} + +void DOMSVGAngle::SetValueInSpecifiedUnits(float aValue, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + if (mType == AngleType::BaseValue) { + mVal->SetBaseValueInSpecifiedUnits(aValue, mSVGElement); + } else { + mVal->mBaseVal = aValue; + } +} + +void DOMSVGAngle::NewValueSpecifiedUnits(uint16_t unitType, + float valueInSpecifiedUnits, + ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->NewValueSpecifiedUnits( + unitType, valueInSpecifiedUnits, + mType == AngleType::BaseValue ? mSVGElement.get() : nullptr); +} + +void DOMSVGAngle::ConvertToSpecifiedUnits(uint16_t unitType, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->ConvertToSpecifiedUnits( + unitType, mType == AngleType::BaseValue ? mSVGElement.get() : nullptr); +} + +void DOMSVGAngle::SetValueAsString(const nsAString& aValue, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + bool isBaseVal = mType == AngleType::BaseValue; + rv = mVal->SetBaseValueString(aValue, isBaseVal ? mSVGElement.get() : nullptr, + isBaseVal); +} + +void DOMSVGAngle::GetValueAsString(nsAString& aValue) { + if (mType == AngleType::AnimValue) { + mSVGElement->FlushAnimations(); + mVal->GetAnimAngleValueString(aValue); + } else { + mVal->GetBaseAngleValueString(aValue); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAngle.h b/dom/svg/DOMSVGAngle.h new file mode 100644 index 0000000000..ed2cb23b64 --- /dev/null +++ b/dom/svg/DOMSVGAngle.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANGLE_H_ +#define DOM_SVG_DOMSVGANGLE_H_ + +#include "nsWrapperCache.h" +#include "SVGElement.h" +#include "mozilla/Attributes.h" + +namespace mozilla { + +class SVGAnimatedOrient; + +namespace dom { +class SVGSVGElement; + +class DOMSVGAngle final : public nsWrapperCache { + public: + enum class AngleType : int8_t { BaseValue, AnimValue, CreatedValue }; + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAngle) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAngle) + + /** + * Generic ctor for DOMSVGAngle objects that are created for an attribute. + */ + DOMSVGAngle(SVGAnimatedOrient* aVal, SVGElement* aSVGElement, AngleType aType) + : mVal(aVal), mSVGElement(aSVGElement), mType(aType) {} + + /** + * Ctor for creating the objects returned by SVGSVGElement.createSVGAngle(), + * which do not initially belong to an attribute. + */ + explicit DOMSVGAngle(SVGSVGElement* aSVGElement); + + // WebIDL + SVGElement* GetParentObject() { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + uint16_t UnitType() const; + float Value() const; + void GetValueAsString(nsAString& aValue); + void SetValue(float aValue, ErrorResult& rv); + float ValueInSpecifiedUnits() const; + void SetValueInSpecifiedUnits(float aValue, ErrorResult& rv); + void SetValueAsString(const nsAString& aValue, ErrorResult& rv); + void NewValueSpecifiedUnits(uint16_t unitType, float value, ErrorResult& rv); + void ConvertToSpecifiedUnits(uint16_t unitType, ErrorResult& rv); + + protected: + ~DOMSVGAngle(); + + SVGAnimatedOrient* mVal; // if mType is CreatedValue, we own the angle. + // Otherwise, the element does. + RefPtr<SVGElement> mSVGElement; + AngleType mType; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANGLE_H_ diff --git a/dom/svg/DOMSVGAnimatedAngle.cpp b/dom/svg/DOMSVGAnimatedAngle.cpp new file mode 100644 index 0000000000..4762f79b20 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedAngle.cpp @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedAngle.h" + +#include "SVGAnimatedOrient.h" +#include "mozilla/dom/SVGAnimatedAngleBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedAngle, mSVGElement) + +JSObject* DOMSVGAnimatedAngle::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedAngle_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DOMSVGAngle> DOMSVGAnimatedAngle::BaseVal() { + return mVal->ToDOMBaseVal(mSVGElement); +} + +already_AddRefed<DOMSVGAngle> DOMSVGAnimatedAngle::AnimVal() { + return mVal->ToDOMAnimVal(mSVGElement); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedAngle.h b/dom/svg/DOMSVGAnimatedAngle.h new file mode 100644 index 0000000000..d5190010fa --- /dev/null +++ b/dom/svg/DOMSVGAnimatedAngle.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDANGLE_H_ +#define DOM_SVG_DOMSVGANIMATEDANGLE_H_ + +#include "nsWrapperCache.h" +#include "SVGElement.h" +#include "mozilla/Attributes.h" + +namespace mozilla { + +class SVGAnimatedOrient; + +namespace dom { + +class DOMSVGAngle; + +class DOMSVGAnimatedAngle final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedAngle) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedAngle) + + DOMSVGAnimatedAngle(SVGAnimatedOrient* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // WebIDL + SVGElement* GetParentObject() { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + already_AddRefed<DOMSVGAngle> BaseVal(); + already_AddRefed<DOMSVGAngle> AnimVal(); + + protected: + ~DOMSVGAnimatedAngle(); + + SVGAnimatedOrient* mVal; // kept alive because it belongs to content + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANIMATEDANGLE_H_ diff --git a/dom/svg/DOMSVGAnimatedBoolean.cpp b/dom/svg/DOMSVGAnimatedBoolean.cpp new file mode 100644 index 0000000000..d35429ac65 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedBoolean.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedBoolean.h" + +#include "mozilla/dom/SVGAnimatedBooleanBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedBoolean, + mSVGElement) + +JSObject* DOMSVGAnimatedBoolean::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedBoolean_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedBoolean.h b/dom/svg/DOMSVGAnimatedBoolean.h new file mode 100644 index 0000000000..7e1b5caae4 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedBoolean.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDBOOLEAN_H_ +#define DOM_SVG_DOMSVGANIMATEDBOOLEAN_H_ + +#include "SVGAnimatedBoolean.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedBoolean final : public nsWrapperCache { + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedBoolean) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedBoolean) + + DOMSVGAnimatedBoolean(SVGAnimatedBoolean* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // WebIDL + SVGElement* GetParentObject() const { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + bool BaseVal() const { return mVal->GetBaseValue(); } + void SetBaseVal(bool aValue) { mVal->SetBaseValue(aValue, mSVGElement); } + bool AnimVal() const { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(); + } + + protected: + ~DOMSVGAnimatedBoolean(); + + SVGAnimatedBoolean* mVal; // kept alive because it belongs to content + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDBOOLEAN_H_ diff --git a/dom/svg/DOMSVGAnimatedEnumeration.cpp b/dom/svg/DOMSVGAnimatedEnumeration.cpp new file mode 100644 index 0000000000..a5d3f16466 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedEnumeration.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedEnumeration.h" + +#include "mozilla/dom/SVGAnimatedEnumerationBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedEnumeration, + mSVGElement) + +JSObject* DOMSVGAnimatedEnumeration::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedEnumeration_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedEnumeration.h b/dom/svg/DOMSVGAnimatedEnumeration.h new file mode 100644 index 0000000000..fb8354654b --- /dev/null +++ b/dom/svg/DOMSVGAnimatedEnumeration.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDENUMERATION_H_ +#define DOM_SVG_DOMSVGANIMATEDENUMERATION_H_ + +#include "nsWrapperCache.h" + +#include "SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedEnumeration : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedEnumeration) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedEnumeration) + + SVGElement* GetParentObject() const { return mSVGElement; } + + JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final; + + virtual uint16_t BaseVal() = 0; + virtual void SetBaseVal(uint16_t aBaseVal, ErrorResult& aRv) = 0; + virtual uint16_t AnimVal() = 0; + + protected: + explicit DOMSVGAnimatedEnumeration(SVGElement* aSVGElement) + : mSVGElement(aSVGElement) {} + virtual ~DOMSVGAnimatedEnumeration() = default; + + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDENUMERATION_H_ diff --git a/dom/svg/DOMSVGAnimatedInteger.cpp b/dom/svg/DOMSVGAnimatedInteger.cpp new file mode 100644 index 0000000000..94018ecc39 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedInteger.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedInteger.h" + +#include "mozilla/dom/SVGAnimatedIntegerBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedInteger, + mSVGElement) + +JSObject* DOMSVGAnimatedInteger::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedInteger_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedInteger.h b/dom/svg/DOMSVGAnimatedInteger.h new file mode 100644 index 0000000000..c4c24fed1c --- /dev/null +++ b/dom/svg/DOMSVGAnimatedInteger.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDINTEGER_H_ +#define DOM_SVG_DOMSVGANIMATEDINTEGER_H_ + +#include "nsWrapperCache.h" + +#include "SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedInteger : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedInteger) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedInteger) + + SVGElement* GetParentObject() const { return mSVGElement; } + + JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final; + + virtual int32_t BaseVal() = 0; + virtual void SetBaseVal(int32_t aBaseVal) = 0; + virtual int32_t AnimVal() = 0; + + protected: + explicit DOMSVGAnimatedInteger(SVGElement* aSVGElement) + : mSVGElement(aSVGElement) {} + virtual ~DOMSVGAnimatedInteger() = default; + + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDINTEGER_H_ diff --git a/dom/svg/DOMSVGAnimatedLength.cpp b/dom/svg/DOMSVGAnimatedLength.cpp new file mode 100644 index 0000000000..53bbfea064 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLength.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedLength.h" + +#include "mozilla/dom/SVGAnimatedLengthBinding.h" +#include "SVGAnimatedLength.h" +#include "DOMSVGLength.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedLength, + mSVGElement) + +JSObject* DOMSVGAnimatedLength::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedLength_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DOMSVGLength> DOMSVGAnimatedLength::BaseVal() { + return mVal->ToDOMBaseVal(mSVGElement); +} + +already_AddRefed<DOMSVGLength> DOMSVGAnimatedLength::AnimVal() { + return mVal->ToDOMAnimVal(mSVGElement); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedLength.h b/dom/svg/DOMSVGAnimatedLength.h new file mode 100644 index 0000000000..09df1d0ce4 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLength.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDLENGTH_H_ +#define DOM_SVG_DOMSVGANIMATEDLENGTH_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" + +namespace mozilla { + +class SVGAnimatedLength; + +namespace dom { + +class DOMSVGLength; + +class DOMSVGAnimatedLength final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedLength) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedLength) + + DOMSVGAnimatedLength(SVGAnimatedLength* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // WebIDL + SVGElement* GetParentObject() { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + already_AddRefed<DOMSVGLength> BaseVal(); + already_AddRefed<DOMSVGLength> AnimVal(); + + protected: + ~DOMSVGAnimatedLength(); + + SVGAnimatedLength* mVal; // kept alive because it belongs to content + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANIMATEDLENGTH_H_ diff --git a/dom/svg/DOMSVGAnimatedLengthList.cpp b/dom/svg/DOMSVGAnimatedLengthList.cpp new file mode 100644 index 0000000000..b64b923745 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLengthList.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedLengthList.h" + +#include "DOMSVGLengthList.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/dom/SVGAnimatedLengthListBinding.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/RefPtr.h" + +// See the architecture comment in this file's header. + +namespace mozilla::dom { + +static inline SVGAttrTearoffTable<SVGAnimatedLengthList, + DOMSVGAnimatedLengthList>& +SVGAnimatedLengthListTearoffTable() { + static SVGAttrTearoffTable<SVGAnimatedLengthList, DOMSVGAnimatedLengthList> + sSVGAnimatedLengthListTearoffTable; + return sSVGAnimatedLengthListTearoffTable; +} + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedLengthList, + mElement) + +JSObject* DOMSVGAnimatedLengthList::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return dom::SVGAnimatedLengthList_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DOMSVGLengthList> DOMSVGAnimatedLengthList::BaseVal() { + if (!mBaseVal) { + mBaseVal = new DOMSVGLengthList(this, InternalAList().GetBaseValue()); + } + RefPtr<DOMSVGLengthList> baseVal = mBaseVal; + return baseVal.forget(); +} + +already_AddRefed<DOMSVGLengthList> DOMSVGAnimatedLengthList::AnimVal() { + if (!mAnimVal) { + mAnimVal = new DOMSVGLengthList(this, InternalAList().GetAnimValue()); + } + RefPtr<DOMSVGLengthList> animVal = mAnimVal; + return animVal.forget(); +} + +/* static */ +already_AddRefed<DOMSVGAnimatedLengthList> +DOMSVGAnimatedLengthList::GetDOMWrapper(SVGAnimatedLengthList* aList, + dom::SVGElement* aElement, + uint8_t aAttrEnum, uint8_t aAxis) { + RefPtr<DOMSVGAnimatedLengthList> wrapper = + SVGAnimatedLengthListTearoffTable().GetTearoff(aList); + if (!wrapper) { + wrapper = new DOMSVGAnimatedLengthList(aElement, aAttrEnum, aAxis); + SVGAnimatedLengthListTearoffTable().AddTearoff(aList, wrapper); + } + return wrapper.forget(); +} + +/* static */ +DOMSVGAnimatedLengthList* DOMSVGAnimatedLengthList::GetDOMWrapperIfExists( + SVGAnimatedLengthList* aList) { + return SVGAnimatedLengthListTearoffTable().GetTearoff(aList); +} + +DOMSVGAnimatedLengthList::~DOMSVGAnimatedLengthList() { + // Script no longer has any references to us, to our base/animVal objects, or + // to any of their list items. + SVGAnimatedLengthListTearoffTable().RemoveTearoff(&InternalAList()); +} + +void DOMSVGAnimatedLengthList::InternalBaseValListWillChangeTo( + const SVGLengthList& aNewValue) { + // When the number of items in our internal counterpart's baseVal changes, + // we MUST keep our baseVal in sync. If we don't, script will either see a + // list that is too short and be unable to access indexes that should be + // valid, or else, MUCH WORSE, script will see a list that is too long and be + // able to access "items" at indexes that are out of bounds (read/write to + // bad memory)!! + + RefPtr<DOMSVGAnimatedLengthList> kungFuDeathGrip; + if (mBaseVal) { + if (aNewValue.Length() < mBaseVal->LengthNoFlush()) { + // InternalListLengthWillChange might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + mBaseVal->InternalListLengthWillChange(aNewValue.Length()); + } + + // If our attribute is not animating, then our animVal mirrors our baseVal + // and we must sync its length too. (If our attribute is animating, then the + // SMIL engine takes care of calling InternalAnimValListWillChangeTo() if + // necessary.) + + if (!IsAnimating()) { + InternalAnimValListWillChangeTo(aNewValue); + } +} + +void DOMSVGAnimatedLengthList::InternalAnimValListWillChangeTo( + const SVGLengthList& aNewValue) { + if (mAnimVal) { + mAnimVal->InternalListLengthWillChange(aNewValue.Length()); + } +} + +bool DOMSVGAnimatedLengthList::IsAnimating() const { + return InternalAList().IsAnimating(); +} + +SVGAnimatedLengthList& DOMSVGAnimatedLengthList::InternalAList() { + return *mElement->GetAnimatedLengthList(mAttrEnum); +} + +const SVGAnimatedLengthList& DOMSVGAnimatedLengthList::InternalAList() const { + return *mElement->GetAnimatedLengthList(mAttrEnum); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedLengthList.h b/dom/svg/DOMSVGAnimatedLengthList.h new file mode 100644 index 0000000000..04551a41b2 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLengthList.h @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDLENGTHLIST_H_ +#define DOM_SVG_DOMSVGANIMATEDLENGTHLIST_H_ + +#include "nsCycleCollectionParticipant.h" +#include "SVGElement.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class SVGAnimatedLengthList; +class SVGLengthList; + +namespace dom { + +class DOMSVGLengthList; + +/** + * Class DOMSVGAnimatedLengthList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGAnimatedLengthList objects. We have this internal-DOM split because DOM + * classes are relatively heavy-weight objects with non-optimal interfaces for + * internal code, and they're relatively infrequently used. Having separate + * internal and DOM classes does add complexity - especially for lists where + * the internal list and DOM lists (and their items) need to be kept in sync - + * but it keeps the internal classes light and fast, and in 99% of cases + * they're all that's used. DOM wrappers are only instantiated when script + * demands it. + * + * Ownership model: + * + * The diagram below shows the ownership model between the various DOM objects + * in the tree of DOM objects that correspond to an SVG length list attribute. + * The angled brackets ">" and "<" denote a reference from one object to + * another, where the "!" character denotes a strong reference, and the "~" + * character denotes a weak reference. + * + * .----<!----. .----<!----. .----<!----. + * | | | | | | + * element ~> DOMSVGAnimatedLengthList ~> DOMSVGLengthList ~> DOMSVGLength + * + * Rationale: + * + * The following three paragraphs explain the main three requirements that must + * be met by any design. These are followed by an explanation of the rationale + * behind our particular design. + * + * 1: DOMSVGAnimatedLengthList, DOMSVGLengthLists and DOMSVGLength get to their + * internal counterparts via their element, and they use their element to send + * out appropriate notifications when they change. Because of this, having + * their element disappear out from under them would be very bad. To keep their + * element alive at least as long as themselves, each of these classes must + * contain a _strong_ reference (directly or indirectly) to their element. + * + * 2: Another central requirement of any design is the SVG specification's + * requirement that script must always be given the exact same objects each + * time it accesses a given object in a DOM object tree corresponding to an SVG + * length list attribute. In practice "always" actually means "whenever script + * has kept a references to a DOM object it previously accessed", since a + * script will only be able to detect any difference in object identity if it + * has a previous reference to compare against. + * + * 3: The wiggle room in the "same object" requirement leads us to a third + * (self imposed) requirement: if script no longer has a reference to a given + * DOM object from an object tree corresponding to an SVG length list + * attribute, and if that object doesn't currently have any descendants, then + * that object should be released to free up memory. + * + * To help in understanding our current design, consider this BROKEN design: + * + * .-------------------------------<!-------------------------. + * |--------------------<!----------------. | + * |----<!----. | | + * | | | | + * element ~> DOMSVGAnimatedLengthList !> DOMSVGLengthList !> DOMSVGLength + * + * Having all the objects keep a reference directly to their element like this + * would reduce the number of dereferences that they need to make to get their + * internal counterpart. However, this design does not meet the "same object" + * requirement of the SVG specification. If script keeps a reference to a + * DOMSVGLength or DOMSVGLengthList object, but not to that object's + * DOMSVGAnimatedLengthList, then the DOMSVGAnimatedLengthList may be garbage + * collected. We'd then have no way to return the same DOMSVGLength / + * DOMSVGLengthList object that the script has a reference to if the script + * went looking for it via the DOMSVGAnimatedLengthList property on the + * element - we'd end up creating a fresh DOMSVGAnimatedLengthList, with no + * knowlegde of the existing DOMSVGLengthList or DOMSVGLength object. + * + * The way we solve this problem is by making sure that parent objects cannot + * die until all their children are dead by having child objects hold a strong + * reference to their parent object. Note that this design means that the child + * objects hold a strong reference to their element too, albeit indirectly via + * the strong reference to their parent object: + * + * .----<!----. .----<!----. .----<!----. + * | | | | | | + * element ~> DOMSVGAnimatedLengthList ~> DOMSVGLengthList ~> DOMSVGLength + * + * One drawback of this design is that objects must look up their parent + * chain to find their element, but that overhead is relatively small. + */ +class DOMSVGAnimatedLengthList final : public nsWrapperCache { + friend class DOMSVGLengthList; + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedLengthList) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedLengthList) + + /** + * Factory method to create and return a DOMSVGAnimatedLengthList wrapper + * for a given internal SVGAnimatedLengthList object. The factory takes care + * of caching the object that it returns so that the same object can be + * returned for the given SVGAnimatedLengthList each time it is requested. + * The cached object is only removed from the cache when it is destroyed due + * to there being no more references to it or to any of its descendant + * objects. If that happens, any subsequent call requesting the DOM wrapper + * for the SVGAnimatedLengthList will naturally result in a new + * DOMSVGAnimatedLengthList being returned. + */ + static already_AddRefed<DOMSVGAnimatedLengthList> GetDOMWrapper( + SVGAnimatedLengthList* aList, dom::SVGElement* aElement, + uint8_t aAttrEnum, uint8_t aAxis); + + /** + * This method returns the DOMSVGAnimatedLengthList wrapper for an internal + * SVGAnimatedLengthList object if it currently has a wrapper. If it does + * not, then nullptr is returned. + */ + static DOMSVGAnimatedLengthList* GetDOMWrapperIfExists( + SVGAnimatedLengthList* aList); + + /** + * Called by internal code to notify us when we need to sync the length of + * our baseVal DOM list with its internal list. This is called just prior to + * the length of the internal baseVal list being changed so that any DOM list + * items that need to be removed from the DOM list can first get their values + * from their internal counterpart. + * + * The only time this method could fail is on OOM when trying to increase the + * length of the DOM list. If that happens then this method simply clears the + * list and returns. Callers just proceed as normal, and we simply accept + * that the DOM list will be empty (until successfully set to a new value). + */ + void InternalBaseValListWillChangeTo(const SVGLengthList& aNewValue); + void InternalAnimValListWillChangeTo(const SVGLengthList& aNewValue); + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool IsAnimating() const; + + // WebIDL + dom::SVGElement* GetParentObject() const { return mElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + // These aren't weak refs because mBaseVal and mAnimVal are weak + already_AddRefed<DOMSVGLengthList> BaseVal(); + already_AddRefed<DOMSVGLengthList> AnimVal(); + + private: + /** + * Only our static GetDOMWrapper() factory method may create objects of our + * type. + */ + DOMSVGAnimatedLengthList(dom::SVGElement* aElement, uint8_t aAttrEnum, + uint8_t aAxis) + : mBaseVal(nullptr), + mAnimVal(nullptr), + mElement(aElement), + mAttrEnum(aAttrEnum), + mAxis(aAxis) {} + + ~DOMSVGAnimatedLengthList(); + + /// Get a reference to this DOM wrapper object's internal counterpart. + SVGAnimatedLengthList& InternalAList(); + const SVGAnimatedLengthList& InternalAList() const; + + // Weak refs to our DOMSVGLengthList baseVal/animVal objects. These objects + // are friends and take care of clearing these pointers when they die, making + // these true weak references. + DOMSVGLengthList* mBaseVal; + DOMSVGLengthList* mAnimVal; + + // Strong ref to our element to keep it alive. We hold this not only for + // ourself, but also for our base/animVal and all of their items. + RefPtr<dom::SVGElement> mElement; + + uint8_t mAttrEnum; + uint8_t mAxis; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANIMATEDLENGTHLIST_H_ diff --git a/dom/svg/DOMSVGAnimatedNumber.cpp b/dom/svg/DOMSVGAnimatedNumber.cpp new file mode 100644 index 0000000000..2910731b8d --- /dev/null +++ b/dom/svg/DOMSVGAnimatedNumber.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedNumber.h" + +#include "mozilla/dom/SVGAnimatedNumberBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedNumber, + mSVGElement) + +JSObject* DOMSVGAnimatedNumber::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedNumber_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedNumber.h b/dom/svg/DOMSVGAnimatedNumber.h new file mode 100644 index 0000000000..eb01589259 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedNumber.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDNUMBER_H_ +#define DOM_SVG_DOMSVGANIMATEDNUMBER_H_ + +#include "nsWrapperCache.h" + +#include "SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedNumber : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedNumber) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedNumber) + + SVGElement* GetParentObject() const { return mSVGElement; } + + JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final; + + virtual float BaseVal() = 0; + virtual void SetBaseVal(float aBaseVal) = 0; + virtual float AnimVal() = 0; + + protected: + explicit DOMSVGAnimatedNumber(SVGElement* aSVGElement) + : mSVGElement(aSVGElement) {} + virtual ~DOMSVGAnimatedNumber() = default; + + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDNUMBER_H_ diff --git a/dom/svg/DOMSVGAnimatedNumberList.cpp b/dom/svg/DOMSVGAnimatedNumberList.cpp new file mode 100644 index 0000000000..d0fac03f4d --- /dev/null +++ b/dom/svg/DOMSVGAnimatedNumberList.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedNumberList.h" + +#include "DOMSVGNumberList.h" +#include "SVGAnimatedNumberList.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/dom/SVGAnimatedNumberListBinding.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/RefPtr.h" + +// See the architecture comment in this file's header. + +namespace mozilla::dom { + +static inline SVGAttrTearoffTable<SVGAnimatedNumberList, + DOMSVGAnimatedNumberList>& +SVGAnimatedNumberListTearoffTable() { + static SVGAttrTearoffTable<SVGAnimatedNumberList, DOMSVGAnimatedNumberList> + sSVGAnimatedNumberListTearoffTable; + return sSVGAnimatedNumberListTearoffTable; +} + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedNumberList, + mElement) + +JSObject* DOMSVGAnimatedNumberList::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return mozilla::dom::SVGAnimatedNumberList_Binding::Wrap(aCx, this, + aGivenProto); +} + +already_AddRefed<DOMSVGNumberList> DOMSVGAnimatedNumberList::BaseVal() { + if (!mBaseVal) { + mBaseVal = new DOMSVGNumberList(this, InternalAList().GetBaseValue()); + } + RefPtr<DOMSVGNumberList> baseVal = mBaseVal; + return baseVal.forget(); +} + +already_AddRefed<DOMSVGNumberList> DOMSVGAnimatedNumberList::AnimVal() { + if (!mAnimVal) { + mAnimVal = new DOMSVGNumberList(this, InternalAList().GetAnimValue()); + } + RefPtr<DOMSVGNumberList> animVal = mAnimVal; + return animVal.forget(); +} + +/* static */ +already_AddRefed<DOMSVGAnimatedNumberList> +DOMSVGAnimatedNumberList::GetDOMWrapper(SVGAnimatedNumberList* aList, + dom::SVGElement* aElement, + uint8_t aAttrEnum) { + RefPtr<DOMSVGAnimatedNumberList> wrapper = + SVGAnimatedNumberListTearoffTable().GetTearoff(aList); + if (!wrapper) { + wrapper = new DOMSVGAnimatedNumberList(aElement, aAttrEnum); + SVGAnimatedNumberListTearoffTable().AddTearoff(aList, wrapper); + } + return wrapper.forget(); +} + +/* static */ +DOMSVGAnimatedNumberList* DOMSVGAnimatedNumberList::GetDOMWrapperIfExists( + SVGAnimatedNumberList* aList) { + return SVGAnimatedNumberListTearoffTable().GetTearoff(aList); +} + +DOMSVGAnimatedNumberList::~DOMSVGAnimatedNumberList() { + // Script no longer has any references to us, to our base/animVal objects, or + // to any of their list items. + SVGAnimatedNumberListTearoffTable().RemoveTearoff(&InternalAList()); +} + +void DOMSVGAnimatedNumberList::InternalBaseValListWillChangeTo( + const SVGNumberList& aNewValue) { + // When the number of items in our internal counterpart's baseVal changes, + // we MUST keep our baseVal in sync. If we don't, script will either see a + // list that is too short and be unable to access indexes that should be + // valid, or else, MUCH WORSE, script will see a list that is too long and be + // able to access "items" at indexes that are out of bounds (read/write to + // bad memory)!! + + RefPtr<DOMSVGAnimatedNumberList> kungFuDeathGrip; + if (mBaseVal) { + if (aNewValue.Length() < mBaseVal->LengthNoFlush()) { + // InternalListLengthWillChange might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + mBaseVal->InternalListLengthWillChange(aNewValue.Length()); + } + + // If our attribute is not animating, then our animVal mirrors our baseVal + // and we must sync its length too. (If our attribute is animating, then the + // SMIL engine takes care of calling InternalAnimValListWillChangeTo() if + // necessary.) + + if (!IsAnimating()) { + InternalAnimValListWillChangeTo(aNewValue); + } +} + +void DOMSVGAnimatedNumberList::InternalAnimValListWillChangeTo( + const SVGNumberList& aNewValue) { + if (mAnimVal) { + mAnimVal->InternalListLengthWillChange(aNewValue.Length()); + } +} + +bool DOMSVGAnimatedNumberList::IsAnimating() const { + return InternalAList().IsAnimating(); +} + +SVGAnimatedNumberList& DOMSVGAnimatedNumberList::InternalAList() { + return *mElement->GetAnimatedNumberList(mAttrEnum); +} + +const SVGAnimatedNumberList& DOMSVGAnimatedNumberList::InternalAList() const { + return *mElement->GetAnimatedNumberList(mAttrEnum); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedNumberList.h b/dom/svg/DOMSVGAnimatedNumberList.h new file mode 100644 index 0000000000..95c9454063 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedNumberList.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDNUMBERLIST_H_ +#define DOM_SVG_DOMSVGANIMATEDNUMBERLIST_H_ + +#include "nsCycleCollectionParticipant.h" +#include "SVGElement.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class SVGAnimatedNumberList; +class SVGNumberList; + +namespace dom { + +class DOMSVGNumberList; + +/** + * Class DOMSVGAnimatedNumberList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGAnimatedNumberList objects. + * + * See the architecture comment in DOMSVGAnimatedLengthList.h (that's + * LENGTH list). The comment for that class largly applies to this one too + * and will go a long way to helping you understand the architecture here. + * + * This class is strongly intertwined with DOMSVGNumberList and DOMSVGNumber. + * Our DOMSVGNumberList base and anim vals are friends and take care of nulling + * out our pointers to them when they die (making our pointers to them true + * weak refs). + */ +class DOMSVGAnimatedNumberList final : public nsWrapperCache { + friend class DOMSVGNumberList; + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedNumberList) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedNumberList) + + /** + * Factory method to create and return a DOMSVGAnimatedNumberList wrapper + * for a given internal SVGAnimatedNumberList object. The factory takes care + * of caching the object that it returns so that the same object can be + * returned for the given SVGAnimatedNumberList each time it is requested. + * The cached object is only removed from the cache when it is destroyed due + * to there being no more references to it or to any of its descendant + * objects. If that happens, any subsequent call requesting the DOM wrapper + * for the SVGAnimatedNumberList will naturally result in a new + * DOMSVGAnimatedNumberList being returned. + */ + static already_AddRefed<DOMSVGAnimatedNumberList> GetDOMWrapper( + SVGAnimatedNumberList* aList, dom::SVGElement* aElement, + uint8_t aAttrEnum); + + /** + * This method returns the DOMSVGAnimatedNumberList wrapper for an internal + * SVGAnimatedNumberList object if it currently has a wrapper. If it does + * not, then nullptr is returned. + */ + static DOMSVGAnimatedNumberList* GetDOMWrapperIfExists( + SVGAnimatedNumberList* aList); + + /** + * Called by internal code to notify us when we need to sync the length of + * our baseVal DOM list with its internal list. This is called just prior to + * the length of the internal baseVal list being changed so that any DOM list + * items that need to be removed from the DOM list can first get their values + * from their internal counterpart. + * + * The only time this method could fail is on OOM when trying to increase the + * length of the DOM list. If that happens then this method simply clears the + * list and returns. Callers just proceed as normal, and we simply accept + * that the DOM list will be empty (until successfully set to a new value). + */ + void InternalBaseValListWillChangeTo(const SVGNumberList& aNewValue); + void InternalAnimValListWillChangeTo(const SVGNumberList& aNewValue); + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool IsAnimating() const; + + // WebIDL + dom::SVGElement* GetParentObject() const { return mElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + // These aren't weak refs because mBaseVal and mAnimVal are weak + already_AddRefed<DOMSVGNumberList> BaseVal(); + already_AddRefed<DOMSVGNumberList> AnimVal(); + + private: + /** + * Only our static GetDOMWrapper() factory method may create objects of our + * type. + */ + DOMSVGAnimatedNumberList(dom::SVGElement* aElement, uint8_t aAttrEnum) + : mBaseVal(nullptr), + mAnimVal(nullptr), + mElement(aElement), + mAttrEnum(aAttrEnum) {} + + ~DOMSVGAnimatedNumberList(); + + /// Get a reference to this DOM wrapper object's internal counterpart. + SVGAnimatedNumberList& InternalAList(); + const SVGAnimatedNumberList& InternalAList() const; + + // Weak refs to our DOMSVGNumberList baseVal/animVal objects. These objects + // are friends and take care of clearing these pointers when they die, making + // these true weak references. + DOMSVGNumberList* mBaseVal; + DOMSVGNumberList* mAnimVal; + + // Strong ref to our element to keep it alive. We hold this not only for + // ourself, but also for our base/animVal and all of their items. + RefPtr<dom::SVGElement> mElement; + + uint8_t mAttrEnum; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANIMATEDNUMBERLIST_H_ diff --git a/dom/svg/DOMSVGAnimatedString.cpp b/dom/svg/DOMSVGAnimatedString.cpp new file mode 100644 index 0000000000..774bbba779 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedString.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedString.h" + +#include "mozilla/dom/SVGAnimatedStringBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedString, + mSVGElement) + +JSObject* DOMSVGAnimatedString::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedString_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedString.h b/dom/svg/DOMSVGAnimatedString.h new file mode 100644 index 0000000000..0fe7b330a0 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedString.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDSTRING_H_ +#define DOM_SVG_DOMSVGANIMATEDSTRING_H_ + +#include "mozilla/SVGAnimatedClassOrString.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedString final : public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedString) + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedString) + + DOMSVGAnimatedString(SVGAnimatedClassOrString* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL + SVGElement* GetParentObject() const { return mSVGElement; } + + void GetBaseVal(nsAString& aResult) { + mVal->GetBaseValue(aResult, mSVGElement); + } + void SetBaseVal(const nsAString& aValue) { + mVal->SetBaseValue(aValue, mSVGElement, true); + } + void GetAnimVal(nsAString& aResult) { + mSVGElement->FlushAnimations(); + mVal->GetAnimValue(aResult, mSVGElement); + } + + private: + ~DOMSVGAnimatedString() { mVal->RemoveTearoff(); } + + SVGAnimatedClassOrString* mVal; // kept alive because it belongs to content + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDSTRING_H_ diff --git a/dom/svg/DOMSVGAnimatedTransformList.cpp b/dom/svg/DOMSVGAnimatedTransformList.cpp new file mode 100644 index 0000000000..ab09bab1fc --- /dev/null +++ b/dom/svg/DOMSVGAnimatedTransformList.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedTransformList.h" + +#include "DOMSVGTransformList.h" +#include "SVGAnimatedTransformList.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/dom/SVGAnimatedTransformListBinding.h" + +namespace mozilla::dom { + +static SVGAttrTearoffTable<SVGAnimatedTransformList, + DOMSVGAnimatedTransformList> + sSVGAnimatedTransformListTearoffTable; + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedTransformList, + mElement) + +JSObject* DOMSVGAnimatedTransformList::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedTransformList_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +already_AddRefed<DOMSVGTransformList> DOMSVGAnimatedTransformList::BaseVal() { + if (!mBaseVal) { + mBaseVal = new DOMSVGTransformList(this, InternalAList().GetBaseValue()); + } + RefPtr<DOMSVGTransformList> baseVal = mBaseVal; + return baseVal.forget(); +} + +already_AddRefed<DOMSVGTransformList> DOMSVGAnimatedTransformList::AnimVal() { + if (!mAnimVal) { + mAnimVal = new DOMSVGTransformList(this, InternalAList().GetAnimValue()); + } + RefPtr<DOMSVGTransformList> animVal = mAnimVal; + return animVal.forget(); +} + +/* static */ +already_AddRefed<DOMSVGAnimatedTransformList> +DOMSVGAnimatedTransformList::GetDOMWrapper(SVGAnimatedTransformList* aList, + SVGElement* aElement) { + RefPtr<DOMSVGAnimatedTransformList> wrapper = + sSVGAnimatedTransformListTearoffTable.GetTearoff(aList); + if (!wrapper) { + wrapper = new DOMSVGAnimatedTransformList(aElement); + sSVGAnimatedTransformListTearoffTable.AddTearoff(aList, wrapper); + } + return wrapper.forget(); +} + +/* static */ +DOMSVGAnimatedTransformList* DOMSVGAnimatedTransformList::GetDOMWrapperIfExists( + SVGAnimatedTransformList* aList) { + return sSVGAnimatedTransformListTearoffTable.GetTearoff(aList); +} + +DOMSVGAnimatedTransformList::~DOMSVGAnimatedTransformList() { + // Script no longer has any references to us, to our base/animVal objects, or + // to any of their list items. + sSVGAnimatedTransformListTearoffTable.RemoveTearoff(&InternalAList()); +} + +void DOMSVGAnimatedTransformList::InternalBaseValListWillChangeLengthTo( + uint32_t aNewLength) { + // When the number of items in our internal counterpart's baseVal changes, + // we MUST keep our baseVal in sync. If we don't, script will either see a + // list that is too short and be unable to access indexes that should be + // valid, or else, MUCH WORSE, script will see a list that is too long and be + // able to access "items" at indexes that are out of bounds (read/write to + // bad memory)!! + + RefPtr<DOMSVGAnimatedTransformList> kungFuDeathGrip; + if (mBaseVal) { + if (aNewLength < mBaseVal->LengthNoFlush()) { + // InternalListLengthWillChange might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + mBaseVal->InternalListLengthWillChange(aNewLength); + } + + // If our attribute is not animating, then our animVal mirrors our baseVal + // and we must sync its length too. (If our attribute is animating, then the + // SMIL engine takes care of calling InternalAnimValListWillChangeLengthTo() + // if necessary.) + + if (!IsAnimating()) { + InternalAnimValListWillChangeLengthTo(aNewLength); + } +} + +void DOMSVGAnimatedTransformList::InternalAnimValListWillChangeLengthTo( + uint32_t aNewLength) { + if (mAnimVal) { + mAnimVal->InternalListLengthWillChange(aNewLength); + } +} + +bool DOMSVGAnimatedTransformList::IsAnimating() const { + return InternalAList().IsAnimating(); +} + +SVGAnimatedTransformList& DOMSVGAnimatedTransformList::InternalAList() { + return *mElement->GetAnimatedTransformList(); +} + +const SVGAnimatedTransformList& DOMSVGAnimatedTransformList::InternalAList() + const { + return *mElement->GetAnimatedTransformList(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedTransformList.h b/dom/svg/DOMSVGAnimatedTransformList.h new file mode 100644 index 0000000000..3da8240d96 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedTransformList.h @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDTRANSFORMLIST_H_ +#define DOM_SVG_DOMSVGANIMATEDTRANSFORMLIST_H_ + +#include "nsCycleCollectionParticipant.h" +#include "SVGElement.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class SVGAnimatedTransformList; + +namespace dom { + +class DOMSVGTransformList; + +/** + * Class DOMSVGAnimatedTransformList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGAnimatedTransformList objects. + * + * See the architecture comment in DOMSVGAnimatedLengthList.h (that's + * LENGTH list). The comment for that class largly applies to this one too + * and will go a long way to helping you understand the architecture here. + * + * This class is strongly intertwined with DOMSVGTransformList and + * DOMSVGTransform. + * Our DOMSVGTransformList base and anim vals are friends and take care of + * nulling out our pointers to them when they die (making our pointers to them + * true weak refs). + */ +class DOMSVGAnimatedTransformList final : public nsWrapperCache { + friend class DOMSVGTransformList; + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING( + DOMSVGAnimatedTransformList) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS( + DOMSVGAnimatedTransformList) + + /** + * Factory method to create and return a DOMSVGAnimatedTransformList wrapper + * for a given internal SVGAnimatedTransformList object. The factory takes + * care of caching the object that it returns so that the same object can be + * returned for the given SVGAnimatedTransformList each time it is + * requested. The cached object is only removed from the cache when it is + * destroyed due to there being no more references to it or to any of its + * descendant objects. If that happens, any subsequent call requesting the DOM + * wrapper for the SVGAnimatedTransformList will naturally result in a new + * DOMSVGAnimatedTransformList being returned. + */ + static already_AddRefed<DOMSVGAnimatedTransformList> GetDOMWrapper( + SVGAnimatedTransformList* aList, SVGElement* aElement); + + /** + * This method returns the DOMSVGAnimatedTransformList wrapper for an internal + * SVGAnimatedTransformList object if it currently has a wrapper. If it does + * not, then nullptr is returned. + */ + static DOMSVGAnimatedTransformList* GetDOMWrapperIfExists( + SVGAnimatedTransformList* aList); + + /** + * Called by internal code to notify us when we need to sync the length of + * our baseVal DOM list with its internal list. This is called just prior to + * the length of the internal baseVal list being changed so that any DOM list + * items that need to be removed from the DOM list can first get their values + * from their internal counterpart. + * + * The only time this method could fail is on OOM when trying to increase the + * length of the DOM list. If that happens then this method simply clears the + * list and returns. Callers just proceed as normal, and we simply accept + * that the DOM list will be empty (until successfully set to a new value). + */ + void InternalBaseValListWillChangeLengthTo(uint32_t aNewLength); + void InternalAnimValListWillChangeLengthTo(uint32_t aNewLength); + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool IsAnimating() const; + + // WebIDL + SVGElement* GetParentObject() const { return mElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + // These aren't weak refs because mBaseVal and mAnimVal are weak + already_AddRefed<DOMSVGTransformList> BaseVal(); + already_AddRefed<DOMSVGTransformList> AnimVal(); + + private: + /** + * Only our static GetDOMWrapper() factory method may create objects of our + * type. + */ + explicit DOMSVGAnimatedTransformList(SVGElement* aElement) + : mBaseVal(nullptr), mAnimVal(nullptr), mElement(aElement) {} + + ~DOMSVGAnimatedTransformList(); + + /// Get a reference to this DOM wrapper object's internal counterpart. + SVGAnimatedTransformList& InternalAList(); + const SVGAnimatedTransformList& InternalAList() const; + + // Weak refs to our DOMSVGTransformList baseVal/animVal objects. These objects + // are friends and take care of clearing these pointers when they die, making + // these true weak references. + DOMSVGTransformList* mBaseVal; + DOMSVGTransformList* mAnimVal; + + // Strong ref to our element to keep it alive. We hold this not only for + // ourself, but also for our base/animVal and all of their items. + RefPtr<SVGElement> mElement; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANIMATEDTRANSFORMLIST_H_ diff --git a/dom/svg/DOMSVGLength.cpp b/dom/svg/DOMSVGLength.cpp new file mode 100644 index 0000000000..d112998d72 --- /dev/null +++ b/dom/svg/DOMSVGLength.cpp @@ -0,0 +1,443 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGLength.h" + +#include "DOMSVGLengthList.h" +#include "DOMSVGAnimatedLengthList.h" +#include "nsError.h" +#include "nsMathUtils.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAttrTearoffTable.h" +#include "SVGLength.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/FloatingPoint.h" + +// See the architecture comment in DOMSVGAnimatedLengthList.h. + +namespace mozilla::dom { + +static SVGAttrTearoffTable<SVGAnimatedLength, DOMSVGLength> + sBaseSVGLengthTearOffTable, sAnimSVGLengthTearOffTable; + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our list's weak ref to us to be safe. (The other option would be to +// not unlink and rely on the breaking of the other edges in the cycle, as +// NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGLength) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGLength) + tmp->CleanupWeakRefs(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGLength) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGLength) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +DOMSVGLength::DOMSVGLength(DOMSVGLengthList* aList, uint8_t aAttrEnum, + uint32_t aListIndex, bool aIsAnimValItem) + : mOwner(aList), + mListIndex(aListIndex), + mAttrEnum(aAttrEnum), + mIsAnimValItem(aIsAnimValItem), + mUnit(SVGLength_Binding::SVG_LENGTHTYPE_NUMBER) { + MOZ_ASSERT(aList, "bad arg"); + MOZ_ASSERT(mAttrEnum == aAttrEnum, "bitfield too small"); + MOZ_ASSERT(aListIndex <= MaxListIndex(), "list index too large"); + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!"); +} + +DOMSVGLength::DOMSVGLength() + : mOwner(nullptr), + mListIndex(0), + mAttrEnum(0), + mIsAnimValItem(false), + mUnit(SVGLength_Binding::SVG_LENGTHTYPE_NUMBER) {} + +DOMSVGLength::DOMSVGLength(SVGAnimatedLength* aVal, SVGElement* aSVGElement, + bool aAnimVal) + : mOwner(aSVGElement), + mListIndex(0), + mAttrEnum(aVal->mAttrEnum), + mIsAnimValItem(aAnimVal), + mUnit(SVGLength_Binding::SVG_LENGTHTYPE_NUMBER) { + MOZ_ASSERT(aVal, "bad arg"); + MOZ_ASSERT(mAttrEnum == aVal->mAttrEnum, "bitfield too small"); +} + +void DOMSVGLength::CleanupWeakRefs() { + // Our mList's weak ref to us must be nulled out when we die (or when we're + // cycle collected), so we that don't leave behind a pointer to + // free / soon-to-be-free memory. + if (nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner)) { + MOZ_ASSERT(lengthList->mItems[mListIndex] == this, + "Clearing out the wrong list index...?"); + lengthList->mItems[mListIndex] = nullptr; + } + + // Similarly, we must update the tearoff table to remove its (non-owning) + // pointer to mVal. + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + auto& table = mIsAnimValItem ? sAnimSVGLengthTearOffTable + : sBaseSVGLengthTearOffTable; + table.RemoveTearoff(svg->GetAnimatedLength(mAttrEnum)); + } +} + +already_AddRefed<DOMSVGLength> DOMSVGLength::GetTearOff(SVGAnimatedLength* aVal, + SVGElement* aSVGElement, + bool aAnimVal) { + auto& table = + aAnimVal ? sAnimSVGLengthTearOffTable : sBaseSVGLengthTearOffTable; + RefPtr<DOMSVGLength> domLength = table.GetTearoff(aVal); + if (!domLength) { + domLength = new DOMSVGLength(aVal, aSVGElement, aAnimVal); + table.AddTearoff(aVal, domLength); + } + + return domLength.forget(); +} + +DOMSVGLength* DOMSVGLength::Copy() { + NS_ASSERTION(HasOwner(), "unexpected caller"); + DOMSVGLength* copy = new DOMSVGLength(); + uint16_t unit; + float value; + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + SVGAnimatedLength* length = svg->GetAnimatedLength(mAttrEnum); + unit = length->GetSpecifiedUnitType(); + value = mIsAnimValItem ? length->GetAnimValInSpecifiedUnits() + : length->GetBaseValInSpecifiedUnits(); + } else { + const SVGLength& length = InternalItem(); + unit = length.GetUnit(); + value = length.GetValueInCurrentUnits(); + } + copy->NewValueSpecifiedUnits(unit, value, IgnoreErrors()); + return copy; +} + +uint16_t DOMSVGLength::UnitType() { + if (mIsAnimValItem) { + Element()->FlushAnimations(); + } + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + return svg->GetAnimatedLength(mAttrEnum)->GetSpecifiedUnitType(); + } + return HasOwner() ? InternalItem().GetUnit() : mUnit; +} + +float DOMSVGLength::GetValue(ErrorResult& aRv) { + if (mIsAnimValItem) { + Element()->FlushAnimations(); // May make HasOwner() == false + } + + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + SVGAnimatedLength* length = svg->GetAnimatedLength(mAttrEnum); + return mIsAnimValItem ? length->GetAnimValue(svg) + : length->GetBaseValue(svg); + } + + if (nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner)) { + float value = InternalItem().GetValueInUserUnits(lengthList->Element(), + lengthList->Axis()); + if (!IsFinite(value)) { + aRv.Throw(NS_ERROR_FAILURE); + } + return value; + } + + float unitToPx; + if (UserSpaceMetrics::ResolveAbsoluteUnit(mUnit, unitToPx)) { + return mValue * unitToPx; + } + + // else [SVGWG issue] Can't convert this length's value to user units + // ReportToConsole + aRv.Throw(NS_ERROR_FAILURE); + return 0.0f; +} + +void DOMSVGLength::SetValue(float aUserUnitValue, ErrorResult& aRv) { + if (mIsAnimValItem) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + aRv = svg->GetAnimatedLength(mAttrEnum)->SetBaseValue(aUserUnitValue, svg, + true); + return; + } + + // Although the value passed in is in user units, this method does not turn + // this length into a user unit length. Instead it converts the user unit + // value to this length's current unit and sets that, leaving this length's + // unit as it is. + + if (nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner)) { + SVGLength& internalItem = InternalItem(); + if (internalItem.GetValueInUserUnits( + lengthList->Element(), lengthList->Axis()) == aUserUnitValue) { + return; + } + float uuPerUnit = internalItem.GetUserUnitsPerUnit(lengthList->Element(), + lengthList->Axis()); + if (uuPerUnit > 0) { + float newValue = aUserUnitValue / uuPerUnit; + if (IsFinite(newValue)) { + AutoChangeLengthListNotifier notifier(this); + internalItem.SetValueAndUnit(newValue, internalItem.GetUnit()); + return; + } + } + } else if (mUnit == SVGLength_Binding::SVG_LENGTHTYPE_NUMBER || + mUnit == SVGLength_Binding::SVG_LENGTHTYPE_PX) { + mValue = aUserUnitValue; + return; + } + // else [SVGWG issue] Can't convert user unit value to this length's unit + // ReportToConsole + aRv.Throw(NS_ERROR_FAILURE); +} + +float DOMSVGLength::ValueInSpecifiedUnits() { + if (mIsAnimValItem) { + Element()->FlushAnimations(); // May make HasOwner() == false + } + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + SVGAnimatedLength* length = svg->GetAnimatedLength(mAttrEnum); + return mIsAnimValItem ? length->GetAnimValInSpecifiedUnits() + : length->GetBaseValInSpecifiedUnits(); + } + + return HasOwner() ? InternalItem().GetValueInCurrentUnits() : mValue; +} + +void DOMSVGLength::SetValueInSpecifiedUnits(float aValue, ErrorResult& aRv) { + if (mIsAnimValItem) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + svg->GetAnimatedLength(mAttrEnum)->SetBaseValueInSpecifiedUnits(aValue, svg, + true); + return; + } + + if (HasOwner()) { + SVGLength& internalItem = InternalItem(); + if (internalItem.GetValueInCurrentUnits() == aValue) { + return; + } + AutoChangeLengthListNotifier notifier(this); + internalItem.SetValueInCurrentUnits(aValue); + return; + } + mValue = aValue; +} + +void DOMSVGLength::SetValueAsString(const nsAString& aValue, ErrorResult& aRv) { + if (mIsAnimValItem) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + aRv = svg->GetAnimatedLength(mAttrEnum)->SetBaseValueString(aValue, svg, + true); + return; + } + + SVGLength value; + if (!value.SetValueFromString(aValue)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + if (HasOwner()) { + SVGLength& internalItem = InternalItem(); + if (internalItem == value) { + return; + } + AutoChangeLengthListNotifier notifier(this); + internalItem = value; + return; + } + mValue = value.GetValueInCurrentUnits(); + mUnit = value.GetUnit(); +} + +void DOMSVGLength::GetValueAsString(nsAString& aValue) { + if (mIsAnimValItem) { + Element()->FlushAnimations(); // May make HasOwner() == false + } + + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + SVGAnimatedLength* length = svg->GetAnimatedLength(mAttrEnum); + if (mIsAnimValItem) { + length->GetAnimValueString(aValue); + } else { + length->GetBaseValueString(aValue); + } + return; + } + if (HasOwner()) { + InternalItem().GetValueAsString(aValue); + return; + } + SVGLength(mValue, mUnit).GetValueAsString(aValue); +} + +void DOMSVGLength::NewValueSpecifiedUnits(uint16_t aUnit, float aValue, + ErrorResult& aRv) { + if (mIsAnimValItem) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + svg->GetAnimatedLength(mAttrEnum)->NewValueSpecifiedUnits(aUnit, aValue, + svg); + return; + } + + if (!SVGLength::IsValidUnitType(aUnit)) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + if (HasOwner()) { + SVGLength& internalItem = InternalItem(); + if (internalItem == SVGLength(aValue, aUnit)) { + return; + } + AutoChangeLengthListNotifier notifier(this); + internalItem.SetValueAndUnit(aValue, uint8_t(aUnit)); + return; + } + mUnit = uint8_t(aUnit); + mValue = aValue; +} + +void DOMSVGLength::ConvertToSpecifiedUnits(uint16_t aUnit, ErrorResult& aRv) { + if (mIsAnimValItem) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + svg->GetAnimatedLength(mAttrEnum)->ConvertToSpecifiedUnits(aUnit, svg); + return; + } + + if (!SVGLength::IsValidUnitType(aUnit)) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + float val; + if (nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner)) { + SVGLength& length = InternalItem(); + if (length.GetUnit() == aUnit) { + return; + } + val = length.GetValueInSpecifiedUnit(aUnit, lengthList->Element(), + lengthList->Axis()); + } else { + val = SVGLength(mValue, mUnit).GetValueInSpecifiedUnit(aUnit, nullptr, 0); + } + if (IsFinite(val)) { + if (HasOwner()) { + AutoChangeLengthListNotifier notifier(this); + InternalItem().SetValueAndUnit(val, aUnit); + } else { + mValue = val; + mUnit = aUnit; + } + return; + } + // else [SVGWG issue] Can't convert unit + // ReportToConsole + aRv.Throw(NS_ERROR_FAILURE); +} + +JSObject* DOMSVGLength::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGLength_Binding::Wrap(aCx, this, aGivenProto); +} + +void DOMSVGLength::InsertingIntoList(DOMSVGLengthList* aList, uint8_t aAttrEnum, + uint32_t aListIndex, bool aIsAnimValItem) { + NS_ASSERTION(!HasOwner(), "Inserting item that is already in a list"); + + mOwner = aList; + mAttrEnum = aAttrEnum; + mListIndex = aListIndex; + mIsAnimValItem = aIsAnimValItem; + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGLength!"); +} + +void DOMSVGLength::RemovingFromList() { + mValue = InternalItem().GetValueInCurrentUnits(); + mUnit = InternalItem().GetUnit(); + mOwner = nullptr; + mIsAnimValItem = false; +} + +SVGLength DOMSVGLength::ToSVGLength() { + if (nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner)) { + SVGAnimatedLength* length = svg->GetAnimatedLength(mAttrEnum); + return SVGLength(mIsAnimValItem ? length->GetAnimValInSpecifiedUnits() + : length->GetBaseValInSpecifiedUnits(), + length->GetSpecifiedUnitType()); + } + return HasOwner() ? InternalItem() : SVGLength(mValue, mUnit); +} + +bool DOMSVGLength::IsAnimating() const { + if (nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner)) { + return lengthList->IsAnimating(); + } + nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner); + return svg && svg->GetAnimatedLength(mAttrEnum)->IsAnimated(); +} + +SVGElement* DOMSVGLength::Element() { + if (nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner)) { + return lengthList->Element(); + } + nsCOMPtr<SVGElement> svg = do_QueryInterface(mOwner); + return svg; +} + +SVGLength& DOMSVGLength::InternalItem() { + nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner); + SVGAnimatedLengthList* alist = + lengthList->Element()->GetAnimatedLengthList(mAttrEnum); + return mIsAnimValItem && alist->mAnimVal ? (*alist->mAnimVal)[mListIndex] + : alist->mBaseVal[mListIndex]; +} + +#ifdef DEBUG +bool DOMSVGLength::IndexIsValid() { + nsCOMPtr<DOMSVGLengthList> lengthList = do_QueryInterface(mOwner); + SVGAnimatedLengthList* alist = + lengthList->Element()->GetAnimatedLengthList(mAttrEnum); + return (mIsAnimValItem && mListIndex < alist->GetAnimValue().Length()) || + (!mIsAnimValItem && mListIndex < alist->GetBaseValue().Length()); +} +#endif + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGLength.h b/dom/svg/DOMSVGLength.h new file mode 100644 index 0000000000..8aeee2d574 --- /dev/null +++ b/dom/svg/DOMSVGLength.h @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGLENGTH_H_ +#define DOM_SVG_DOMSVGLENGTH_H_ + +#include "DOMSVGLengthList.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsTArray.h" +#include "SVGLength.h" +#include "mozilla/Attributes.h" +#include "nsWrapperCache.h" + +#define MOZ_SVG_LIST_INDEX_BIT_COUNT 22 // supports > 4 million list items + +namespace mozilla { + +class ErrorResult; + +namespace dom { +class SVGElement; + +/** + * Class DOMSVGLength + * + * This class creates the DOM objects that wrap internal SVGLength objects that + * are in an SVGLengthList. It is also used to create the objects returned by + * SVGSVGElement.createSVGLength(). + * + * For the DOM wrapper classes for non-list SVGLength, see SVGAnimatedLength.h. + * + * See the architecture comment in DOMSVGAnimatedLengthList.h. + * + * This class is strongly intertwined with DOMSVGAnimatedLengthList and + * DOMSVGLengthList. We are a friend of DOMSVGLengthList, and are responsible + * for nulling out our DOMSVGLengthList's pointer to us when we die, making it + * a real weak pointer. + * + * When objects of this type are in a DOMSVGLengthList they belong to an + * attribute. While they belong to an attribute, the objects' values come from + * their corresponding internal SVGLength objects in the internal SVGLengthList + * objects for the attribute. Getting and setting values of a DOMSVGLength + * requires reading and writing to its internal SVGLength. However, if the + * DOMSVGLength is detached from its DOMSVGLengthList then it first makes a + * copy of its internal SVGLength's value and unit so that it doesn't appear to + * "lose" its value from script's perspective on being removed from the list. + * This means that these DOM tearoffs have space to store these values, even + * though they're not used in the common case. + * + * Objects of this type are also used to reflect the baseVal and animVal of + * a single, non-list SVGLength attribute. Getting and settings values of the + * DOMSVGLength in this case requires reading and writing to the corresponding + * SVGAnimatedLength object. + * + * This class also stores its current list index, attribute enum, and whether + * it belongs to a baseVal or animVal list. This is so that objects of this + * type can find their corresponding internal SVGLength. + * + * To use these classes for <length> attributes as well as <list-of-length> + * attributes, we would need to take a bit from mListIndex and use that to + * indicate whether the object belongs to a list or non-list attribute, then + * if-else as appropriate. The bug for doing that work is: + * https://bugzilla.mozilla.org/show_bug.cgi?id=571734 + */ +class DOMSVGLength final : public nsWrapperCache { + template <class T> + friend class AutoChangeLengthListNotifier; + + /** + * Ctor for creating the object returned by + * SVGAnimatedLength::ToDOMBaseVal/ToDOMAnimVal + */ + DOMSVGLength(SVGAnimatedLength* aVal, dom::SVGElement* aSVGElement, + bool aAnimVal); + + ~DOMSVGLength() { CleanupWeakRefs(); } + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGLength) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGLength) + + /** + * Generic ctor for DOMSVGLength objects that are created for an attribute. + */ + DOMSVGLength(DOMSVGLengthList* aList, uint8_t aAttrEnum, uint32_t aListIndex, + bool aIsAnimValItem); + + /** + * Ctor for creating the objects returned by SVGSVGElement.createSVGLength(), + * which do not initially belong to an attribute. + */ + DOMSVGLength(); + + static already_AddRefed<DOMSVGLength> GetTearOff(SVGAnimatedLength* aVal, + dom::SVGElement* aSVGElement, + bool aAnimVal); + + /** + * Create an unowned copy of a length that is owned or is reflecting a single + * attribute. The caller is responsible for the first AddRef(). + */ + DOMSVGLength* Copy(); + + /** + * Returns true if our attribute is animating. + */ + bool IsAnimating() const; + + /** + * In future, if this class is used for non-list lengths, this will be + * different to IsInList(). + */ + bool HasOwner() const { return !!mOwner; } + + /** + * This method is called to notify this DOM object that it is being inserted + * into a list, and give it the information it needs as a result. + * + * This object MUST NOT already belong to a list when this method is called. + * That's not to say that script can't move these DOM objects between + * lists - it can - it's just that the logic to handle that (and send out + * the necessary notifications) is located elsewhere (in DOMSVGLengthList).) + */ + void InsertingIntoList(DOMSVGLengthList* aList, uint8_t aAttrEnum, + uint32_t aListIndex, bool aIsAnimValItem); + + static uint32_t MaxListIndex() { + return (1U << MOZ_SVG_LIST_INDEX_BIT_COUNT) - 1; + } + + /// This method is called to notify this object that its list index changed. + void UpdateListIndex(uint32_t aListIndex) { mListIndex = aListIndex; } + + /** + * This method is called to notify this DOM object that it is about to be + * removed from its current DOM list so that it can first make a copy of its + * internal counterpart's values. (If it didn't do this, then it would + * "lose" its value on being removed.) + */ + void RemovingFromList(); + + SVGLength ToSVGLength(); + + // WebIDL + uint16_t UnitType(); + float GetValue(ErrorResult& aRv); + void SetValue(float aUserUnitValue, ErrorResult& aRv); + float ValueInSpecifiedUnits(); + void SetValueInSpecifiedUnits(float aValue, ErrorResult& aRv); + void GetValueAsString(nsAString& aValue); + void SetValueAsString(const nsAString& aValue, ErrorResult& aRv); + void NewValueSpecifiedUnits(uint16_t aUnit, float aValue, ErrorResult& aRv); + void ConvertToSpecifiedUnits(uint16_t aUnit, ErrorResult& aRv); + + nsISupports* GetParentObject() { return mOwner; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + private: + dom::SVGElement* Element(); + + uint8_t AttrEnum() const { return mAttrEnum; } + + /** + * Get a reference to the internal SVGLength list item that this DOM wrapper + * object currently wraps. + * + * To simplify the code we just have this one method for obtaining both + * baseVal and animVal internal items. This means that animVal items don't + * get const protection, but then our setter methods guard against changing + * animVal items. + */ + SVGLength& InternalItem(); + +#ifdef DEBUG + bool IndexIsValid(); +#endif + + /** + * Clears soon-to-be-invalid weak references in external objects that were + * set up during the creation of this object. This should be called during + * destruction and during cycle collection. + */ + void CleanupWeakRefs(); + + RefPtr<nsISupports> mOwner; // Either a DOMSVGLengthList if we're in a list, + // an SVGElement if we're an attribute or null + + // Bounds for the following are checked in the ctor, so be sure to update + // that if you change the capacity of any of the following. + + uint32_t mListIndex : MOZ_SVG_LIST_INDEX_BIT_COUNT; + uint32_t mAttrEnum : 4; // supports up to 16 attributes + uint32_t mIsAnimValItem : 1; + + // The following members are only used when we're not in a list: + uint32_t mUnit : 5; // can handle 31 units (the 10 SVG 1.1 units + rem, vw, + // vh, wm, calc + future additions) + float mValue = 0.0f; +}; + +} // namespace dom +} // namespace mozilla + +#undef MOZ_SVG_LIST_INDEX_BIT_COUNT + +#endif // DOM_SVG_DOMSVGLENGTH_H_ diff --git a/dom/svg/DOMSVGLengthList.cpp b/dom/svg/DOMSVGLengthList.cpp new file mode 100644 index 0000000000..21b4e2efb6 --- /dev/null +++ b/dom/svg/DOMSVGLengthList.cpp @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGLengthList.h" + +#include "SVGElement.h" +#include "DOMSVGLength.h" +#include "nsError.h" +#include "SVGAnimatedLengthList.h" +#include "mozilla/dom/SVGLengthListBinding.h" +#include <algorithm> + +// See the comment in this file's header. + +// local helper functions +namespace { + +using mozilla::dom::DOMSVGLength; + +void UpdateListIndicesFromIndex(FallibleTArray<DOMSVGLength*>& aItemsArray, + uint32_t aStartingIndex) { + uint32_t length = aItemsArray.Length(); + + for (uint32_t i = aStartingIndex; i < length; ++i) { + if (aItemsArray[i]) { + aItemsArray[i]->UpdateListIndex(i); + } + } +} + +} // namespace + +namespace mozilla::dom { + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our DOMSVGAnimatedLengthList's weak ref to us to be safe. (The other +// option would be to not unlink and rely on the breaking of the other edges in +// the cycle, as NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGLengthList) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGLengthList) + if (tmp->mAList) { + if (tmp->IsAnimValList()) { + tmp->mAList->mAnimVal = nullptr; + } else { + tmp->mAList->mBaseVal = nullptr; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAList) + } + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGLengthList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGLengthList) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGLengthList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGLengthList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGLengthList) + NS_INTERFACE_MAP_ENTRY(DOMSVGLengthList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +void DOMSVGLengthList::IndexedSetter(uint32_t index, DOMSVGLength& newValue, + ErrorResult& error) { + // Need to take a ref to the return value so it does not leak. + RefPtr<DOMSVGLength> ignored = ReplaceItem(newValue, index, error); + Unused << ignored; +} + +JSObject* DOMSVGLengthList::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return mozilla::dom::SVGLengthList_Binding::Wrap(cx, this, aGivenProto); +} + +void DOMSVGLengthList::InternalListLengthWillChange(uint32_t aNewLength) { + uint32_t oldLength = mItems.Length(); + + if (aNewLength > DOMSVGLength::MaxListIndex()) { + // It's safe to get out of sync with our internal list as long as we have + // FEWER items than it does. + aNewLength = DOMSVGLength::MaxListIndex(); + } + + RefPtr<DOMSVGLengthList> kungFuDeathGrip; + if (aNewLength < oldLength) { + // RemovingFromList() might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + + // If our length will decrease, notify the items that will be removed: + for (uint32_t i = aNewLength; i < oldLength; ++i) { + if (mItems[i]) { + mItems[i]->RemovingFromList(); + } + } + + if (!mItems.SetLength(aNewLength, fallible)) { + // We silently ignore SetLength OOM failure since being out of sync is safe + // so long as we have *fewer* items than our internal list. + mItems.Clear(); + return; + } + + // If our length has increased, null out the new pointers: + for (uint32_t i = oldLength; i < aNewLength; ++i) { + mItems[i] = nullptr; + } +} + +SVGLengthList& DOMSVGLengthList::InternalList() const { + SVGAnimatedLengthList* alist = Element()->GetAnimatedLengthList(AttrEnum()); + return IsAnimValList() && alist->mAnimVal ? *alist->mAnimVal + : alist->mBaseVal; +} + +// ---------------------------------------------------------------------------- + +void DOMSVGLengthList::Clear(ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (LengthNoFlush() > 0) { + AutoChangeLengthListNotifier notifier(this); + // Notify any existing DOM items of removal *before* truncating the lists + // so that they can find their SVGLength internal counterparts and copy + // their values. This also notifies the animVal list: + mAList->InternalBaseValListWillChangeTo(SVGLengthList()); + + mItems.Clear(); + InternalList().Clear(); + } +} + +already_AddRefed<DOMSVGLength> DOMSVGLengthList::Initialize( + DOMSVGLength& newItem, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + // If newItem already has an owner or is reflecting an attribute, we should + // insert a clone of newItem, and for consistency, this should happen even if + // *this* is the list that newItem is currently in. Note that in the case of + // newItem being in this list, the Clear() call before the InsertItemBefore() + // call would remove it from this list, and so the InsertItemBefore() call + // would not insert a clone of newItem, it would actually insert newItem. To + // prevent that from happening we have to do the clone here, if necessary. + + RefPtr<DOMSVGLength> domItem = &newItem; + if (domItem->HasOwner()) { + domItem = domItem->Copy(); + } + + ErrorResult rv; + Clear(rv); + MOZ_ASSERT(!rv.Failed()); + return InsertItemBefore(*domItem, 0, error); +} + +already_AddRefed<DOMSVGLength> DOMSVGLengthList::GetItem(uint32_t index, + ErrorResult& error) { + bool found; + RefPtr<DOMSVGLength> item = IndexedGetter(index, found, error); + if (!found) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + } + return item.forget(); +} + +already_AddRefed<DOMSVGLength> DOMSVGLengthList::IndexedGetter( + uint32_t index, bool& found, ErrorResult& error) { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + found = index < LengthNoFlush(); + if (found) { + return GetItemAt(index); + } + return nullptr; +} + +already_AddRefed<DOMSVGLength> DOMSVGLengthList::InsertItemBefore( + DOMSVGLength& newItem, uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + index = std::min(index, LengthNoFlush()); + if (index >= DOMSVGLength::MaxListIndex()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGLength> domItem = &newItem; + if (domItem->HasOwner()) { + domItem = domItem->Copy(); // must do this before changing anything! + } + + // Ensure we have enough memory so we can avoid complex error handling below: + if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || + !InternalList().SetCapacity(InternalList().Length() + 1)) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + if (AnimListMirrorsBaseList()) { + if (!mAList->mAnimVal->mItems.SetCapacity( + mAList->mAnimVal->mItems.Length() + 1, fallible)) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + } + + AutoChangeLengthListNotifier notifier(this); + // Now that we know we're inserting, keep animVal list in sync as necessary. + MaybeInsertNullInAnimValListAt(index); + + InternalList().InsertItem(index, domItem->ToSVGLength()); + MOZ_ALWAYS_TRUE(mItems.InsertElementAt(index, domItem.get(), fallible)); + + // This MUST come after the insertion into InternalList(), or else under the + // insertion into InternalList() the values read from domItem would be bad + // data from InternalList() itself!: + domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList()); + + UpdateListIndicesFromIndex(mItems, index + 1); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGLength> DOMSVGLengthList::ReplaceItem( + DOMSVGLength& newItem, uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (index >= LengthNoFlush()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGLength> domItem = &newItem; + if (domItem->HasOwner()) { + domItem = domItem->Copy(); // must do this before changing anything! + } + + AutoChangeLengthListNotifier notifier(this); + if (mItems[index]) { + // Notify any existing DOM item of removal *before* modifying the lists so + // that the DOM item can copy the *old* value at its index: + mItems[index]->RemovingFromList(); + } + + InternalList()[index] = domItem->ToSVGLength(); + mItems[index] = domItem; + + // This MUST come after the ToSVGPoint() call, otherwise that call + // would end up reading bad data from InternalList()! + domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList()); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGLength> DOMSVGLengthList::RemoveItem( + uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (index >= LengthNoFlush()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + AutoChangeLengthListNotifier notifier(this); + // Now that we know we're removing, keep animVal list in sync as necessary. + // Do this *before* touching InternalList() so the removed item can get its + // internal value. + MaybeRemoveItemFromAnimValListAt(index); + + // We have to return the removed item, so get it, creating it if necessary: + RefPtr<DOMSVGLength> result = GetItemAt(index); + + // Notify the DOM item of removal *before* modifying the lists so that the + // DOM item can copy its *old* value: + mItems[index]->RemovingFromList(); + + InternalList().RemoveItem(index); + mItems.RemoveElementAt(index); + + UpdateListIndicesFromIndex(mItems, index); + + return result.forget(); +} + +already_AddRefed<DOMSVGLength> DOMSVGLengthList::GetItemAt(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length()); + + if (!mItems[aIndex]) { + mItems[aIndex] = + new DOMSVGLength(this, AttrEnum(), aIndex, IsAnimValList()); + } + RefPtr<DOMSVGLength> result = mItems[aIndex]; + return result.forget(); +} + +void DOMSVGLengthList::MaybeInsertNullInAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + DOMSVGLengthList* animVal = mAList->mAnimVal; + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible)); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1); +} + +void DOMSVGLengthList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + // This needs to be a strong reference; otherwise, the RemovingFromList call + // below might drop the last reference to animVal before we're done with it. + RefPtr<DOMSVGLengthList> animVal = mAList->mAnimVal; + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + + if (animVal->mItems[aIndex]) { + animVal->mItems[aIndex]->RemovingFromList(); + } + animVal->mItems.RemoveElementAt(aIndex); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGLengthList.h b/dom/svg/DOMSVGLengthList.h new file mode 100644 index 0000000000..943bbf64d7 --- /dev/null +++ b/dom/svg/DOMSVGLengthList.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGLENGTHLIST_H_ +#define DOM_SVG_DOMSVGLENGTHLIST_H_ + +#include "DOMSVGAnimatedLengthList.h" +#include "mozAutoDocUpdate.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsTArray.h" +#include "SVGLengthList.h" +#include "mozilla/Attributes.h" +#include "mozilla/Unused.h" + +// {cbecb7a4-d6f3-47b5-b5a3-3e5bdbf5b2f9} +#define MOZILLA_DOMSVGLENGTHLIST_IID \ + { \ + 0xcbecb7a4, 0xd6f3, 0xd6f3, { \ + 0xb5, 0xa3, 0x3e, 0x5b, 0xdb, 0xf5, 0xb2, 0xf9 \ + } \ + } + +namespace mozilla { +class ErrorResult; + +namespace dom { +class DOMSVGLength; +class SVGElement; + +//---------------------------------------------------------------------- +// Helper class: AutoChangeLengthListNotifier +// Stack-based helper class to pair calls to WillChangeLengthList and +// DidChangeLengthList. Used by DOMSVGLength and DOMSVGLengthList. +template <class T> +class MOZ_RAII AutoChangeLengthListNotifier : public mozAutoDocUpdate { + public: + explicit AutoChangeLengthListNotifier(T* aValue) + : mozAutoDocUpdate(aValue->Element()->GetComposedDoc(), true), + mValue(aValue) { + MOZ_ASSERT(aValue, "Expecting non-null value"); + mEmptyOrOldValue = + mValue->Element()->WillChangeLengthList(mValue->AttrEnum(), *this); + } + + ~AutoChangeLengthListNotifier() { + mValue->Element()->DidChangeLengthList(mValue->AttrEnum(), mEmptyOrOldValue, + *this); + if (mValue->IsAnimating()) { + mValue->Element()->AnimationNeedsResample(); + } + } + + private: + T* const mValue; + nsAttrValue mEmptyOrOldValue; +}; + +/** + * Class DOMSVGLengthList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGLengthList objects. + * + * See the architecture comment in DOMSVGAnimatedLengthList.h. + * + * This class is strongly intertwined with DOMSVGAnimatedLengthList and + * DOMSVGLength. We are a friend of DOMSVGAnimatedLengthList, and are + * responsible for nulling out our DOMSVGAnimatedLengthList's pointer to us + * when we die, essentially making its pointer to us a weak pointer. Similarly, + * our DOMSVGLength items are friends of us and responsible for nulling out our + * pointers to them. + * + * Our DOM items are created lazily on demand as and when script requests them. + */ +class DOMSVGLengthList final : public nsISupports, public nsWrapperCache { + template <class T> + friend class AutoChangeLengthListNotifier; + friend class DOMSVGLength; + + ~DOMSVGLengthList() { + // Our mAList's weak ref to us must be nulled out when we die. If GC has + // unlinked us using the cycle collector code, then that has already + // happened, and mAList is null. + if (mAList) { + (IsAnimValList() ? mAList->mAnimVal : mAList->mBaseVal) = nullptr; + } + } + + public: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOMSVGLENGTHLIST_IID) + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGLengthList) + + DOMSVGLengthList(DOMSVGAnimatedLengthList* aAList, + const SVGLengthList& aInternalList) + : mAList(aAList) { + // aInternalList must be passed in explicitly because we can't use + // InternalList() here. (Because it depends on IsAnimValList, which depends + // on this object having been assigned to aAList's mBaseVal or mAnimVal, + // which hasn't happened yet.) + + InternalListLengthWillChange(aInternalList.Length()); // Sync mItems + } + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + nsISupports* GetParentObject() { return static_cast<nsIContent*>(Element()); } + + /** + * This will normally be the same as InternalList().Length(), except if we've + * hit OOM in which case our length will be zero. + */ + uint32_t LengthNoFlush() const { + MOZ_ASSERT( + mItems.Length() == 0 || mItems.Length() == InternalList().Length(), + "DOM wrapper's list length is out of sync"); + return mItems.Length(); + } + + /// Called to notify us to synchronize our length and detach excess items. + void InternalListLengthWillChange(uint32_t aNewLength); + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool IsAnimating() const { return mAList->IsAnimating(); } + /** + * Returns true if there is an animated list mirroring the base list. + */ + bool AnimListMirrorsBaseList() const { + return mAList->mAnimVal && !mAList->IsAnimating(); + } + + uint32_t NumberOfItems() const { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + return LengthNoFlush(); + } + void Clear(ErrorResult& aError); + already_AddRefed<DOMSVGLength> Initialize(DOMSVGLength& newItem, + ErrorResult& error); + already_AddRefed<DOMSVGLength> GetItem(uint32_t index, ErrorResult& error); + already_AddRefed<DOMSVGLength> IndexedGetter(uint32_t index, bool& found, + ErrorResult& error); + already_AddRefed<DOMSVGLength> InsertItemBefore(DOMSVGLength& newItem, + uint32_t index, + ErrorResult& error); + already_AddRefed<DOMSVGLength> ReplaceItem(DOMSVGLength& newItem, + uint32_t index, + ErrorResult& error); + already_AddRefed<DOMSVGLength> RemoveItem(uint32_t index, ErrorResult& error); + already_AddRefed<DOMSVGLength> AppendItem(DOMSVGLength& newItem, + ErrorResult& error) { + return InsertItemBefore(newItem, LengthNoFlush(), error); + } + void IndexedSetter(uint32_t index, DOMSVGLength& newValue, + ErrorResult& error); + uint32_t Length() const { return NumberOfItems(); } + + private: + dom::SVGElement* Element() const { return mAList->mElement; } + + uint8_t AttrEnum() const { return mAList->mAttrEnum; } + + uint8_t Axis() const { return mAList->mAxis; } + + /// Used to determine if this list is the baseVal or animVal list. + bool IsAnimValList() const { + MOZ_ASSERT(this == mAList->mBaseVal || this == mAList->mAnimVal, + "Calling IsAnimValList() too early?!"); + return this == mAList->mAnimVal; + } + + /** + * Get a reference to this object's corresponding internal SVGLengthList. + * + * To simplify the code we just have this one method for obtaining both + * baseVal and animVal internal lists. This means that animVal lists don't + * get const protection, but our setter methods guard against changing + * animVal lists. + */ + SVGLengthList& InternalList() const; + + /// Returns the DOMSVGLength at aIndex, creating it if necessary. + already_AddRefed<DOMSVGLength> GetItemAt(uint32_t aIndex); + + void MaybeInsertNullInAnimValListAt(uint32_t aIndex); + void MaybeRemoveItemFromAnimValListAt(uint32_t aIndex); + + // Weak refs to our DOMSVGLength items. The items are friends and take care + // of clearing our pointer to them when they die. + FallibleTArray<DOMSVGLength*> mItems; + + RefPtr<DOMSVGAnimatedLengthList> mAList; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DOMSVGLengthList, MOZILLA_DOMSVGLENGTHLIST_IID) + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGLENGTHLIST_H_ diff --git a/dom/svg/DOMSVGNumber.cpp b/dom/svg/DOMSVGNumber.cpp new file mode 100644 index 0000000000..d24332aca4 --- /dev/null +++ b/dom/svg/DOMSVGNumber.cpp @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGNumber.h" +#include "DOMSVGNumberList.h" +#include "DOMSVGAnimatedNumberList.h" +#include "SVGAnimatedNumberList.h" +#include "SVGElement.h" +#include "nsError.h" +#include "nsContentUtils.h" // for NS_ENSURE_FINITE +#include "mozilla/dom/SVGNumberBinding.h" +#include "mozilla/dom/SVGSVGElement.h" + +// See the architecture comment in DOMSVGAnimatedNumberList.h. + +namespace mozilla::dom { + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our list's weak ref to us to be safe. (The other option would be to +// not unlink and rely on the breaking of the other edges in the cycle, as +// NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGNumber) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGNumber) + // We may not belong to a list, so we must null check tmp->mList. + if (tmp->mList) { + tmp->mList->mItems[tmp->mListIndex] = nullptr; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_UNLINK(mList) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGNumber) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGNumber) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +DOMSVGNumber::DOMSVGNumber(DOMSVGNumberList* aList, uint8_t aAttrEnum, + uint32_t aListIndex, bool aIsAnimValItem) + : mList(aList), + mParent(aList), + mListIndex(aListIndex), + mAttrEnum(aAttrEnum), + mIsAnimValItem(aIsAnimValItem), + mValue(0.0f) { + // These shifts are in sync with the members in the header. + MOZ_ASSERT(aList && aAttrEnum < (1 << 4) && aListIndex <= MaxListIndex(), + "bad arg"); + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!"); +} + +DOMSVGNumber::DOMSVGNumber(nsISupports* aParent) + : mList(nullptr), + mParent(aParent), + mListIndex(0), + mAttrEnum(0), + mIsAnimValItem(false), + mValue(0.0f) {} + +DOMSVGNumber::DOMSVGNumber(SVGSVGElement* aParent) + : mList(nullptr), + mParent(ToSupports(aParent)), + mListIndex(0), + mAttrEnum(0), + mIsAnimValItem(false), + mValue(0.0f) {} + +float DOMSVGNumber::Value() { + if (mIsAnimValItem && HasOwner()) { + Element()->FlushAnimations(); // May make HasOwner() == false + } + return HasOwner() ? InternalItem() : mValue; +} + +void DOMSVGNumber::SetValue(float aValue, ErrorResult& aRv) { + if (mIsAnimValItem) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (HasOwner()) { + if (InternalItem() == aValue) { + return; + } + AutoChangeNumberListNotifier notifier(this); + InternalItem() = aValue; + return; + } + + mValue = aValue; +} + +void DOMSVGNumber::InsertingIntoList(DOMSVGNumberList* aList, uint8_t aAttrEnum, + uint32_t aListIndex, bool aIsAnimValItem) { + NS_ASSERTION(!HasOwner(), "Inserting item that is already in a list"); + + mList = aList; + mAttrEnum = aAttrEnum; + mListIndex = aListIndex; + mIsAnimValItem = aIsAnimValItem; + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!"); +} + +void DOMSVGNumber::RemovingFromList() { + mValue = InternalItem(); + mList = nullptr; + mIsAnimValItem = false; +} + +float DOMSVGNumber::ToSVGNumber() { + return HasOwner() ? InternalItem() : mValue; +} + +float& DOMSVGNumber::InternalItem() { + SVGAnimatedNumberList* alist = Element()->GetAnimatedNumberList(mAttrEnum); + return mIsAnimValItem && alist->mAnimVal ? (*alist->mAnimVal)[mListIndex] + : alist->mBaseVal[mListIndex]; +} + +#ifdef DEBUG +bool DOMSVGNumber::IndexIsValid() { + SVGAnimatedNumberList* alist = Element()->GetAnimatedNumberList(mAttrEnum); + return (mIsAnimValItem && mListIndex < alist->GetAnimValue().Length()) || + (!mIsAnimValItem && mListIndex < alist->GetBaseValue().Length()); +} +#endif + +JSObject* DOMSVGNumber::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGNumber_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGNumber.h b/dom/svg/DOMSVGNumber.h new file mode 100644 index 0000000000..7023f6bf0f --- /dev/null +++ b/dom/svg/DOMSVGNumber.h @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGNUMBER_H_ +#define DOM_SVG_DOMSVGNUMBER_H_ + +#include "DOMSVGNumberList.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "nsWrapperCache.h" + +#define MOZ_SVG_LIST_INDEX_BIT_COUNT 27 // supports > 134 million list items + +namespace mozilla { +class ErrorResult; + +namespace dom { +class SVGElement; +class SVGSVGElement; + +/** + * Class DOMSVGNumber + * + * This class creates the DOM objects that wrap internal SVGNumber objects that + * are in an SVGNumberList. It is also used to create the objects returned by + * SVGSVGElement.createSVGNumber(). + * + * For the DOM wrapper classes for non-list SVGNumber, see SVGAnimatedNumber.h. + * + * See the architecture comment in DOMSVGAnimatedNumberList.h. + * + * See the comment in DOMSVGLength.h (yes, LENGTH), which applies here too. + */ +class DOMSVGNumber final : public nsWrapperCache { + template <class T> + friend class AutoChangeNumberListNotifier; + + ~DOMSVGNumber() { + // Our mList's weak ref to us must be nulled out when we die. If GC has + // unlinked us using the cycle collector code, then that has already + // happened, and mList is null. + if (mList) { + mList->mItems[mListIndex] = nullptr; + } + } + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGNumber) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGNumber) + + /** + * Generic ctor for DOMSVGNumber objects that are created for an attribute. + */ + DOMSVGNumber(DOMSVGNumberList* aList, uint8_t aAttrEnum, uint32_t aListIndex, + bool aIsAnimValItem); + + /** + * Ctor for creating the objects returned by SVGSVGElement.createSVGNumber(), + * which do not initially belong to an attribute. + */ + explicit DOMSVGNumber(SVGSVGElement* aParent); + + private: + explicit DOMSVGNumber(nsISupports* aParent); + + public: + /** + * Create an unowned copy. The caller is responsible for the first AddRef(). + */ + DOMSVGNumber* Clone() { + DOMSVGNumber* clone = new DOMSVGNumber(mParent); + clone->mValue = ToSVGNumber(); + return clone; + } + + bool IsInList() const { return !!mList; } + + /** + * Returns true if our attribute is animating. + */ + bool IsAnimating() const { return mList && mList->IsAnimating(); } + + /** + * In future, if this class is used for non-list numbers, this will be + * different to IsInList(). + */ + bool HasOwner() const { return !!mList; } + + /** + * This method is called to notify this DOM object that it is being inserted + * into a list, and give it the information it needs as a result. + * + * This object MUST NOT already belong to a list when this method is called. + * That's not to say that script can't move these DOM objects between + * lists - it can - it's just that the logic to handle that (and send out + * the necessary notifications) is located elsewhere (in DOMSVGNumberList).) + */ + void InsertingIntoList(DOMSVGNumberList* aList, uint8_t aAttrEnum, + uint32_t aListIndex, bool aIsAnimValItem); + + static uint32_t MaxListIndex() { + return (1U << MOZ_SVG_LIST_INDEX_BIT_COUNT) - 1; + } + + /// This method is called to notify this object that its list index changed. + void UpdateListIndex(uint32_t aListIndex) { mListIndex = aListIndex; } + + /** + * This method is called to notify this DOM object that it is about to be + * removed from its current DOM list so that it can first make a copy of its + * internal counterpart's value. (If it didn't do this, then it would + * "lose" its value on being removed.) + */ + void RemovingFromList(); + + float ToSVGNumber(); + + nsISupports* GetParentObject() { return mParent; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + float Value(); + + void SetValue(float aValue, ErrorResult& aRv); + + private: + dom::SVGElement* Element() { return mList->Element(); } + + uint8_t AttrEnum() const { return mAttrEnum; } + + /** + * Get a reference to the internal SVGNumber list item that this DOM wrapper + * object currently wraps. + * + * To simplify the code we just have this one method for obtaining both + * baseVal and animVal internal items. This means that animVal items don't + * get const protection, but then our setter methods guard against changing + * animVal items. + */ + float& InternalItem(); + +#ifdef DEBUG + bool IndexIsValid(); +#endif + + RefPtr<DOMSVGNumberList> mList; + nsCOMPtr<nsISupports> mParent; + + // Bounds for the following are checked in the ctor, so be sure to update + // that if you change the capacity of any of the following. + + uint32_t mListIndex : MOZ_SVG_LIST_INDEX_BIT_COUNT; + uint32_t mAttrEnum : 4; // supports up to 16 attributes + uint32_t mIsAnimValItem : 1; + + // The following member is only used when we're not in a list: + float mValue; +}; + +} // namespace dom +} // namespace mozilla + +#undef MOZ_SVG_LIST_INDEX_BIT_COUNT + +#endif // DOM_SVG_DOMSVGNUMBER_H_ diff --git a/dom/svg/DOMSVGNumberList.cpp b/dom/svg/DOMSVGNumberList.cpp new file mode 100644 index 0000000000..c75140faa0 --- /dev/null +++ b/dom/svg/DOMSVGNumberList.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGNumberList.h" + +#include "SVGElement.h" +#include "DOMSVGNumber.h" +#include "nsError.h" +#include "SVGAnimatedNumberList.h" +#include "mozilla/dom/SVGNumberListBinding.h" +#include "mozilla/RefPtr.h" +#include <algorithm> + +// See the comment in this file's header. + +// local helper functions +namespace { + +using mozilla::dom::DOMSVGNumber; + +void UpdateListIndicesFromIndex(FallibleTArray<DOMSVGNumber*>& aItemsArray, + uint32_t aStartingIndex) { + uint32_t length = aItemsArray.Length(); + + for (uint32_t i = aStartingIndex; i < length; ++i) { + if (aItemsArray[i]) { + aItemsArray[i]->UpdateListIndex(i); + } + } +} + +} // namespace + +namespace mozilla::dom { + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our DOMSVGAnimatedNumberList's weak ref to us to be safe. (The other +// option would be to not unlink and rely on the breaking of the other edges in +// the cycle, as NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGNumberList) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGNumberList) + if (tmp->mAList) { + if (tmp->IsAnimValList()) { + tmp->mAList->mAnimVal = nullptr; + } else { + tmp->mAList->mBaseVal = nullptr; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAList) + } + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGNumberList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGNumberList) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGNumberList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGNumberList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGNumberList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* DOMSVGNumberList::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return mozilla::dom::SVGNumberList_Binding::Wrap(cx, this, aGivenProto); +} + +void DOMSVGNumberList::InternalListLengthWillChange(uint32_t aNewLength) { + uint32_t oldLength = mItems.Length(); + + if (aNewLength > DOMSVGNumber::MaxListIndex()) { + // It's safe to get out of sync with our internal list as long as we have + // FEWER items than it does. + aNewLength = DOMSVGNumber::MaxListIndex(); + } + + RefPtr<DOMSVGNumberList> kungFuDeathGrip; + if (aNewLength < oldLength) { + // RemovingFromList() might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + + // If our length will decrease, notify the items that will be removed: + for (uint32_t i = aNewLength; i < oldLength; ++i) { + if (mItems[i]) { + mItems[i]->RemovingFromList(); + } + } + + if (!mItems.SetLength(aNewLength, fallible)) { + // We silently ignore SetLength OOM failure since being out of sync is safe + // so long as we have *fewer* items than our internal list. + mItems.Clear(); + return; + } + + // If our length has increased, null out the new pointers: + for (uint32_t i = oldLength; i < aNewLength; ++i) { + mItems[i] = nullptr; + } +} + +SVGNumberList& DOMSVGNumberList::InternalList() const { + SVGAnimatedNumberList* alist = Element()->GetAnimatedNumberList(AttrEnum()); + return IsAnimValList() && alist->mAnimVal ? *alist->mAnimVal + : alist->mBaseVal; +} + +void DOMSVGNumberList::Clear(ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (LengthNoFlush() > 0) { + AutoChangeNumberListNotifier notifier(this); + // Notify any existing DOM items of removal *before* truncating the lists + // so that they can find their SVGNumber internal counterparts and copy + // their values. This also notifies the animVal list: + mAList->InternalBaseValListWillChangeTo(SVGNumberList()); + + mItems.Clear(); + InternalList().Clear(); + } +} + +already_AddRefed<DOMSVGNumber> DOMSVGNumberList::Initialize( + DOMSVGNumber& aItem, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + // If newItem is already in a list we should insert a clone of newItem, and + // for consistency, this should happen even if *this* is the list that + // newItem is currently in. Note that in the case of newItem being in this + // list, the Clear() call before the InsertItemBefore() call would remove it + // from this list, and so the InsertItemBefore() call would not insert a + // clone of newItem, it would actually insert newItem. To prevent that from + // happening we have to do the clone here, if necessary. + RefPtr<DOMSVGNumber> domItem = aItem.HasOwner() ? aItem.Clone() : &aItem; + + Clear(error); + MOZ_ASSERT(!error.Failed()); + return InsertItemBefore(*domItem, 0, error); +} + +already_AddRefed<DOMSVGNumber> DOMSVGNumberList::GetItem(uint32_t index, + ErrorResult& error) { + bool found; + RefPtr<DOMSVGNumber> item = IndexedGetter(index, found, error); + if (!found) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + } + return item.forget(); +} + +already_AddRefed<DOMSVGNumber> DOMSVGNumberList::IndexedGetter( + uint32_t index, bool& found, ErrorResult& error) { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + found = index < LengthNoFlush(); + if (found) { + return GetItemAt(index); + } + return nullptr; +} + +already_AddRefed<DOMSVGNumber> DOMSVGNumberList::InsertItemBefore( + DOMSVGNumber& aItem, uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + index = std::min(index, LengthNoFlush()); + if (index >= DOMSVGNumber::MaxListIndex()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + // must do this before changing anything! + RefPtr<DOMSVGNumber> domItem = aItem.HasOwner() ? aItem.Clone() : &aItem; + + // Ensure we have enough memory so we can avoid complex error handling below: + if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || + !InternalList().SetCapacity(InternalList().Length() + 1)) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + if (AnimListMirrorsBaseList()) { + if (!mAList->mAnimVal->mItems.SetCapacity( + mAList->mAnimVal->mItems.Length() + 1, fallible)) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + } + + AutoChangeNumberListNotifier notifier(this); + // Now that we know we're inserting, keep animVal list in sync as necessary. + MaybeInsertNullInAnimValListAt(index); + + InternalList().InsertItem(index, domItem->ToSVGNumber()); + MOZ_ALWAYS_TRUE(mItems.InsertElementAt(index, domItem, fallible)); + + // This MUST come after the insertion into InternalList(), or else under the + // insertion into InternalList() the values read from domItem would be bad + // data from InternalList() itself!: + domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList()); + + UpdateListIndicesFromIndex(mItems, index + 1); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGNumber> DOMSVGNumberList::ReplaceItem( + DOMSVGNumber& aItem, uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (index >= LengthNoFlush()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + // must do this before changing anything! + RefPtr<DOMSVGNumber> domItem = aItem.HasOwner() ? aItem.Clone() : &aItem; + + AutoChangeNumberListNotifier notifier(this); + if (mItems[index]) { + // Notify any existing DOM item of removal *before* modifying the lists so + // that the DOM item can copy the *old* value at its index: + mItems[index]->RemovingFromList(); + } + + InternalList()[index] = domItem->ToSVGNumber(); + mItems[index] = domItem; + + // This MUST come after the ToSVGPoint() call, otherwise that call + // would end up reading bad data from InternalList()! + domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList()); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGNumber> DOMSVGNumberList::RemoveItem( + uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (index >= LengthNoFlush()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + // Now that we know we're removing, keep animVal list in sync as necessary. + // Do this *before* touching InternalList() so the removed item can get its + // internal value. + MaybeRemoveItemFromAnimValListAt(index); + + // We have to return the removed item, so get it, creating it if necessary: + RefPtr<DOMSVGNumber> result = GetItemAt(index); + + AutoChangeNumberListNotifier notifier(this); + // Notify the DOM item of removal *before* modifying the lists so that the + // DOM item can copy its *old* value: + mItems[index]->RemovingFromList(); + + InternalList().RemoveItem(index); + mItems.RemoveElementAt(index); + + UpdateListIndicesFromIndex(mItems, index); + + return result.forget(); +} + +already_AddRefed<DOMSVGNumber> DOMSVGNumberList::GetItemAt(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length()); + + if (!mItems[aIndex]) { + mItems[aIndex] = + new DOMSVGNumber(this, AttrEnum(), aIndex, IsAnimValList()); + } + RefPtr<DOMSVGNumber> result = mItems[aIndex]; + return result.forget(); +} + +void DOMSVGNumberList::MaybeInsertNullInAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + DOMSVGNumberList* animVal = mAList->mAnimVal; + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible)); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1); +} + +void DOMSVGNumberList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + // This needs to be a strong reference; otherwise, the RemovingFromList call + // below might drop the last reference to animVal before we're done with it. + RefPtr<DOMSVGNumberList> animVal = mAList->mAnimVal; + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + + if (animVal->mItems[aIndex]) { + animVal->mItems[aIndex]->RemovingFromList(); + } + animVal->mItems.RemoveElementAt(aIndex); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGNumberList.h b/dom/svg/DOMSVGNumberList.h new file mode 100644 index 0000000000..0e36d30ba3 --- /dev/null +++ b/dom/svg/DOMSVGNumberList.h @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGNUMBERLIST_H_ +#define DOM_SVG_DOMSVGNUMBERLIST_H_ + +#include "DOMSVGAnimatedNumberList.h" +#include "mozAutoDocUpdate.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsTArray.h" +#include "SVGNumberList.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { +class DOMSVGNumber; +class SVGElement; + +//---------------------------------------------------------------------- +// Helper class: AutoChangeNumberListNotifier +// Stack-based helper class to pair calls to WillChangeNumberList and +// DidChangeNumberList. Used by DOMSVGNumber and DOMSVGNumberList. +template <class T> +class MOZ_RAII AutoChangeNumberListNotifier : public mozAutoDocUpdate { + public: + explicit AutoChangeNumberListNotifier(T* aValue) + : mozAutoDocUpdate(aValue->Element()->GetComposedDoc(), true), + mValue(aValue) { + MOZ_ASSERT(mValue, "Expecting non-null value"); + mEmptyOrOldValue = + mValue->Element()->WillChangeNumberList(mValue->AttrEnum(), *this); + } + + ~AutoChangeNumberListNotifier() { + mValue->Element()->DidChangeNumberList(mValue->AttrEnum(), mEmptyOrOldValue, + *this); + if (mValue->IsAnimating()) { + mValue->Element()->AnimationNeedsResample(); + } + } + + private: + T* const mValue; + nsAttrValue mEmptyOrOldValue; +}; + +/** + * Class DOMSVGNumberList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGNumberList objects. + * + * See the architecture comment in DOMSVGAnimatedNumberList.h. + * + * This class is strongly intertwined with DOMSVGAnimatedNumberList and + * DOMSVGNumber. We are a friend of DOMSVGAnimatedNumberList, and are + * responsible for nulling out our DOMSVGAnimatedNumberList's pointer to us + * when we die, essentially making its pointer to us a weak pointer. Similarly, + * our DOMSVGNumber items are friends of us and responsible for nulling out our + * pointers to them. + * + * Our DOM items are created lazily on demand as and when script requests them. + */ +class DOMSVGNumberList final : public nsISupports, public nsWrapperCache { + template <class T> + friend class AutoChangeNumberListNotifier; + friend class DOMSVGNumber; + + ~DOMSVGNumberList() { + // Our mAList's weak ref to us must be nulled out when we die. If GC has + // unlinked us using the cycle collector code, then that has already + // happened, and mAList is null. + if (mAList) { + (IsAnimValList() ? mAList->mAnimVal : mAList->mBaseVal) = nullptr; + } + } + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGNumberList) + + DOMSVGNumberList(DOMSVGAnimatedNumberList* aAList, + const SVGNumberList& aInternalList) + : mAList(aAList) { + // aInternalList must be passed in explicitly because we can't use + // InternalList() here. (Because it depends on IsAnimValList, which depends + // on this object having been assigned to aAList's mBaseVal or mAnimVal, + // which hasn't happened yet.) + + InternalListLengthWillChange(aInternalList.Length()); // Sync mItems + } + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + nsISupports* GetParentObject() { return static_cast<nsIContent*>(Element()); } + + /** + * This will normally be the same as InternalList().Length(), except if we've + * hit OOM in which case our length will be zero. + */ + uint32_t LengthNoFlush() const { + MOZ_ASSERT( + mItems.Length() == 0 || mItems.Length() == InternalList().Length(), + "DOM wrapper's list length is out of sync"); + return mItems.Length(); + } + + /// Called to notify us to synchronize our length and detach excess items. + void InternalListLengthWillChange(uint32_t aNewLength); + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool IsAnimating() const { return mAList->IsAnimating(); } + /** + * Returns true if there is an animated list mirroring the base list. + */ + bool AnimListMirrorsBaseList() const { + return mAList->mAnimVal && !mAList->IsAnimating(); + } + + uint32_t NumberOfItems() const { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + return LengthNoFlush(); + } + void Clear(ErrorResult& error); + already_AddRefed<DOMSVGNumber> Initialize(DOMSVGNumber& aItem, + ErrorResult& error); + already_AddRefed<DOMSVGNumber> GetItem(uint32_t index, ErrorResult& error); + already_AddRefed<DOMSVGNumber> IndexedGetter(uint32_t index, bool& found, + ErrorResult& error); + already_AddRefed<DOMSVGNumber> InsertItemBefore(DOMSVGNumber& aItem, + uint32_t index, + ErrorResult& error); + already_AddRefed<DOMSVGNumber> ReplaceItem(DOMSVGNumber& aItem, + uint32_t index, + ErrorResult& error); + already_AddRefed<DOMSVGNumber> RemoveItem(uint32_t index, ErrorResult& error); + already_AddRefed<DOMSVGNumber> AppendItem(DOMSVGNumber& newItem, + ErrorResult& error) { + return InsertItemBefore(newItem, LengthNoFlush(), error); + } + uint32_t Length() const { return NumberOfItems(); } + + private: + dom::SVGElement* Element() const { return mAList->mElement; } + + uint8_t AttrEnum() const { return mAList->mAttrEnum; } + + /// Used to determine if this list is the baseVal or animVal list. + bool IsAnimValList() const { + MOZ_ASSERT(this == mAList->mBaseVal || this == mAList->mAnimVal, + "Calling IsAnimValList() too early?!"); + return this == mAList->mAnimVal; + } + + /** + * Get a reference to this object's corresponding internal SVGNumberList. + * + * To simplify the code we just have this one method for obtaining both + * baseVal and animVal internal lists. This means that animVal lists don't + * get const protection, but our setter methods guard against changing + * animVal lists. + */ + SVGNumberList& InternalList() const; + + /// Returns the DOMSVGNumber at aIndex, creating it if necessary. + already_AddRefed<DOMSVGNumber> GetItemAt(uint32_t aIndex); + + void MaybeInsertNullInAnimValListAt(uint32_t aIndex); + void MaybeRemoveItemFromAnimValListAt(uint32_t aIndex); + + // Weak refs to our DOMSVGNumber items. The items are friends and take care + // of clearing our pointer to them when they die. + FallibleTArray<DOMSVGNumber*> mItems; + + RefPtr<DOMSVGAnimatedNumberList> mAList; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGNUMBERLIST_H_ diff --git a/dom/svg/DOMSVGPathSeg.cpp b/dom/svg/DOMSVGPathSeg.cpp new file mode 100644 index 0000000000..c39f624c93 --- /dev/null +++ b/dom/svg/DOMSVGPathSeg.cpp @@ -0,0 +1,314 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGPathSeg.h" +#include "DOMSVGPathSegList.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGElement.h" +#include "mozAutoDocUpdate.h" +#include "nsError.h" + +// See the architecture comment in DOMSVGPathSegList.h. + +namespace mozilla::dom { + +using namespace dom::SVGPathSeg_Binding; + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our list's weak ref to us to be safe. (The other option would be to +// not unlink and rely on the breaking of the other edges in the cycle, as +// NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPathSeg) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPathSeg) + // We may not belong to a list, so we must null check tmp->mList. + if (tmp->mList) { + tmp->mList->ItemAt(tmp->mListIndex) = nullptr; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mList) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPathSeg) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPathSeg) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +DOMSVGPathSeg::DOMSVGPathSeg(DOMSVGPathSegList* aList, uint32_t aListIndex, + bool aIsAnimValItem) + : mList(aList), mListIndex(aListIndex), mIsAnimValItem(aIsAnimValItem) { + // These shifts are in sync with the members in the header. + MOZ_ASSERT(aList && aListIndex <= MaxListIndex(), "bad arg"); + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGPathSeg!"); +} + +DOMSVGPathSeg::DOMSVGPathSeg() + : mList(nullptr), mListIndex(0), mIsAnimValItem(false) {} + +void DOMSVGPathSeg::InsertingIntoList(DOMSVGPathSegList* aList, + uint32_t aListIndex, + bool aIsAnimValItem) { + MOZ_ASSERT(!HasOwner(), "Inserting item that is already in a list"); + + mList = aList; + mListIndex = aListIndex; + mIsAnimValItem = aIsAnimValItem; + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGPathSeg!"); +} + +void DOMSVGPathSeg::RemovingFromList() { + uint32_t argCount = SVGPathSegUtils::ArgCountForType(Type()); + // InternalItem() + 1, because the args come after the encoded seg type + memcpy(PtrToMemberArgs(), InternalItem() + 1, argCount * sizeof(float)); + mList = nullptr; + mIsAnimValItem = false; +} + +void DOMSVGPathSeg::ToSVGPathSegEncodedData(float* aRaw) { + MOZ_ASSERT(aRaw, "null pointer"); + uint32_t argCount = SVGPathSegUtils::ArgCountForType(Type()); + if (IsInList()) { + // 1 + argCount, because we're copying the encoded seg type and args + memcpy(aRaw, InternalItem(), (1 + argCount) * sizeof(float)); + } else { + aRaw[0] = SVGPathSegUtils::EncodeType(Type()); + // aRaw + 1, because the args go after the encoded seg type + memcpy(aRaw + 1, PtrToMemberArgs(), argCount * sizeof(float)); + } +} + +float* DOMSVGPathSeg::InternalItem() { + uint32_t dataIndex = mList->mItems[mListIndex].mInternalDataIndex; + return &(mList->InternalList().mData[dataIndex]); +} + +#ifdef DEBUG +bool DOMSVGPathSeg::IndexIsValid() { + SVGAnimatedPathSegList* alist = Element()->GetAnimPathSegList(); + return (mIsAnimValItem && mListIndex < alist->GetAnimValue().CountItems()) || + (!mIsAnimValItem && mListIndex < alist->GetBaseValue().CountItems()); +} +#endif + +//////////////////////////////////////////////////////////////////////// +// Implementation of DOMSVGPathSeg sub-classes below this point + +#define IMPL_PROP_WITH_TYPE(segName, propName, index, type) \ + type DOMSVGPathSeg##segName::propName() { \ + if (mIsAnimValItem && HasOwner()) { \ + Element()->FlushAnimations(); /* May make HasOwner() == false */ \ + } \ + return type(HasOwner() ? InternalItem()[1 + index] : mArgs[index]); \ + } \ + void DOMSVGPathSeg##segName::Set##propName(type a##propName, \ + ErrorResult& rv) { \ + if (mIsAnimValItem) { \ + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); \ + return; \ + } \ + if (HasOwner()) { \ + if (InternalItem()[1 + index] == float(a##propName)) { \ + return; \ + } \ + AutoChangePathSegListNotifier notifier(this); \ + InternalItem()[1 + index] = float(a##propName); \ + } else { \ + mArgs[index] = float(a##propName); \ + } \ + } + +// For float, the normal type of arguments +#define IMPL_FLOAT_PROP(segName, propName, index) \ + IMPL_PROP_WITH_TYPE(segName, propName, index, float) + +// For the boolean flags in arc commands +#define IMPL_BOOL_PROP(segName, propName, index) \ + IMPL_PROP_WITH_TYPE(segName, propName, index, bool) + +/////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(MovetoAbs, X, 0) +IMPL_FLOAT_PROP(MovetoAbs, Y, 1) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(MovetoRel, X, 0) +IMPL_FLOAT_PROP(MovetoRel, Y, 1) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(LinetoAbs, X, 0) +IMPL_FLOAT_PROP(LinetoAbs, Y, 1) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(LinetoRel, X, 0) +IMPL_FLOAT_PROP(LinetoRel, Y, 1) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoCubicAbs, X1, 0) +IMPL_FLOAT_PROP(CurvetoCubicAbs, Y1, 1) +IMPL_FLOAT_PROP(CurvetoCubicAbs, X2, 2) +IMPL_FLOAT_PROP(CurvetoCubicAbs, Y2, 3) +IMPL_FLOAT_PROP(CurvetoCubicAbs, X, 4) +IMPL_FLOAT_PROP(CurvetoCubicAbs, Y, 5) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoCubicRel, X1, 0) +IMPL_FLOAT_PROP(CurvetoCubicRel, Y1, 1) +IMPL_FLOAT_PROP(CurvetoCubicRel, X2, 2) +IMPL_FLOAT_PROP(CurvetoCubicRel, Y2, 3) +IMPL_FLOAT_PROP(CurvetoCubicRel, X, 4) +IMPL_FLOAT_PROP(CurvetoCubicRel, Y, 5) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, X1, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, Y1, 1) +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, X, 2) +IMPL_FLOAT_PROP(CurvetoQuadraticAbs, Y, 3) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoQuadraticRel, X1, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticRel, Y1, 1) +IMPL_FLOAT_PROP(CurvetoQuadraticRel, X, 2) +IMPL_FLOAT_PROP(CurvetoQuadraticRel, Y, 3) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(ArcAbs, R1, 0) +IMPL_FLOAT_PROP(ArcAbs, R2, 1) +IMPL_FLOAT_PROP(ArcAbs, Angle, 2) +IMPL_BOOL_PROP(ArcAbs, LargeArcFlag, 3) +IMPL_BOOL_PROP(ArcAbs, SweepFlag, 4) +IMPL_FLOAT_PROP(ArcAbs, X, 5) +IMPL_FLOAT_PROP(ArcAbs, Y, 6) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(ArcRel, R1, 0) +IMPL_FLOAT_PROP(ArcRel, R2, 1) +IMPL_FLOAT_PROP(ArcRel, Angle, 2) +IMPL_BOOL_PROP(ArcRel, LargeArcFlag, 3) +IMPL_BOOL_PROP(ArcRel, SweepFlag, 4) +IMPL_FLOAT_PROP(ArcRel, X, 5) +IMPL_FLOAT_PROP(ArcRel, Y, 6) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(LinetoHorizontalAbs, X, 0) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(LinetoHorizontalRel, X, 0) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(LinetoVerticalAbs, Y, 0) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(LinetoVerticalRel, Y, 0) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, X2, 0) +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, Y2, 1) +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, X, 2) +IMPL_FLOAT_PROP(CurvetoCubicSmoothAbs, Y, 3) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, X2, 0) +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, Y2, 1) +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, X, 2) +IMPL_FLOAT_PROP(CurvetoCubicSmoothRel, Y, 3) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothAbs, X, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothAbs, Y, 1) + +//////////////////////////////////////////////////////////////////////// + +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothRel, X, 0) +IMPL_FLOAT_PROP(CurvetoQuadraticSmoothRel, Y, 1) + +// This must come after DOMSVGPathSegClosePath et. al. have been declared. +/* static */ +DOMSVGPathSeg* DOMSVGPathSeg::CreateFor(DOMSVGPathSegList* aList, + uint32_t aListIndex, + bool aIsAnimValItem) { + uint32_t dataIndex = aList->mItems[aListIndex].mInternalDataIndex; + float* data = &aList->InternalList().mData[dataIndex]; + uint32_t type = SVGPathSegUtils::DecodeType(data[0]); + + switch (type) { + case PATHSEG_CLOSEPATH: + return new DOMSVGPathSegClosePath(aList, aListIndex, aIsAnimValItem); + case PATHSEG_MOVETO_ABS: + return new DOMSVGPathSegMovetoAbs(aList, aListIndex, aIsAnimValItem); + case PATHSEG_MOVETO_REL: + return new DOMSVGPathSegMovetoRel(aList, aListIndex, aIsAnimValItem); + case PATHSEG_LINETO_ABS: + return new DOMSVGPathSegLinetoAbs(aList, aListIndex, aIsAnimValItem); + case PATHSEG_LINETO_REL: + return new DOMSVGPathSegLinetoRel(aList, aListIndex, aIsAnimValItem); + case PATHSEG_CURVETO_CUBIC_ABS: + return new DOMSVGPathSegCurvetoCubicAbs(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_CURVETO_CUBIC_REL: + return new DOMSVGPathSegCurvetoCubicRel(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_CURVETO_QUADRATIC_ABS: + return new DOMSVGPathSegCurvetoQuadraticAbs(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_CURVETO_QUADRATIC_REL: + return new DOMSVGPathSegCurvetoQuadraticRel(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_ARC_ABS: + return new DOMSVGPathSegArcAbs(aList, aListIndex, aIsAnimValItem); + case PATHSEG_ARC_REL: + return new DOMSVGPathSegArcRel(aList, aListIndex, aIsAnimValItem); + case PATHSEG_LINETO_HORIZONTAL_ABS: + return new DOMSVGPathSegLinetoHorizontalAbs(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_LINETO_HORIZONTAL_REL: + return new DOMSVGPathSegLinetoHorizontalRel(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_LINETO_VERTICAL_ABS: + return new DOMSVGPathSegLinetoVerticalAbs(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_LINETO_VERTICAL_REL: + return new DOMSVGPathSegLinetoVerticalRel(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + return new DOMSVGPathSegCurvetoCubicSmoothAbs(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: + return new DOMSVGPathSegCurvetoCubicSmoothRel(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + return new DOMSVGPathSegCurvetoQuadraticSmoothAbs(aList, aListIndex, + aIsAnimValItem); + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: + return new DOMSVGPathSegCurvetoQuadraticSmoothRel(aList, aListIndex, + aIsAnimValItem); + default: + MOZ_ASSERT_UNREACHABLE("Invalid path segment type"); + return nullptr; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGPathSeg.h b/dom/svg/DOMSVGPathSeg.h new file mode 100644 index 0000000000..706a204064 --- /dev/null +++ b/dom/svg/DOMSVGPathSeg.h @@ -0,0 +1,655 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGPATHSEG_H_ +#define DOM_SVG_DOMSVGPATHSEG_H_ + +#include "DOMSVGPathSegList.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "SVGPathSegUtils.h" +#include "mozilla/dom/SVGPathSegBinding.h" + +#define MOZ_SVG_LIST_INDEX_BIT_COUNT 31 + +namespace mozilla::dom { + +class SVGElement; + +#define CHECK_ARG_COUNT_IN_SYNC(segType) \ + MOZ_ASSERT( \ + ArrayLength(mArgs) == \ + SVGPathSegUtils::ArgCountForType(uint32_t(segType)) || \ + uint32_t(segType) == dom::SVGPathSeg_Binding::PATHSEG_CLOSEPATH, \ + "Arg count/array size out of sync") + +#define IMPL_SVGPATHSEG_SUBCLASS_COMMON(segName, segType) \ + explicit DOMSVGPathSeg##segName(const float* aArgs) : DOMSVGPathSeg() { \ + CHECK_ARG_COUNT_IN_SYNC(segType); \ + memcpy( \ + mArgs, aArgs, \ + SVGPathSegUtils::ArgCountForType(uint32_t(segType)) * sizeof(float)); \ + } \ + DOMSVGPathSeg##segName(DOMSVGPathSegList* aList, uint32_t aListIndex, \ + bool aIsAnimValItem) \ + : DOMSVGPathSeg(aList, aListIndex, aIsAnimValItem) { \ + CHECK_ARG_COUNT_IN_SYNC(segType); \ + } \ + /* From DOMSVGPathSeg: */ \ + uint32_t Type() const override { return segType; } \ + DOMSVGPathSeg* Clone() override { \ + /* InternalItem() + 1, because we're skipping the encoded seg type */ \ + float* args = IsInList() ? InternalItem() + 1 : mArgs; \ + return new DOMSVGPathSeg##segName(args); \ + } \ + float* PtrToMemberArgs() override { return mArgs; } \ + \ + JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) \ + override { \ + return dom::SVGPathSeg##segName##_Binding::Wrap(aCx, this, aGivenProto); \ + } + +/** + * Class DOMSVGPathSeg + * + * This class is the base class of the classes that create the DOM objects that + * wrap the internal path segments that are encoded in an SVGPathData. Its + * sub-classes are also used to create the objects returned by + * SVGPathElement.createSVGPathSegXxx(). + * + * See the architecture comment in DOMSVGPathSegList.h for an overview of the + * important points regarding these DOM wrapper structures. + * + * See the architecture comment in DOMSVGLength.h (yes, LENGTH) for an overview + * of the important points regarding how this specific class works. + * + * The main differences between this class and DOMSVGLength is that we have + * sub-classes (it does not), and the "internal counterpart" that we provide a + * DOM wrapper for is a list of floats, not an instance of an internal class. + */ +class DOMSVGPathSeg : public nsWrapperCache { + template <class T> + friend class AutoChangePathSegListNotifier; + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGPathSeg) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGPathSeg) + + /** + * Unlike the other list classes, we hide our ctor (because no one should be + * creating instances of this class directly). This factory method in exposed + * instead to take care of creating instances of the correct sub-class. + */ + static DOMSVGPathSeg* CreateFor(DOMSVGPathSegList* aList, uint32_t aListIndex, + bool aIsAnimValItem); + + /** + * Create an unowned copy of this object. The caller is responsible for the + * first AddRef()! + */ + virtual DOMSVGPathSeg* Clone() = 0; + + bool IsInList() const { return !!mList; } + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool AttrIsAnimating() const { return mList && mList->AttrIsAnimating(); } + + /** + * In future, if this class is used for non-list segments, this will be + * different to IsInList(). + */ + bool HasOwner() const { return !!mList; } + + /** + * This method is called to notify this DOM object that it is being inserted + * into a list, and give it the information it needs as a result. + * + * This object MUST NOT already belong to a list when this method is called. + * That's not to say that script can't move these DOM objects between + * lists - it can - it's just that the logic to handle that (and send out + * the necessary notifications) is located elsewhere (in DOMSVGPathSegList).) + */ + void InsertingIntoList(DOMSVGPathSegList* aList, uint32_t aListIndex, + bool aIsAnimValItem); + + static uint32_t MaxListIndex() { + return (1U << MOZ_SVG_LIST_INDEX_BIT_COUNT) - 1; + } + + /// This method is called to notify this object that its list index changed. + void UpdateListIndex(uint32_t aListIndex) { mListIndex = aListIndex; } + + /** + * This method is called to notify this DOM object that it is about to be + * removed from its current DOM list so that it can first make a copy of its + * internal counterpart's values. (If it didn't do this, then it would + * "lose" its value on being removed.) + */ + void RemovingFromList(); + + /** + * This method converts the segment to a string of floats as found in + * SVGPathData (i.e. the first float contains the type of the segment, + * encoded into a float, followed by its arguments in the same order as they + * are given in the <path> element's 'd' attribute). + */ + void ToSVGPathSegEncodedData(float* aRaw); + + /** + * The type of this path segment. + */ + virtual uint32_t Type() const = 0; + + // WebIDL + DOMSVGPathSegList* GetParentObject() { return mList; } + uint16_t PathSegType() const { return Type(); } + void GetPathSegTypeAsLetter(nsAString& aPathSegTypeAsLetter) { + aPathSegTypeAsLetter = SVGPathSegUtils::GetPathSegTypeAsLetter(Type()); + } + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override = 0; + + protected: + /** + * Generic ctor for DOMSVGPathSeg objects that are created for an attribute. + */ + DOMSVGPathSeg(DOMSVGPathSegList* aList, uint32_t aListIndex, + bool aIsAnimValItem); + + /** + * Ctor for creating the objects returned by + * SVGPathElement.createSVGPathSegXxx(), which do not initially belong to an + * attribute. + */ + DOMSVGPathSeg(); + + virtual ~DOMSVGPathSeg() { + // Our mList's weak ref to us must be nulled out when we die. If GC has + // unlinked us using the cycle collector code, then that has already + // happened, and mList is null. + if (mList) { + mList->ItemAt(mListIndex) = nullptr; + } + } + + dom::SVGElement* Element() { return mList->Element(); } + + /** + * Get a reference to the internal SVGPathSeg list item that this DOM wrapper + * object currently wraps. + * + * To simplify the code we just have this one method for obtaining both + * baseVal and animVal internal items. This means that animVal items don't + * get const protection, but then our setter methods guard against changing + * animVal items. + */ + float* InternalItem(); + + virtual float* PtrToMemberArgs() = 0; + +#ifdef DEBUG + bool IndexIsValid(); +#endif + + RefPtr<DOMSVGPathSegList> mList; + + // Bounds for the following are checked in the ctor, so be sure to update + // that if you change the capacity of any of the following. + + uint32_t mListIndex : MOZ_SVG_LIST_INDEX_BIT_COUNT; + uint32_t mIsAnimValItem : 1; // uint32_t because MSVC won't pack otherwise +}; + +class DOMSVGPathSegClosePath : public DOMSVGPathSeg { + public: + DOMSVGPathSegClosePath() : DOMSVGPathSeg() {} + + IMPL_SVGPATHSEG_SUBCLASS_COMMON(ClosePath, + dom::SVGPathSeg_Binding::PATHSEG_CLOSEPATH) + + protected: + // To allow IMPL_SVGPATHSEG_SUBCLASS_COMMON above to compile we need an + // mArgs, but since C++ doesn't allow zero-sized arrays we need to give it + // one (unused) element. + float mArgs[1]; +}; + +class DOMSVGPathSegMovetoAbs : public DOMSVGPathSeg { + public: + DOMSVGPathSegMovetoAbs(float x, float y) : DOMSVGPathSeg() { + mArgs[0] = x; + mArgs[1] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON(MovetoAbs, + dom::SVGPathSeg_Binding::PATHSEG_MOVETO_ABS) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[2]; +}; + +class DOMSVGPathSegMovetoRel : public DOMSVGPathSeg { + public: + DOMSVGPathSegMovetoRel(float x, float y) : DOMSVGPathSeg() { + mArgs[0] = x; + mArgs[1] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON(MovetoRel, + dom::SVGPathSeg_Binding::PATHSEG_MOVETO_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[2]; +}; + +class DOMSVGPathSegLinetoAbs : public DOMSVGPathSeg { + public: + DOMSVGPathSegLinetoAbs(float x, float y) : DOMSVGPathSeg() { + mArgs[0] = x; + mArgs[1] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoAbs, + dom::SVGPathSeg_Binding::PATHSEG_LINETO_ABS) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[2]; +}; + +class DOMSVGPathSegLinetoRel : public DOMSVGPathSeg { + public: + DOMSVGPathSegLinetoRel(float x, float y) : DOMSVGPathSeg() { + mArgs[0] = x; + mArgs[1] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON(LinetoRel, + dom::SVGPathSeg_Binding::PATHSEG_LINETO_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[2]; +}; + +class DOMSVGPathSegCurvetoCubicAbs : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoCubicAbs(float x1, float y1, float x2, float y2, float x, + float y) + : DOMSVGPathSeg() { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x2; + mArgs[3] = y2; + mArgs[4] = x; + mArgs[5] = y; + } + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float X1(); + void SetX1(float aX1, ErrorResult& rv); + float Y1(); + void SetY1(float aY1, ErrorResult& rv); + float X2(); + void SetX2(float aX2, ErrorResult& rv); + float Y2(); + void SetY2(float aY2, ErrorResult& rv); + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoCubicAbs, dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_ABS) + + protected: + float mArgs[6]; +}; + +class DOMSVGPathSegCurvetoCubicRel : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoCubicRel(float x1, float y1, float x2, float y2, float x, + float y) + : DOMSVGPathSeg() { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x2; + mArgs[3] = y2; + mArgs[4] = x; + mArgs[5] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoCubicRel, dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float X1(); + void SetX1(float aX1, ErrorResult& rv); + float Y1(); + void SetY1(float aY1, ErrorResult& rv); + float X2(); + void SetX2(float aX2, ErrorResult& rv); + float Y2(); + void SetY2(float aY2, ErrorResult& rv); + + protected: + float mArgs[6]; +}; + +class DOMSVGPathSegCurvetoQuadraticAbs : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoQuadraticAbs(float x1, float y1, float x, float y) + : DOMSVGPathSeg() { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x; + mArgs[3] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoQuadraticAbs, + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_ABS) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float X1(); + void SetX1(float aX1, ErrorResult& rv); + float Y1(); + void SetY1(float aY1, ErrorResult& rv); + + protected: + float mArgs[4]; +}; + +class DOMSVGPathSegCurvetoQuadraticRel : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoQuadraticRel(float x1, float y1, float x, float y) + : DOMSVGPathSeg() { + mArgs[0] = x1; + mArgs[1] = y1; + mArgs[2] = x; + mArgs[3] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoQuadraticRel, + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float X1(); + void SetX1(float aX1, ErrorResult& rv); + float Y1(); + void SetY1(float aY1, ErrorResult& rv); + + protected: + float mArgs[4]; +}; + +class DOMSVGPathSegArcAbs : public DOMSVGPathSeg { + public: + DOMSVGPathSegArcAbs(float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag, float x, float y) + : DOMSVGPathSeg() { + mArgs[0] = r1; + mArgs[1] = r2; + mArgs[2] = angle; + mArgs[3] = largeArcFlag; + mArgs[4] = sweepFlag; + mArgs[5] = x; + mArgs[6] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON(ArcAbs, + dom::SVGPathSeg_Binding::PATHSEG_ARC_ABS) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float R1(); + void SetR1(float aR1, ErrorResult& rv); + float R2(); + void SetR2(float aR2, ErrorResult& rv); + float Angle(); + void SetAngle(float aAngle, ErrorResult& rv); + bool LargeArcFlag(); + void SetLargeArcFlag(bool aLargeArcFlag, ErrorResult& rv); + bool SweepFlag(); + void SetSweepFlag(bool aSweepFlag, ErrorResult& rv); + + protected: + float mArgs[7]; +}; + +class DOMSVGPathSegArcRel : public DOMSVGPathSeg { + public: + DOMSVGPathSegArcRel(float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag, float x, float y) + : DOMSVGPathSeg() { + mArgs[0] = r1; + mArgs[1] = r2; + mArgs[2] = angle; + mArgs[3] = largeArcFlag; + mArgs[4] = sweepFlag; + mArgs[5] = x; + mArgs[6] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON(ArcRel, + dom::SVGPathSeg_Binding::PATHSEG_ARC_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float R1(); + void SetR1(float aR1, ErrorResult& rv); + float R2(); + void SetR2(float aR2, ErrorResult& rv); + float Angle(); + void SetAngle(float aAngle, ErrorResult& rv); + bool LargeArcFlag(); + void SetLargeArcFlag(bool aLargeArcFlag, ErrorResult& rv); + bool SweepFlag(); + void SetSweepFlag(bool aSweepFlag, ErrorResult& rv); + + protected: + float mArgs[7]; +}; + +class DOMSVGPathSegLinetoHorizontalAbs : public DOMSVGPathSeg { + public: + explicit DOMSVGPathSegLinetoHorizontalAbs(float x) : DOMSVGPathSeg() { + mArgs[0] = x; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + LinetoHorizontalAbs, + dom::SVGPathSeg_Binding::PATHSEG_LINETO_HORIZONTAL_ABS) + + float X(); + void SetX(float aX, ErrorResult& rv); + + protected: + float mArgs[1]; +}; + +class DOMSVGPathSegLinetoHorizontalRel : public DOMSVGPathSeg { + public: + explicit DOMSVGPathSegLinetoHorizontalRel(float x) : DOMSVGPathSeg() { + mArgs[0] = x; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + LinetoHorizontalRel, + dom::SVGPathSeg_Binding::PATHSEG_LINETO_HORIZONTAL_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + + protected: + float mArgs[1]; +}; + +class DOMSVGPathSegLinetoVerticalAbs : public DOMSVGPathSeg { + public: + explicit DOMSVGPathSegLinetoVerticalAbs(float y) : DOMSVGPathSeg() { + mArgs[0] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + LinetoVerticalAbs, dom::SVGPathSeg_Binding::PATHSEG_LINETO_VERTICAL_ABS) + + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[1]; +}; + +class DOMSVGPathSegLinetoVerticalRel : public DOMSVGPathSeg { + public: + explicit DOMSVGPathSegLinetoVerticalRel(float y) : DOMSVGPathSeg() { + mArgs[0] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + LinetoVerticalRel, dom::SVGPathSeg_Binding::PATHSEG_LINETO_VERTICAL_REL) + + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[1]; +}; + +class DOMSVGPathSegCurvetoCubicSmoothAbs : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoCubicSmoothAbs(float x2, float y2, float x, float y) + : DOMSVGPathSeg() { + mArgs[0] = x2; + mArgs[1] = y2; + mArgs[2] = x; + mArgs[3] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoCubicSmoothAbs, + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float X2(); + void SetX2(float aX2, ErrorResult& rv); + float Y2(); + void SetY2(float aY2, ErrorResult& rv); + + protected: + float mArgs[4]; +}; + +class DOMSVGPathSegCurvetoCubicSmoothRel : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoCubicSmoothRel(float x2, float y2, float x, float y) + : DOMSVGPathSeg() { + mArgs[0] = x2; + mArgs[1] = y2; + mArgs[2] = x; + mArgs[3] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoCubicSmoothRel, + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + float X2(); + void SetX2(float aX2, ErrorResult& rv); + float Y2(); + void SetY2(float aY2, ErrorResult& rv); + + protected: + float mArgs[4]; +}; + +class DOMSVGPathSegCurvetoQuadraticSmoothAbs : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) : DOMSVGPathSeg() { + mArgs[0] = x; + mArgs[1] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoQuadraticSmoothAbs, + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[2]; +}; + +class DOMSVGPathSegCurvetoQuadraticSmoothRel : public DOMSVGPathSeg { + public: + DOMSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) : DOMSVGPathSeg() { + mArgs[0] = x; + mArgs[1] = y; + } + + IMPL_SVGPATHSEG_SUBCLASS_COMMON( + CurvetoQuadraticSmoothRel, + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL) + + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + + protected: + float mArgs[2]; +}; + +} // namespace mozilla::dom + +#undef MOZ_SVG_LIST_INDEX_BIT_COUNT + +#endif // DOM_SVG_DOMSVGPATHSEG_H_ diff --git a/dom/svg/DOMSVGPathSegList.cpp b/dom/svg/DOMSVGPathSegList.cpp new file mode 100644 index 0000000000..b931fcf599 --- /dev/null +++ b/dom/svg/DOMSVGPathSegList.cpp @@ -0,0 +1,535 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGPathSegList.h" + +#include "DOMSVGPathSeg.h" +#include "nsError.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGAttrTearoffTable.h" +#include "SVGPathSegUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGPathSegListBinding.h" +#include "mozilla/RefPtr.h" + +// See the comment in this file's header. + +namespace mozilla::dom { + +static inline SVGAttrTearoffTable<void, DOMSVGPathSegList>& +SVGPathSegListTearoffTable() { + static SVGAttrTearoffTable<void, DOMSVGPathSegList> + sSVGPathSegListTearoffTable; + return sSVGPathSegListTearoffTable; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPathSegList) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPathSegList) + // No unlinking of mElement, we'd need to null out the value pointer (the + // object it points to is held by the element) and null-check it everywhere. + tmp->RemoveFromTearoffTable(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPathSegList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPathSegList) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPathSegList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPathSegList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPathSegList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* static */ +already_AddRefed<DOMSVGPathSegList> DOMSVGPathSegList::GetDOMWrapper( + void* aList, SVGElement* aElement, bool aIsAnimValList) { + RefPtr<DOMSVGPathSegList> wrapper = + SVGPathSegListTearoffTable().GetTearoff(aList); + if (!wrapper) { + wrapper = new DOMSVGPathSegList(aElement, aIsAnimValList); + SVGPathSegListTearoffTable().AddTearoff(aList, wrapper); + } + return wrapper.forget(); +} + +/* static */ +DOMSVGPathSegList* DOMSVGPathSegList::GetDOMWrapperIfExists(void* aList) { + return SVGPathSegListTearoffTable().GetTearoff(aList); +} + +void DOMSVGPathSegList::RemoveFromTearoffTable() { + // There are now no longer any references to us held by script or list items. + // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()! + void* key = mIsAnimValList ? InternalAList().GetAnimValKey() + : InternalAList().GetBaseValKey(); + SVGPathSegListTearoffTable().RemoveTearoff(key); +} + +DOMSVGPathSegList::~DOMSVGPathSegList() { RemoveFromTearoffTable(); } + +JSObject* DOMSVGPathSegList::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return mozilla::dom::SVGPathSegList_Binding::Wrap(cx, this, aGivenProto); +} + +void DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData& aNewValue) { + // When the number of items in our internal counterpart changes, we MUST stay + // in sync. Everything in the scary comment in + // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here just as + // much, but we have the additional issue that failing to stay in sync would + // mean that - assuming we aren't reading bad memory - we would likely end up + // decoding command types from argument floats when looking in our + // SVGPathData's data array! Either way, we'll likely then go down + // MOZ_ASSERT_UNREACHABLE code paths, or end up reading/setting more bad + // memory!! + + // The only time that our other DOM list type implementations remove items is + // if those items become surplus items due to an attribute change or SMIL + // animation sample shortening the list. In general though, they try to keep + // their existing DOM items, even when things change. To be consistent, we'd + // really like to do the same thing. However, because different types of path + // segment correspond to different DOMSVGPathSeg subclasses, the type of + // items in our list are generally not the same, which makes this harder for + // us. We have to remove DOM segments if their type is not the same as the + // type of the new internal segment at their index. + // + // We also need to sync up mInternalDataIndex, but since we need to loop over + // all the items in the new list checking types anyway, that's almost + // insignificant in terms of overhead. + // + // Note that this method is called on every single SMIL animation resample + // and we have no way to short circuit the overhead since we don't have a + // way to tell if the call is due to a new animation, or a resample of an + // existing animation (when the number and type of items would be the same). + // (Note that a new animation could start overriding an existing animation at + // any time, so checking IsAnimating() wouldn't work.) Because we get called + // on every sample, it would not be acceptable alternative to throw away all + // our items and let them be recreated lazily, since that would break what + // script sees! + + uint32_t length = mItems.Length(); + uint32_t index = 0; + + uint32_t dataLength = aNewValue.mData.Length(); + uint32_t dataIndex = 0; // index into aNewValue's raw data array + + uint32_t newSegType; + + RefPtr<DOMSVGPathSegList> kungFuDeathGrip; + if (length) { + // RemovingFromList() might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + // + // NOTE: For path-seg lists (unlike other list types), we have to do this + // *whenever our list is nonempty* (even if we're growing in length). + // That's because the path-seg-type of any segment could differ between old + // list vs. new list, which will make us destroy & recreate that segment, + // which could remove the last reference to us. + // + // (We explicitly *don't* want to create a kungFuDeathGrip in the length=0 + // case, though, because we do hit this code inside our constructor before + // any other owning references have been added, and at that point, the + // deathgrip-removal would make us die before we exit our constructor.) + kungFuDeathGrip = this; + } + + while (index < length && dataIndex < dataLength) { + newSegType = SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]); + if (ItemAt(index) && ItemAt(index)->Type() != newSegType) { + ItemAt(index)->RemovingFromList(); + ItemAt(index) = nullptr; + } + // Only after the RemovingFromList() can we touch mInternalDataIndex! + mItems[index].mInternalDataIndex = dataIndex; + ++index; + dataIndex += 1 + SVGPathSegUtils::ArgCountForType(newSegType); + } + + MOZ_ASSERT((index == length && dataIndex <= dataLength) || + (index <= length && dataIndex == dataLength), + "very bad - list corruption?"); + + if (index < length) { + // aNewValue has fewer items than our previous internal counterpart + + uint32_t newLength = index; + + // Remove excess items from the list: + for (; index < length; ++index) { + if (ItemAt(index)) { + ItemAt(index)->RemovingFromList(); + ItemAt(index) = nullptr; + } + } + + // Only now may we truncate mItems + mItems.TruncateLength(newLength); + } else if (dataIndex < dataLength) { + // aNewValue has more items than our previous internal counterpart + + // Sync mItems: + while (dataIndex < dataLength) { + if (mItems.Length() && + mItems.Length() - 1 > DOMSVGPathSeg::MaxListIndex()) { + // It's safe to get out of sync with our internal list as long as we + // have FEWER items than it does. + return; + } + if (!mItems.AppendElement(ItemProxy(nullptr, dataIndex), fallible)) { + // OOM + ErrorResult rv; + Clear(rv); + MOZ_ASSERT(!rv.Failed()); + return; + } + dataIndex += + 1 + SVGPathSegUtils::ArgCountForType( + SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex])); + } + } + + MOZ_ASSERT(dataIndex == dataLength, "Serious processing error"); + MOZ_ASSERT(index == length, "Serious counting error"); +} + +bool DOMSVGPathSegList::AttrIsAnimating() const { + return InternalAList().IsAnimating(); +} + +bool DOMSVGPathSegList::AnimListMirrorsBaseList() const { + return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) && + !AttrIsAnimating(); +} + +SVGPathData& DOMSVGPathSegList::InternalList() const { + SVGAnimatedPathSegList* alist = mElement->GetAnimPathSegList(); + return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal + : alist->mBaseVal; +} + +SVGAnimatedPathSegList& DOMSVGPathSegList::InternalAList() const { + MOZ_ASSERT(mElement->GetAnimPathSegList(), "Internal error"); + return *mElement->GetAnimPathSegList(); +} + +// ---------------------------------------------------------------------------- +// nsIDOMSVGPathSegList implementation: + +void DOMSVGPathSegList::Clear(ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (LengthNoFlush() > 0) { + AutoChangePathSegListNotifier notifier(this); + // DOM list items that are to be removed must be removed before we change + // the internal list, otherwise they wouldn't be able to copy their + // internal counterparts' values! + + InternalListWillChangeTo(SVGPathData()); // clears mItems + + if (!AttrIsAnimating()) { + // The anim val list is in sync with the base val list + DOMSVGPathSegList* animList = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + if (animList) { + animList->InternalListWillChangeTo(SVGPathData()); // clears its mItems + } + } + + InternalList().Clear(); + } +} + +already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::Initialize( + DOMSVGPathSeg& aNewItem, ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + // If aNewItem is already in a list we should insert a clone of aNewItem, + // and for consistency, this should happen even if *this* is the list that + // aNewItem is currently in. Note that in the case of aNewItem being in this + // list, the Clear() call before the InsertItemBefore() call would remove it + // from this list, and so the InsertItemBefore() call would not insert a + // clone of aNewItem, it would actually insert aNewItem. To prevent that + // from happening we have to do the clone here, if necessary. + + RefPtr<DOMSVGPathSeg> domItem = &aNewItem; + if (aNewItem.HasOwner()) { + domItem = aNewItem.Clone(); + } + + Clear(aError); + MOZ_ASSERT(!aError.Failed(), "How could this fail?"); + return InsertItemBefore(*domItem, 0, aError); +} + +already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::GetItem(uint32_t index, + ErrorResult& error) { + bool found; + RefPtr<DOMSVGPathSeg> item = IndexedGetter(index, found, error); + if (!found) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + } + return item.forget(); +} + +already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::IndexedGetter( + uint32_t aIndex, bool& aFound, ErrorResult& aError) { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + aFound = aIndex < LengthNoFlush(); + if (aFound) { + return GetItemAt(aIndex); + } + return nullptr; +} + +already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::InsertItemBefore( + DOMSVGPathSeg& aNewItem, uint32_t aIndex, ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + uint32_t internalIndex; + if (aIndex < LengthNoFlush()) { + internalIndex = mItems[aIndex].mInternalDataIndex; + } else { + aIndex = LengthNoFlush(); + internalIndex = InternalList().mData.Length(); + } + if (aIndex >= DOMSVGPathSeg::MaxListIndex()) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGPathSeg> domItem = &aNewItem; + if (domItem->HasOwner()) { + domItem = domItem->Clone(); // must do this before changing anything! + } + + uint32_t argCount = SVGPathSegUtils::ArgCountForType(domItem->Type()); + + // Ensure we have enough memory so we can avoid complex error handling below: + if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || + !InternalList().mData.SetCapacity( + InternalList().mData.Length() + 1 + argCount, fallible)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + if (AnimListMirrorsBaseList()) { + DOMSVGPathSegList* animVal = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + MOZ_ASSERT(animVal, "animVal should be a valid pointer"); + if (!animVal->mItems.SetCapacity(animVal->mItems.Length() + 1, fallible)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + } + + AutoChangePathSegListNotifier notifier(this); + // Now that we know we're inserting, keep animVal list in sync as necessary. + MaybeInsertNullInAnimValListAt(aIndex, internalIndex, argCount); + + float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS]; + domItem->ToSVGPathSegEncodedData(segAsRaw); + + MOZ_ALWAYS_TRUE(InternalList().mData.InsertElementsAt( + internalIndex, segAsRaw, 1 + argCount, fallible)); + MOZ_ALWAYS_TRUE(mItems.InsertElementAt( + aIndex, ItemProxy(domItem.get(), internalIndex), fallible)); + + // This MUST come after the insertion into InternalList(), or else under the + // insertion into InternalList() the values read from domItem would be bad + // data from InternalList() itself!: + domItem->InsertingIntoList(this, aIndex, IsAnimValList()); + + UpdateListIndicesFromIndex(aIndex + 1, argCount + 1); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::ReplaceItem( + DOMSVGPathSeg& aNewItem, uint32_t aIndex, ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (aIndex >= LengthNoFlush()) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGPathSeg> domItem = &aNewItem; + if (domItem->HasOwner()) { + domItem = domItem->Clone(); // must do this before changing anything! + } + + AutoChangePathSegListNotifier notifier(this); + if (ItemAt(aIndex)) { + // Notify any existing DOM item of removal *before* modifying the lists so + // that the DOM item can copy the *old* value at its index: + ItemAt(aIndex)->RemovingFromList(); + } + + uint32_t internalIndex = mItems[aIndex].mInternalDataIndex; + // We use InternalList() to get oldArgCount since we may not have a DOM + // wrapper at the index being replaced. + uint32_t oldType = + SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]); + + // NOTE: ArgCountForType returns a (small) unsigned value, but we're + // intentionally putting it in a signed variable, because we're going to + // subtract these values and might produce something negative. + int32_t oldArgCount = SVGPathSegUtils::ArgCountForType(oldType); + int32_t newArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type()); + + float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS]; + domItem->ToSVGPathSegEncodedData(segAsRaw); + + if (!InternalList().mData.ReplaceElementsAt(internalIndex, 1 + oldArgCount, + segAsRaw, 1 + newArgCount, + fallible)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + ItemAt(aIndex) = domItem; + + // This MUST come after the ToSVGPathSegEncodedData call, otherwise that call + // would end up reading bad data from InternalList()! + domItem->InsertingIntoList(this, aIndex, IsAnimValList()); + + int32_t delta = newArgCount - oldArgCount; + if (delta != 0) { + for (uint32_t i = aIndex + 1; i < LengthNoFlush(); ++i) { + mItems[i].mInternalDataIndex += delta; + } + } + + return domItem.forget(); +} + +already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::RemoveItem( + uint32_t aIndex, ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (aIndex >= LengthNoFlush()) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + // We have to return the removed item, so get it, creating it if necessary: + RefPtr<DOMSVGPathSeg> result = GetItemAt(aIndex); + + AutoChangePathSegListNotifier notifier(this); + // Notify the DOM item of removal *before* modifying the lists so that the + // DOM item can copy its *old* value: + ItemAt(aIndex)->RemovingFromList(); + + uint32_t internalIndex = mItems[aIndex].mInternalDataIndex; + uint32_t segType = + SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]); + // NOTE: ArgCountForType returns a (small) unsigned value, but we're + // intentionally putting it in a signed value, because we're going to + // negate it, and you can't negate an unsigned value. + int32_t argCount = SVGPathSegUtils::ArgCountForType(segType); + + // Now that we know we're removing, keep animVal list in sync as necessary. + // Do this *before* touching InternalList() so the removed item can get its + // internal value. + MaybeRemoveItemFromAnimValListAt(aIndex, argCount); + + InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount); + mItems.RemoveElementAt(aIndex); + + UpdateListIndicesFromIndex(aIndex, -(argCount + 1)); + + return result.forget(); +} + +already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::GetItemAt(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length()); + + if (!ItemAt(aIndex)) { + ItemAt(aIndex) = DOMSVGPathSeg::CreateFor(this, aIndex, IsAnimValList()); + } + RefPtr<DOMSVGPathSeg> result = ItemAt(aIndex); + return result.forget(); +} + +void DOMSVGPathSegList::MaybeInsertNullInAnimValListAt( + uint32_t aIndex, uint32_t aInternalIndex, uint32_t aArgCountForItem) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + // The anim val list is in sync with the base val list + DOMSVGPathSegList* animVal = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt( + aIndex, ItemProxy(nullptr, aInternalIndex), fallible)); + + animVal->UpdateListIndicesFromIndex(aIndex + 1, 1 + aArgCountForItem); +} + +void DOMSVGPathSegList::MaybeRemoveItemFromAnimValListAt( + uint32_t aIndex, int32_t aArgCountForItem) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + // This needs to be a strong reference; otherwise, the RemovingFromList call + // below might drop the last reference to animVal before we're done with it. + RefPtr<DOMSVGPathSegList> animVal = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + + if (animVal->ItemAt(aIndex)) { + animVal->ItemAt(aIndex)->RemovingFromList(); + } + animVal->mItems.RemoveElementAt(aIndex); + + animVal->UpdateListIndicesFromIndex(aIndex, -(1 + aArgCountForItem)); +} + +void DOMSVGPathSegList::UpdateListIndicesFromIndex( + uint32_t aStartingIndex, int32_t aInternalDataIndexDelta) { + uint32_t length = mItems.Length(); + + for (uint32_t i = aStartingIndex; i < length; ++i) { + mItems[i].mInternalDataIndex += aInternalDataIndexDelta; + if (ItemAt(i)) { + ItemAt(i)->UpdateListIndex(i); + } + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGPathSegList.h b/dom/svg/DOMSVGPathSegList.h new file mode 100644 index 0000000000..7339c4ae46 --- /dev/null +++ b/dom/svg/DOMSVGPathSegList.h @@ -0,0 +1,265 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGPATHSEGLIST_H_ +#define DOM_SVG_DOMSVGPATHSEGLIST_H_ + +#include "mozAutoDocUpdate.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsTArray.h" +#include "SVGPathData.h" // IWYU pragma: keep +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla { + +class ErrorResult; +class SVGAnimatedPathSegList; + +namespace dom { + +class DOMSVGPathSeg; + +//---------------------------------------------------------------------- +// Helper class: AutoChangePathSegListNotifier +// Stack-based helper class to pair calls to WillChangePathSegList and +// DidChangePathSegList. Used by DOMSVGPathSeg and DOMSVGPathSegList. +template <class T> +class MOZ_RAII AutoChangePathSegListNotifier : public mozAutoDocUpdate { + public: + explicit AutoChangePathSegListNotifier(T* aValue) + : mozAutoDocUpdate(aValue->Element()->GetComposedDoc(), true), + mValue(aValue) { + MOZ_ASSERT(mValue, "Expecting non-null value"); + mEmptyOrOldValue = mValue->Element()->WillChangePathSegList(*this); + } + + ~AutoChangePathSegListNotifier() { + mValue->Element()->DidChangePathSegList(mEmptyOrOldValue, *this); + if (mValue->AttrIsAnimating()) { + mValue->Element()->AnimationNeedsResample(); + } + } + + private: + T* const mValue; + nsAttrValue mEmptyOrOldValue; +}; + +/** + * Class DOMSVGPathSegList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGPathData objects. + * + * See the architecture comment in DOMSVGAnimatedLengthList.h first (that's + * LENGTH list), then continue reading the remainder of this comment. + * + * The architecture of this class is very similar to that of DOMSVGLengthList + * except that, since there is no nsIDOMSVGAnimatedPathSegList interface + * in SVG, we have no parent DOMSVGAnimatedPathSegList (unlike DOMSVGLengthList + * which has a parent DOMSVGAnimatedLengthList class). (There is an + * SVGAnimatedPathData interface, but that is quite different to + * DOMSVGAnimatedLengthList, since it is inherited by elements rather than + * elements having members of that type.) As a consequence, much of the logic + * that would otherwise be in DOMSVGAnimatedPathSegList (and is in + * DOMSVGAnimatedLengthList) is contained in this class. + * + * This class is strongly intertwined with DOMSVGPathSeg. Our DOMSVGPathSeg + * items are friends of us and responsible for nulling out our pointers to + * them when they die. + * + * Our DOM items are created lazily on demand as and when script requests them. + */ +class DOMSVGPathSegList final : public nsISupports, public nsWrapperCache { + template <class T> + friend class AutoChangePathSegListNotifier; + friend class DOMSVGPathSeg; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGPathSegList) + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + nsISupports* GetParentObject() { return static_cast<nsIContent*>(mElement); } + + /** + * Factory method to create and return a DOMSVGPathSegList wrapper + * for a given internal SVGPathData object. The factory takes care + * of caching the object that it returns so that the same object can be + * returned for the given SVGPathData each time it is requested. + * The cached object is only removed from the cache when it is destroyed due + * to there being no more references to it or to any of its descendant + * objects. If that happens, any subsequent call requesting the DOM wrapper + * for the SVGPathData will naturally result in a new + * DOMSVGPathSegList being returned. + * + * It's unfortunate that aList is a void* instead of a typed argument. This + * is because the mBaseVal and mAnimVal members of SVGAnimatedPathSegList are + * of different types - a plain SVGPathData, and a SVGPathData*. We + * use the addresses of these members as the key for the hash table, and + * clearly SVGPathData* and a SVGPathData** are not the same type. + */ + static already_AddRefed<DOMSVGPathSegList> GetDOMWrapper( + void* aList, dom::SVGElement* aElement, bool aIsAnimValList); + + /** + * This method returns the DOMSVGPathSegList wrapper for an internal + * SVGPathData object if it currently has a wrapper. If it does + * not, then nullptr is returned. + */ + static DOMSVGPathSegList* GetDOMWrapperIfExists(void* aList); + + /** + * This will normally be the same as InternalList().CountItems(), except if + * we've hit OOM, in which case our length will be zero. + */ + uint32_t LengthNoFlush() const { + MOZ_ASSERT( + mItems.Length() == 0 || mItems.Length() == InternalList().CountItems(), + "DOM wrapper's list length is out of sync"); + return mItems.Length(); + } + + /** + * WATCH OUT! If you add code to call this on a baseVal wrapper, then you + * must also call it on the animVal wrapper too if necessary!! See other + * callers! + * + * Called by internal code to notify us when we need to sync the length of + * this DOM list with its internal list. This is called immediately prior to + * the length of the internal list being changed so that any DOM list items + * that need to be removed from the DOM list can first copy their values from + * their internal counterpart. + * + * The only time this method could fail is on OOM when trying to increase the + * length of the DOM list. If that happens then this method simply clears the + * list and returns. Callers just proceed as normal, and we simply accept + * that the DOM list will be empty (until successfully set to a new value). + */ + void InternalListWillChangeTo(const SVGPathData& aNewValue); + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool AttrIsAnimating() const; + /** + * Returns true if there is an animated list mirroring the base list. + */ + bool AnimListMirrorsBaseList() const; + + uint32_t NumberOfItems() const { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + return LengthNoFlush(); + } + void Clear(ErrorResult& aError); + already_AddRefed<DOMSVGPathSeg> Initialize(DOMSVGPathSeg& aNewItem, + ErrorResult& aError); + already_AddRefed<DOMSVGPathSeg> GetItem(uint32_t index, ErrorResult& error); + already_AddRefed<DOMSVGPathSeg> IndexedGetter(uint32_t index, bool& found, + ErrorResult& error); + already_AddRefed<DOMSVGPathSeg> InsertItemBefore(DOMSVGPathSeg& aNewItem, + uint32_t aIndex, + ErrorResult& aError); + already_AddRefed<DOMSVGPathSeg> ReplaceItem(DOMSVGPathSeg& aNewItem, + uint32_t aIndex, + ErrorResult& aError); + already_AddRefed<DOMSVGPathSeg> RemoveItem(uint32_t aIndex, + ErrorResult& aError); + already_AddRefed<DOMSVGPathSeg> AppendItem(DOMSVGPathSeg& aNewItem, + ErrorResult& aError) { + return InsertItemBefore(aNewItem, LengthNoFlush(), aError); + } + uint32_t Length() const { return NumberOfItems(); } + + private: + /** + * Only our static GetDOMWrapper() factory method may create objects of our + * type. + */ + DOMSVGPathSegList(dom::SVGElement* aElement, bool aIsAnimValList) + : mElement(aElement), mIsAnimValList(aIsAnimValList) { + InternalListWillChangeTo(InternalList()); // Sync mItems + } + + ~DOMSVGPathSegList(); + + dom::SVGElement* Element() const { return mElement.get(); } + + /// Used to determine if this list is the baseVal or animVal list. + bool IsAnimValList() const { return mIsAnimValList; } + + /** + * Get a reference to this object's corresponding internal SVGPathData. + * + * To simplify the code we just have this one method for obtaining both + * base val and anim val internal lists. This means that anim val lists don't + * get const protection, but our setter methods guard against changing + * anim val lists. + */ + SVGPathData& InternalList() const; + + SVGAnimatedPathSegList& InternalAList() const; + + /// Creates an instance of the appropriate DOMSVGPathSeg sub-class for + // aIndex, if it doesn't already exist, and then returns it. + already_AddRefed<DOMSVGPathSeg> GetItemAt(uint32_t aIndex); + + void MaybeInsertNullInAnimValListAt(uint32_t aIndex, uint32_t aInternalIndex, + uint32_t aArgCountForItem); + void MaybeRemoveItemFromAnimValListAt(uint32_t aIndex, + int32_t aArgCountForItem); + + // Calls UpdateListIndex on all elements in |mItems| that satisfy ItemAt(), + // from |aStartingIndex| to the end of |mItems|. Also adjusts + // |mItems.mInternalDataIndex| by the requested amount. + void UpdateListIndicesFromIndex(uint32_t aStartingIndex, + int32_t aInternalDataIndexDelta); + + DOMSVGPathSeg*& ItemAt(uint32_t aIndex) { return mItems[aIndex].mItem; } + + void RemoveFromTearoffTable(); + + /** + * This struct is used in our array of mItems to provide us with somewhere to + * store the indexes into the internal SVGPathData of the internal seg data + * that our DOMSVGPathSeg items wrap (the internal segment data is or varying + * length, so we can't just use the index of our DOMSVGPathSeg items + * themselves). The reason that we have this separate struct rather than + * just storing the internal indexes in the DOMSVGPathSeg items is because we + * want to create the DOMSVGPathSeg items lazily on demand. + */ + struct ItemProxy { + ItemProxy() : mItem(nullptr), mInternalDataIndex(0) {} + ItemProxy(DOMSVGPathSeg* aItem, uint32_t aInternalDataIndex) + : mItem(aItem), mInternalDataIndex(aInternalDataIndex) {} + + DOMSVGPathSeg* mItem; + uint32_t mInternalDataIndex; + }; + + // Weak refs to our DOMSVGPathSeg items. The items are friends and take care + // of clearing our pointer to them when they die. + FallibleTArray<ItemProxy> mItems; + + // Strong ref to our element to keep it alive. We hold this not only for + // ourself, but also for our DOMSVGPathSeg items too. + RefPtr<dom::SVGElement> mElement; + + bool mIsAnimValList; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGPATHSEGLIST_H_ diff --git a/dom/svg/DOMSVGPoint.cpp b/dom/svg/DOMSVGPoint.cpp new file mode 100644 index 0000000000..5cf97c54e4 --- /dev/null +++ b/dom/svg/DOMSVGPoint.cpp @@ -0,0 +1,227 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGPoint.h" + +#include "DOMSVGPointList.h" +#include "gfx2DGlue.h" +#include "nsError.h" +#include "mozilla/dom/DOMMatrix.h" +#include "mozilla/dom/SVGPointBinding.h" + +// See the architecture comment in DOMSVGPointList.h. + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// Helper class: AutoChangePointNotifier +// +class MOZ_RAII AutoChangePointNotifier { + public: + explicit AutoChangePointNotifier(DOMSVGPoint* aValue) : mValue(aValue) { + MOZ_ASSERT(mValue, "Expecting non-null value"); + } + + ~AutoChangePointNotifier() { + if (mValue->IsTranslatePoint()) { + mValue->DidChangeTranslate(); + } + } + + private: + DOMSVGPoint* const mValue; +}; + +static SVGAttrTearoffTable<SVGPoint, DOMSVGPoint> sSVGTranslateTearOffTable; + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our list's weak ref to us to be safe. (The other option would be to +// not unlink and rely on the breaking of the other edges in the cycle, as +// NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPoint) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPoint) + tmp->CleanupWeakRefs(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPoint) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPoint) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +JSObject* DOMSVGPoint::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGPoint_Binding::Wrap(aCx, this, aGivenProto); +} + +float DOMSVGPoint::X() { + if (mIsAnimValItem && IsInList()) { + Element()->FlushAnimations(); // May make IsInList() == false + } + return InternalItem().mX; +} + +void DOMSVGPoint::SetX(float aX, ErrorResult& rv) { + if (mIsAnimValItem) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + auto& val = InternalItem(); + + if (val.mX == aX) { + return; + } + + AutoChangePointListNotifier listNotifier(this); + AutoChangePointNotifier translateNotifier(this); + + val.mX = aX; +} + +float DOMSVGPoint::Y() { + if (mIsAnimValItem && IsInList()) { + Element()->FlushAnimations(); // May make IsInList() == false + } + return InternalItem().mY; +} + +void DOMSVGPoint::SetY(float aY, ErrorResult& rv) { + if (mIsAnimValItem) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + auto& val = InternalItem(); + + if (val.mY == aY) { + return; + } + + AutoChangePointListNotifier listNotifier(this); + AutoChangePointNotifier translateNotifier(this); + + val.mY = aY; +} + +already_AddRefed<DOMSVGPoint> DOMSVGPoint::MatrixTransform( + const DOMMatrix2DInit& aMatrix, ErrorResult& aRv) { + RefPtr<DOMMatrixReadOnly> matrix = + DOMMatrixReadOnly::FromMatrix(GetParentObject(), aMatrix, aRv); + if (aRv.Failed()) { + return nullptr; + } + const auto* matrix2D = matrix->GetInternal2D(); + if (!matrix2D->IsFinite()) { + aRv.ThrowTypeError<MSG_NOT_FINITE>("MatrixTransform matrix"); + return nullptr; + } + auto pt = matrix2D->TransformPoint(InternalItem()); + RefPtr<DOMSVGPoint> newPoint = new DOMSVGPoint(ToPoint(pt)); + return newPoint.forget(); +} + +void DOMSVGPoint::InsertingIntoList(DOMSVGPointList* aList, uint32_t aListIndex, + bool aIsAnimValItem) { + MOZ_RELEASE_ASSERT(!IsInList(), "Inserting item that is already in a list"); + MOZ_RELEASE_ASSERT(!mIsTranslatePoint, + "Inserting item that is a currentTranslate"); + + delete mVal; + mVal = nullptr; + + mOwner = aList; + mListIndex = aListIndex; + mIsAnimValItem = aIsAnimValItem; + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGPoint!"); +} + +void DOMSVGPoint::RemovingFromList() { + MOZ_ASSERT( + IsInList(), + "We should start in a list if we're going to be removed from one."); + mVal = new SVGPoint(InternalItem()); + mOwner = nullptr; + mIsAnimValItem = false; +} + +SVGPoint& DOMSVGPoint::InternalItem() { + if (nsCOMPtr<DOMSVGPointList> pointList = do_QueryInterface(mOwner)) { + return pointList->InternalList().mItems[mListIndex]; + } + return *mVal; +} + +already_AddRefed<DOMSVGPoint> DOMSVGPoint::GetTranslateTearOff( + SVGPoint* aVal, SVGSVGElement* aSVGSVGElement) { + RefPtr<DOMSVGPoint> domPoint = sSVGTranslateTearOffTable.GetTearoff(aVal); + if (!domPoint) { + domPoint = new DOMSVGPoint(aVal, aSVGSVGElement); + sSVGTranslateTearOffTable.AddTearoff(aVal, domPoint); + } + + return domPoint.forget(); +} + +bool DOMSVGPoint::AttrIsAnimating() const { + nsCOMPtr<DOMSVGPointList> pointList = do_QueryInterface(mOwner); + return pointList && pointList->AttrIsAnimating(); +} + +void DOMSVGPoint::DidChangeTranslate() { + nsCOMPtr<SVGSVGElement> svg = do_QueryInterface(mOwner); + MOZ_ASSERT(svg); + nsContentUtils::AddScriptRunner( + NewRunnableMethod("dom::SVGSVGElement::DidChangeTranslate", svg, + &SVGSVGElement::DidChangeTranslate)); +} + +SVGElement* DOMSVGPoint::Element() { + if (nsCOMPtr<DOMSVGPointList> pointList = do_QueryInterface(mOwner)) { + return pointList->Element(); + } + nsCOMPtr<SVGSVGElement> svg = do_QueryInterface(mOwner); + return svg; +} + +void DOMSVGPoint::CleanupWeakRefs() { + // Our mList's weak ref to us must be nulled out when we die (or when we're + // cycle collected), so we that don't leave behind a pointer to + // free / soon-to-be-free memory. + if (nsCOMPtr<DOMSVGPointList> pointList = do_QueryInterface(mOwner)) { + MOZ_ASSERT(pointList->mItems[mListIndex] == this, + "Clearing out the wrong list index...?"); + pointList->mItems[mListIndex] = nullptr; + } + + if (mVal) { + if (mIsTranslatePoint) { + // Similarly, we must update the tearoff table to remove its (non-owning) + // pointer to mVal. + sSVGTranslateTearOffTable.RemoveTearoff(mVal); + } else { + // In this case we own mVal + delete mVal; + } + mVal = nullptr; + } +} + +#ifdef DEBUG +bool DOMSVGPoint::IndexIsValid() { + nsCOMPtr<DOMSVGPointList> pointList = do_QueryInterface(mOwner); + return mListIndex < pointList->InternalList().Length(); +} +#endif + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGPoint.h b/dom/svg/DOMSVGPoint.h new file mode 100644 index 0000000000..eeea3d3075 --- /dev/null +++ b/dom/svg/DOMSVGPoint.h @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGPOINT_H_ +#define DOM_SVG_DOMSVGPOINT_H_ + +#include "DOMSVGPointList.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "SVGPoint.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/gfx/2D.h" + +#define MOZ_SVG_LIST_INDEX_BIT_COUNT 30 + +namespace mozilla::dom { +struct DOMMatrix2DInit; + +/** + * Class DOMSVGPoint + * + * This class creates the DOM objects that wrap internal SVGPoint objects that + * are in an SVGPointList. It is also used to create the objects returned by + * SVGSVGElement.createSVGPoint() and other functions that return DOM SVGPoint + * objects. + * + * See the architecture comment in DOMSVGPointList.h for an overview of the + * important points regarding these DOM wrapper structures. + * + * See the architecture comment in DOMSVGLength.h (yes, LENGTH) for an overview + * of the important points regarding how this specific class works. + */ +class DOMSVGPoint final : public nsWrapperCache { + template <class T> + friend class AutoChangePointListNotifier; + + using Point = gfx::Point; + + public: + /** + * Generic ctor for DOMSVGPoint objects that are created for an attribute. + */ + DOMSVGPoint(DOMSVGPointList* aList, uint32_t aListIndex, bool aIsAnimValItem) + : mVal(nullptr), + mOwner(aList), + mListIndex(aListIndex), + mIsAnimValItem(aIsAnimValItem), + mIsTranslatePoint(false) { + // These shifts are in sync with the members. + MOZ_ASSERT(aList && aListIndex <= MaxListIndex(), "bad arg"); + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGPoint!"); + } + + // Constructor for unowned points and SVGSVGElement.createSVGPoint + explicit DOMSVGPoint(const Point& aPt) + : mListIndex(0), mIsAnimValItem(false), mIsTranslatePoint(false) { + // In this case we own mVal + mVal = new SVGPoint(aPt.x, aPt.y); + } + + private: + // The translate of an SVGSVGElement + DOMSVGPoint(SVGPoint* aPt, SVGSVGElement* aSVGSVGElement) + : mVal(aPt), + mOwner(ToSupports(aSVGSVGElement)), + mListIndex(0), + mIsAnimValItem(false), + mIsTranslatePoint(true) {} + + virtual ~DOMSVGPoint() { CleanupWeakRefs(); } + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGPoint) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGPoint) + + static already_AddRefed<DOMSVGPoint> GetTranslateTearOff( + SVGPoint* aVal, SVGSVGElement* aSVGSVGElement); + + bool IsInList() const { return HasOwner() && !IsTranslatePoint(); } + + /** + * "Owner" here means that the instance has an + * internal counterpart from which it gets its values. (A better name may + * be HasWrappee().) + */ + bool HasOwner() const { return !!mOwner; } + + bool IsTranslatePoint() const { return mIsTranslatePoint; } + + void DidChangeTranslate(); + + /** + * This method is called to notify this DOM object that it is being inserted + * into a list, and give it the information it needs as a result. + * + * This object MUST NOT already belong to a list when this method is called. + * That's not to say that script can't move these DOM objects between + * lists - it can - it's just that the logic to handle that (and send out + * the necessary notifications) is located elsewhere (in DOMSVGPointList).) + */ + void InsertingIntoList(DOMSVGPointList* aList, uint32_t aListIndex, + bool aIsAnimValItem); + + static uint32_t MaxListIndex() { + return (1U << MOZ_SVG_LIST_INDEX_BIT_COUNT) - 1; + } + + /// This method is called to notify this object that its list index changed. + void UpdateListIndex(uint32_t aListIndex) { mListIndex = aListIndex; } + + /** + * This method is called to notify this DOM object that it is about to be + * removed from its current DOM list so that it can first make a copy of its + * internal counterpart's values. (If it didn't do this, then it would + * "lose" its value on being removed.) + */ + void RemovingFromList(); + + SVGPoint ToSVGPoint() { return InternalItem(); } + + // WebIDL + float X(); + void SetX(float aX, ErrorResult& rv); + float Y(); + void SetY(float aY, ErrorResult& rv); + already_AddRefed<DOMSVGPoint> MatrixTransform(const DOMMatrix2DInit& aMatrix, + ErrorResult& aRv); + + nsISupports* GetParentObject() { return Element(); } + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool AttrIsAnimating() const; + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + DOMSVGPoint* Copy() { return new DOMSVGPoint(InternalItem()); } + + private: +#ifdef DEBUG + bool IndexIsValid(); +#endif + + SVGElement* Element(); + + /** + * Clears soon-to-be-invalid weak references in external objects that were + * set up during the creation of this object. This should be called during + * destruction and during cycle collection. + */ + void CleanupWeakRefs(); + + /** + * Get a reference to the internal SVGPoint list item that this DOM wrapper + * object currently wraps. + */ + SVGPoint& InternalItem(); + + SVGPoint* mVal; // If mIsTranslatePoint is true, the element owns + // the value. Otherwise we do. + RefPtr<nsISupports> mOwner; // If mIsTranslatePoint is true, this is an + // SVGSVGElement, if we're unowned it's null, or + // we're in a list and it's a DOMSVGPointList + + // Bounds for the following are checked in the ctor, so be sure to update + // that if you change the capacity of any of the following. + + uint32_t mListIndex : MOZ_SVG_LIST_INDEX_BIT_COUNT; + uint32_t mIsAnimValItem : 1; // True if We're the animated value of a list + uint32_t mIsTranslatePoint : 1; // true iff our owner is a SVGSVGElement +}; + +} // namespace mozilla::dom + +#undef MOZ_SVG_LIST_INDEX_BIT_COUNT + +#endif // DOM_SVG_DOMSVGPOINT_H_ diff --git a/dom/svg/DOMSVGPointList.cpp b/dom/svg/DOMSVGPointList.cpp new file mode 100644 index 0000000000..d512b58b07 --- /dev/null +++ b/dom/svg/DOMSVGPointList.cpp @@ -0,0 +1,414 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGPointList.h" + +#include "nsContentUtils.h" +#include "DOMSVGPoint.h" +#include "nsError.h" +#include "SVGAnimatedPointList.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGPointListBinding.h" +#include <algorithm> + +// See the comment in this file's header. + +// local helper functions +namespace { + +void UpdateListIndicesFromIndex( + FallibleTArray<mozilla::dom::DOMSVGPoint*>& aItemsArray, + uint32_t aStartingIndex) { + uint32_t length = aItemsArray.Length(); + + for (uint32_t i = aStartingIndex; i < length; ++i) { + if (aItemsArray[i]) { + aItemsArray[i]->UpdateListIndex(i); + } + } +} + +} // namespace + +namespace mozilla::dom { + +static inline SVGAttrTearoffTable<void, DOMSVGPointList>& +SVGPointListTearoffTable() { + static SVGAttrTearoffTable<void, DOMSVGPointList> sSVGPointListTearoffTable; + return sSVGPointListTearoffTable; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPointList) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPointList) + // No unlinking of mElement, we'd need to null out the value pointer (the + // object it points to is held by the element) and null-check it everywhere. + tmp->RemoveFromTearoffTable(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPointList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPointList) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPointList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPointList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPointList) + NS_INTERFACE_MAP_ENTRY_CONCRETE(DOMSVGPointList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* static */ +already_AddRefed<DOMSVGPointList> DOMSVGPointList::GetDOMWrapper( + void* aList, SVGElement* aElement, bool aIsAnimValList) { + RefPtr<DOMSVGPointList> wrapper = + SVGPointListTearoffTable().GetTearoff(aList); + if (!wrapper) { + wrapper = new DOMSVGPointList(aElement, aIsAnimValList); + SVGPointListTearoffTable().AddTearoff(aList, wrapper); + } + return wrapper.forget(); +} + +/* static */ +DOMSVGPointList* DOMSVGPointList::GetDOMWrapperIfExists(void* aList) { + return SVGPointListTearoffTable().GetTearoff(aList); +} + +void DOMSVGPointList::RemoveFromTearoffTable() { + // Called from Unlink and the destructor. + // + // There are now no longer any references to us held by script or list items. + // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()! + void* key = mIsAnimValList ? InternalAList().GetAnimValKey() + : InternalAList().GetBaseValKey(); + SVGPointListTearoffTable().RemoveTearoff(key); +} + +DOMSVGPointList::~DOMSVGPointList() { RemoveFromTearoffTable(); } + +JSObject* DOMSVGPointList::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return mozilla::dom::SVGPointList_Binding::Wrap(cx, this, aGivenProto); +} + +void DOMSVGPointList::InternalListWillChangeTo(const SVGPointList& aNewValue) { + // When the number of items in our internal counterpart changes, we MUST stay + // in sync. Everything in the scary comment in + // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here too! + + uint32_t oldLength = mItems.Length(); + + uint32_t newLength = aNewValue.Length(); + if (newLength > DOMSVGPoint::MaxListIndex()) { + // It's safe to get out of sync with our internal list as long as we have + // FEWER items than it does. + newLength = DOMSVGPoint::MaxListIndex(); + } + + RefPtr<DOMSVGPointList> kungFuDeathGrip; + if (newLength < oldLength) { + // RemovingFromList() might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + + // If our length will decrease, notify the items that will be removed: + for (uint32_t i = newLength; i < oldLength; ++i) { + if (mItems[i]) { + mItems[i]->RemovingFromList(); + } + } + + if (!mItems.SetLength(newLength, fallible)) { + // We silently ignore SetLength OOM failure since being out of sync is safe + // so long as we have *fewer* items than our internal list. + mItems.Clear(); + return; + } + + // If our length has increased, null out the new pointers: + for (uint32_t i = oldLength; i < newLength; ++i) { + mItems[i] = nullptr; + } +} + +bool DOMSVGPointList::AttrIsAnimating() const { + return InternalAList().IsAnimating(); +} + +bool DOMSVGPointList::AnimListMirrorsBaseList() const { + return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) && + !AttrIsAnimating(); +} + +SVGPointList& DOMSVGPointList::InternalList() const { + SVGAnimatedPointList* alist = mElement->GetAnimatedPointList(); + return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal + : alist->mBaseVal; +} + +SVGAnimatedPointList& DOMSVGPointList::InternalAList() const { + MOZ_ASSERT(mElement->GetAnimatedPointList(), "Internal error"); + return *mElement->GetAnimatedPointList(); +} + +// ---------------------------------------------------------------------------- +// nsIDOMSVGPointList implementation: + +void DOMSVGPointList::Clear(ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (LengthNoFlush() > 0) { + AutoChangePointListNotifier notifier(this); + // DOM list items that are to be removed must be removed before we change + // the internal list, otherwise they wouldn't be able to copy their + // internal counterparts' values! + + InternalListWillChangeTo(SVGPointList()); // clears mItems + + if (!AttrIsAnimating()) { + // The anim val list is in sync with the base val list + DOMSVGPointList* animList = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + if (animList) { + animList->InternalListWillChangeTo( + SVGPointList()); // clears its mItems + } + } + + InternalList().Clear(); + } +} + +already_AddRefed<DOMSVGPoint> DOMSVGPointList::Initialize(DOMSVGPoint& aNewItem, + ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + // If aNewItem is already in a list we should insert a clone of aNewItem, + // and for consistency, this should happen even if *this* is the list that + // aNewItem is currently in. Note that in the case of aNewItem being in this + // list, the Clear() call before the InsertItemBefore() call would remove it + // from this list, and so the InsertItemBefore() call would not insert a + // clone of aNewItem, it would actually insert aNewItem. To prevent that + // from happening we have to do the clone here, if necessary. + + RefPtr<DOMSVGPoint> domItem = &aNewItem; + if (domItem->HasOwner()) { + domItem = domItem->Copy(); // must do this before changing anything! + } + + ErrorResult rv; + Clear(rv); + MOZ_ASSERT(!rv.Failed()); + return InsertItemBefore(*domItem, 0, aError); +} + +already_AddRefed<DOMSVGPoint> DOMSVGPointList::GetItem(uint32_t index, + ErrorResult& error) { + bool found; + RefPtr<DOMSVGPoint> item = IndexedGetter(index, found, error); + if (!found) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + } + return item.forget(); +} + +already_AddRefed<DOMSVGPoint> DOMSVGPointList::IndexedGetter( + uint32_t aIndex, bool& aFound, ErrorResult& aError) { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + aFound = aIndex < LengthNoFlush(); + if (aFound) { + return GetItemAt(aIndex); + } + return nullptr; +} + +already_AddRefed<DOMSVGPoint> DOMSVGPointList::InsertItemBefore( + DOMSVGPoint& aNewItem, uint32_t aIndex, ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + aIndex = std::min(aIndex, LengthNoFlush()); + if (aIndex >= DOMSVGPoint::MaxListIndex()) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGPoint> domItem = &aNewItem; + if (domItem->HasOwner()) { + domItem = domItem->Copy(); // must do this before changing anything! + } + + // Ensure we have enough memory so we can avoid complex error handling below: + if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || + !InternalList().SetCapacity(InternalList().Length() + 1)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + if (AnimListMirrorsBaseList()) { + DOMSVGPointList* animVal = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + MOZ_ASSERT(animVal, "animVal must be a valid pointer"); + if (!animVal->mItems.SetCapacity(animVal->mItems.Length() + 1, fallible)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + } + + AutoChangePointListNotifier notifier(this); + // Now that we know we're inserting, keep animVal list in sync as necessary. + MaybeInsertNullInAnimValListAt(aIndex); + + InternalList().InsertItem(aIndex, domItem->ToSVGPoint()); + MOZ_ALWAYS_TRUE(mItems.InsertElementAt(aIndex, domItem, fallible)); + + // This MUST come after the insertion into InternalList(), or else under the + // insertion into InternalList() the values read from domItem would be bad + // data from InternalList() itself!: + domItem->InsertingIntoList(this, aIndex, IsAnimValList()); + + UpdateListIndicesFromIndex(mItems, aIndex + 1); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGPoint> DOMSVGPointList::ReplaceItem( + DOMSVGPoint& aNewItem, uint32_t aIndex, ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (aIndex >= LengthNoFlush()) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGPoint> domItem = &aNewItem; + if (domItem->HasOwner()) { + domItem = domItem->Copy(); // must do this before changing anything! + } + + AutoChangePointListNotifier notifier(this); + if (mItems[aIndex]) { + // Notify any existing DOM item of removal *before* modifying the lists so + // that the DOM item can copy the *old* value at its index: + mItems[aIndex]->RemovingFromList(); + } + + InternalList()[aIndex] = domItem->ToSVGPoint(); + mItems[aIndex] = domItem; + + // This MUST come after the ToSVGPoint() call, otherwise that call + // would end up reading bad data from InternalList()! + domItem->InsertingIntoList(this, aIndex, IsAnimValList()); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGPoint> DOMSVGPointList::RemoveItem(uint32_t aIndex, + ErrorResult& aError) { + if (IsAnimValList()) { + aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (aIndex >= LengthNoFlush()) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + AutoChangePointListNotifier notifier(this); + // Now that we know we're removing, keep animVal list in sync as necessary. + // Do this *before* touching InternalList() so the removed item can get its + // internal value. + MaybeRemoveItemFromAnimValListAt(aIndex); + + // We have to return the removed item, so get it, creating it if necessary: + RefPtr<DOMSVGPoint> result = GetItemAt(aIndex); + + // Notify the DOM item of removal *before* modifying the lists so that the + // DOM item can copy its *old* value: + mItems[aIndex]->RemovingFromList(); + + InternalList().RemoveItem(aIndex); + mItems.RemoveElementAt(aIndex); + + UpdateListIndicesFromIndex(mItems, aIndex); + + return result.forget(); +} + +already_AddRefed<DOMSVGPoint> DOMSVGPointList::GetItemAt(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length()); + + if (!mItems[aIndex]) { + mItems[aIndex] = new DOMSVGPoint(this, aIndex, IsAnimValList()); + } + RefPtr<DOMSVGPoint> result = mItems[aIndex]; + return result.forget(); +} + +void DOMSVGPointList::MaybeInsertNullInAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + // The anim val list is in sync with the base val list + DOMSVGPointList* animVal = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible)); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1); +} + +void DOMSVGPointList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + // This needs to be a strong reference; otherwise, the RemovingFromList call + // below might drop the last reference to animVal before we're done with it. + RefPtr<DOMSVGPointList> animVal = + GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + + if (animVal->mItems[aIndex]) { + animVal->mItems[aIndex]->RemovingFromList(); + } + animVal->mItems.RemoveElementAt(aIndex); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGPointList.h b/dom/svg/DOMSVGPointList.h new file mode 100644 index 0000000000..36c0d653ef --- /dev/null +++ b/dom/svg/DOMSVGPointList.h @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGPOINTLIST_H_ +#define DOM_SVG_DOMSVGPOINTLIST_H_ + +#include "mozAutoDocUpdate.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsTArray.h" +#include "SVGPointList.h" // IWYU pragma: keep +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +// {61812ad1-c078-4cd1-87e6-bc1c1b8d7284} +#define MOZILLA_DOMSVGPOINTLIST_IID \ + { \ + 0x61812ad1, 0xc078, 0x4cd1, { \ + 0x87, 0xe6, 0xbc, 0x1c, 0x1b, 0x8d, 0x72, 0x84 \ + } \ + } + +namespace mozilla { + +class ErrorResult; +class SVGAnimatedPointList; + +namespace dom { + +class DOMSVGPoint; +class SVGElement; + +//---------------------------------------------------------------------- +// Helper class: AutoChangePointListNotifier +// Stack-based helper class to pair calls to WillChangePointList and +// DidChangePointList. Used by DOMSVGPoint and DOMSVGPointList. +template <class T> +class MOZ_RAII AutoChangePointListNotifier { + public: + explicit AutoChangePointListNotifier(T* aValue) : mValue(aValue) { + MOZ_ASSERT(mValue, "Expecting non-null value"); + if (mValue->IsInList()) { + mUpdateBatch.emplace(mValue->Element()->GetComposedDoc(), true); + mEmptyOrOldValue = + mValue->Element()->WillChangePointList(mUpdateBatch.ref()); + } + } + + ~AutoChangePointListNotifier() { + if (mValue->IsInList()) { + mValue->Element()->DidChangePointList(mEmptyOrOldValue, + mUpdateBatch.ref()); + if (mValue->AttrIsAnimating()) { + mValue->Element()->AnimationNeedsResample(); + } + } + } + + private: + Maybe<mozAutoDocUpdate> mUpdateBatch; + T* const mValue; + nsAttrValue mEmptyOrOldValue; +}; + +/** + * Class DOMSVGPointList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGPointList objects. + * + * See the architecture comment in DOMSVGAnimatedLengthList.h first (that's + * LENGTH list), then continue reading the remainder of this comment. + * + * The architecture of this class is very similar to that of DOMSVGLengthList + * except that, since there is no nsIDOMSVGAnimatedPointList interface + * in SVG, we have no parent DOMSVGAnimatedPointList (unlike DOMSVGLengthList + * which has a parent DOMSVGAnimatedLengthList class). (There is an + * SVGAnimatedPoints interface, but that is quite different to + * DOMSVGAnimatedLengthList, since it is inherited by elements rather than + * elements having members of that type.) As a consequence, much of the logic + * that would otherwise be in DOMSVGAnimatedPointList (and is in + * DOMSVGAnimatedLengthList) is contained in this class. + * + * This class is strongly intertwined with DOMSVGPoint. Our DOMSVGPoint + * items are friends of us and responsible for nulling out our pointers to + * them when they die. + * + * Our DOM items are created lazily on demand as and when script requests them. + */ +class DOMSVGPointList final : public nsISupports, public nsWrapperCache { + template <class T> + friend class AutoChangePointListNotifier; + friend class DOMSVGPoint; + + public: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOMSVGPOINTLIST_IID) + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGPointList) + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + nsISupports* GetParentObject() { return static_cast<nsIContent*>(mElement); } + + /** + * Factory method to create and return a DOMSVGPointList wrapper + * for a given internal SVGPointList object. The factory takes care + * of caching the object that it returns so that the same object can be + * returned for the given SVGPointList each time it is requested. + * The cached object is only removed from the cache when it is destroyed due + * to there being no more references to it or to any of its descendant + * objects. If that happens, any subsequent call requesting the DOM wrapper + * for the SVGPointList will naturally result in a new + * DOMSVGPointList being returned. + * + * It's unfortunate that aList is a void* instead of a typed argument. This + * is because the mBaseVal and mAnimVal members of SVGAnimatedPointList are + * of different types - a plain SVGPointList, and a SVGPointList*. We + * use the addresses of these members as the key for the hash table, and + * clearly SVGPointList* and a SVGPointList** are not the same type. + */ + static already_AddRefed<DOMSVGPointList> GetDOMWrapper( + void* aList, dom::SVGElement* aElement, bool aIsAnimValList); + + /** + * This method returns the DOMSVGPointList wrapper for an internal + * SVGPointList object if it currently has a wrapper. If it does + * not, then nullptr is returned. + */ + static DOMSVGPointList* GetDOMWrapperIfExists(void* aList); + + /** + * This will normally be the same as InternalList().Length(), except if + * we've hit OOM, in which case our length will be zero. + */ + uint32_t LengthNoFlush() const { + MOZ_ASSERT( + mItems.Length() == 0 || mItems.Length() == InternalList().Length(), + "DOM wrapper's list length is out of sync"); + return mItems.Length(); + } + + /** + * WATCH OUT! If you add code to call this on a baseVal wrapper, then you + * must also call it on the animVal wrapper too if necessary!! See other + * callers! + * + * Called by internal code to notify us when we need to sync the length of + * this DOM list with its internal list. This is called immediately prior to + * the length of the internal list being changed so that any DOM list items + * that need to be removed from the DOM list can first copy their values from + * their internal counterpart. + * + * The only time this method could fail is on OOM when trying to increase the + * length of the DOM list. If that happens then this method simply clears the + * list and returns. Callers just proceed as normal, and we simply accept + * that the DOM list will be empty (until successfully set to a new value). + */ + void InternalListWillChangeTo(const SVGPointList& aNewValue); + + /* + * We need this so that templates that work on lists and elements can check + * ownership where elements may be not be in a list. + */ + bool IsInList() const { return true; } + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool AttrIsAnimating() const; + + /** + * Returns true if there is an animated list mirroring the base list. + */ + bool AnimListMirrorsBaseList() const; + + uint32_t NumberOfItems() const { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + return LengthNoFlush(); + } + void Clear(ErrorResult& aError); + already_AddRefed<DOMSVGPoint> Initialize(DOMSVGPoint& aNewItem, + ErrorResult& aError); + already_AddRefed<DOMSVGPoint> GetItem(uint32_t index, ErrorResult& error); + already_AddRefed<DOMSVGPoint> IndexedGetter(uint32_t index, bool& found, + ErrorResult& error); + already_AddRefed<DOMSVGPoint> InsertItemBefore(DOMSVGPoint& aNewItem, + uint32_t aIndex, + ErrorResult& aError); + already_AddRefed<DOMSVGPoint> ReplaceItem(DOMSVGPoint& aNewItem, + uint32_t aIndex, + ErrorResult& aError); + already_AddRefed<DOMSVGPoint> RemoveItem(uint32_t aIndex, + ErrorResult& aError); + already_AddRefed<DOMSVGPoint> AppendItem(DOMSVGPoint& aNewItem, + ErrorResult& aError) { + return InsertItemBefore(aNewItem, LengthNoFlush(), aError); + } + uint32_t Length() const { return NumberOfItems(); } + + private: + /** + * Only our static GetDOMWrapper() factory method may create objects of our + * type. + */ + DOMSVGPointList(dom::SVGElement* aElement, bool aIsAnimValList) + : mElement(aElement), mIsAnimValList(aIsAnimValList) { + InternalListWillChangeTo(InternalList()); // Sync mItems + } + + ~DOMSVGPointList(); + + dom::SVGElement* Element() const { return mElement.get(); } + + /// Used to determine if this list is the baseVal or animVal list. + bool IsAnimValList() const { return mIsAnimValList; } + + /** + * Get a reference to this object's corresponding internal SVGPointList. + * + * To simplify the code we just have this one method for obtaining both + * base val and anim val internal lists. This means that anim val lists don't + * get const protection, but our setter methods guard against changing + * anim val lists. + */ + SVGPointList& InternalList() const; + + SVGAnimatedPointList& InternalAList() const; + + /// Returns the DOMSVGPoint at aIndex, creating it if necessary. + already_AddRefed<DOMSVGPoint> GetItemAt(uint32_t aIndex); + + void MaybeInsertNullInAnimValListAt(uint32_t aIndex); + void MaybeRemoveItemFromAnimValListAt(uint32_t aIndex); + + void RemoveFromTearoffTable(); + + // Weak refs to our DOMSVGPoint items. The items are friends and take care + // of clearing our pointer to them when they die. + FallibleTArray<DOMSVGPoint*> mItems; + + // Strong ref to our element to keep it alive. We hold this not only for + // ourself, but also for our DOMSVGPoint items too. + RefPtr<dom::SVGElement> mElement; + + bool mIsAnimValList; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DOMSVGPointList, MOZILLA_DOMSVGPOINTLIST_IID) + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGPOINTLIST_H_ diff --git a/dom/svg/DOMSVGStringList.cpp b/dom/svg/DOMSVGStringList.cpp new file mode 100644 index 0000000000..4f5c72ea17 --- /dev/null +++ b/dom/svg/DOMSVGStringList.cpp @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGStringList.h" + +#include "mozAutoDocUpdate.h" +#include "mozilla/dom/SVGStringListBinding.h" +#include "mozilla/dom/SVGTests.h" +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsQueryObject.h" +#include "SVGAttrTearoffTable.h" +#include <algorithm> + +// See the architecture comment in this file's header. + +namespace mozilla::dom { + +static inline SVGAttrTearoffTable<SVGStringList, DOMSVGStringList>& +SVGStringListTearoffTable() { + static SVGAttrTearoffTable<SVGStringList, DOMSVGStringList> + sSVGStringListTearoffTable; + return sSVGStringListTearoffTable; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGStringList) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGStringList) + // No unlinking of mElement, we'd need to null out the value pointer (the + // object it points to is held by the element) and null-check it everywhere. + tmp->RemoveFromTearoffTable(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGStringList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGStringList) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGStringList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGStringList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGStringList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +//---------------------------------------------------------------------- +// Helper class: AutoChangeStringListNotifier +// Stack-based helper class to pair calls to WillChangeStringListList and +// DidChangeStringListList. +class MOZ_RAII AutoChangeStringListNotifier : public mozAutoDocUpdate { + public: + explicit AutoChangeStringListNotifier(DOMSVGStringList* aStringList) + : mozAutoDocUpdate(aStringList->mElement->GetComposedDoc(), true), + mStringList(aStringList) { + MOZ_ASSERT(mStringList, "Expecting non-null stringList"); + mEmptyOrOldValue = mStringList->mElement->WillChangeStringList( + mStringList->mIsConditionalProcessingAttribute, mStringList->mAttrEnum, + *this); + } + + ~AutoChangeStringListNotifier() { + mStringList->mElement->DidChangeStringList( + mStringList->mIsConditionalProcessingAttribute, mStringList->mAttrEnum, + mEmptyOrOldValue, *this); + } + + private: + DOMSVGStringList* const mStringList; + nsAttrValue mEmptyOrOldValue; +}; + +/* static */ +already_AddRefed<DOMSVGStringList> DOMSVGStringList::GetDOMWrapper( + SVGStringList* aList, SVGElement* aElement, + bool aIsConditionalProcessingAttribute, uint8_t aAttrEnum) { + RefPtr<DOMSVGStringList> wrapper = + SVGStringListTearoffTable().GetTearoff(aList); + if (!wrapper) { + wrapper = new DOMSVGStringList(aElement, aIsConditionalProcessingAttribute, + aAttrEnum); + SVGStringListTearoffTable().AddTearoff(aList, wrapper); + } + return wrapper.forget(); +} + +void DOMSVGStringList::RemoveFromTearoffTable() { + // Script no longer has any references to us. + SVGStringListTearoffTable().RemoveTearoff(&InternalList()); +} + +DOMSVGStringList::~DOMSVGStringList() { RemoveFromTearoffTable(); } + +/* virtual */ +JSObject* DOMSVGStringList::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGStringList_Binding::Wrap(aCx, this, aGivenProto); +} + +// ---------------------------------------------------------------------------- +// SVGStringList implementation: + +uint32_t DOMSVGStringList::NumberOfItems() const { + return InternalList().Length(); +} + +uint32_t DOMSVGStringList::Length() const { return NumberOfItems(); } + +void DOMSVGStringList::Clear() { + if (InternalList().IsExplicitlySet()) { + AutoChangeStringListNotifier notifier(this); + InternalList().Clear(); + } +} + +void DOMSVGStringList::Initialize(const nsAString& aNewItem, nsAString& aRetval, + ErrorResult& aRv) { + if (InternalList().IsExplicitlySet()) { + InternalList().Clear(); + } + InsertItemBefore(aNewItem, 0, aRetval, aRv); +} + +void DOMSVGStringList::GetItem(uint32_t aIndex, nsAString& aRetval, + ErrorResult& aRv) { + bool found; + IndexedGetter(aIndex, found, aRetval); + if (!found) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + } +} + +void DOMSVGStringList::IndexedGetter(uint32_t aIndex, bool& aFound, + nsAString& aRetval) { + aFound = aIndex < InternalList().Length(); + if (aFound) { + aRetval = InternalList()[aIndex]; + } +} + +void DOMSVGStringList::InsertItemBefore(const nsAString& aNewItem, + uint32_t aIndex, nsAString& aRetval, + ErrorResult& aRv) { + if (aNewItem.IsEmpty()) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + aIndex = std::min(aIndex, InternalList().Length()); + + // Ensure we have enough memory so we can avoid complex error handling below: + if (!InternalList().SetCapacity(InternalList().Length() + 1)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + AutoChangeStringListNotifier notifier(this); + InternalList().InsertItem(aIndex, aNewItem); + aRetval = aNewItem; +} + +void DOMSVGStringList::ReplaceItem(const nsAString& aNewItem, uint32_t aIndex, + nsAString& aRetval, ErrorResult& aRv) { + if (aNewItem.IsEmpty()) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + if (aIndex >= InternalList().Length()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + aRetval = InternalList()[aIndex]; + AutoChangeStringListNotifier notifier(this); + InternalList().ReplaceItem(aIndex, aNewItem); +} + +void DOMSVGStringList::RemoveItem(uint32_t aIndex, nsAString& aRetval, + ErrorResult& aRv) { + if (aIndex >= InternalList().Length()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + AutoChangeStringListNotifier notifier(this); + InternalList().RemoveItem(aIndex); +} + +void DOMSVGStringList::AppendItem(const nsAString& aNewItem, nsAString& aRetval, + ErrorResult& aRv) { + InsertItemBefore(aNewItem, InternalList().Length(), aRetval, aRv); +} + +SVGStringList& DOMSVGStringList::InternalList() const { + if (mIsConditionalProcessingAttribute) { + nsCOMPtr<dom::SVGTests> tests = do_QueryObject(mElement); + return tests->mStringListAttributes[mAttrEnum]; + } + return mElement->GetStringListInfo().mValues[mAttrEnum]; +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGStringList.h b/dom/svg/DOMSVGStringList.h new file mode 100644 index 0000000000..5c05294d47 --- /dev/null +++ b/dom/svg/DOMSVGStringList.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGSTRINGLIST_H_ +#define DOM_SVG_DOMSVGSTRINGLIST_H_ + +#include "nsCycleCollectionParticipant.h" +#include "SVGElement.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class ErrorResult; +class SVGStringList; + +namespace dom { + +/** + * Class DOMSVGStringList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGPathData objects. + * + * See the architecture comment in DOMSVGAnimatedLengthList.h first (that's + * LENGTH list), then continue reading the remainder of this comment. + * + * The architecture of this class is similar to that of DOMSVGLengthList + * except for two important aspects: + * + * First, since there is no nsIDOMSVGAnimatedStringList interface in SVG, we + * have no parent DOMSVGAnimatedStringList (unlike DOMSVGLengthList which has + * a parent DOMSVGAnimatedLengthList class). As a consequence, much of the + * logic that would otherwise be in DOMSVGAnimatedStringList (and is in + * DOMSVGAnimatedLengthList) is contained in this class. + * + * Second, since there is no nsIDOMSVGString interface in SVG, we have no + * DOMSVGString items to maintain. As far as script is concerned, objects + * of this class contain a list of strings, not a list of mutable objects + * like the other SVG list types. As a result, unlike the other SVG list + * types, this class does not create its items lazily on demand and store + * them so it can return the same objects each time. It simply returns a new + * string each time any given item is requested. + */ +class DOMSVGStringList final : public nsISupports, public nsWrapperCache { + friend class AutoChangeStringListNotifier; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGStringList) + + dom::SVGElement* GetParentObject() const { return mElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + uint32_t NumberOfItems() const; + uint32_t Length() const; + void Clear(); + void Initialize(const nsAString& aNewItem, nsAString& aRetval, + ErrorResult& aRv); + void GetItem(uint32_t aIndex, nsAString& aRetval, ErrorResult& aRv); + void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aRetval); + void InsertItemBefore(const nsAString& aNewItem, uint32_t aIndex, + nsAString& aRetval, ErrorResult& aRv); + void ReplaceItem(const nsAString& aNewItem, uint32_t aIndex, + nsAString& aRetval, ErrorResult& aRv); + void RemoveItem(uint32_t aIndex, nsAString& aRetval, ErrorResult& aRv); + void AppendItem(const nsAString& aNewItem, nsAString& aRetval, + ErrorResult& aRv); + + /** + * Factory method to create and return a DOMSVGStringList wrapper + * for a given internal SVGStringList object. The factory takes care + * of caching the object that it returns so that the same object can be + * returned for the given SVGStringList each time it is requested. + * The cached object is only removed from the cache when it is destroyed due + * to there being no more references to it. If that happens, any subsequent + * call requesting the DOM wrapper for the SVGStringList will naturally + * result in a new DOMSVGStringList being returned. + */ + static already_AddRefed<DOMSVGStringList> GetDOMWrapper( + SVGStringList* aList, dom::SVGElement* aElement, + bool aIsConditionalProcessingAttribute, uint8_t aAttrEnum); + + private: + /** + * Only our static GetDOMWrapper() factory method may create objects of our + * type. + */ + DOMSVGStringList(dom::SVGElement* aElement, + bool aIsConditionalProcessingAttribute, uint8_t aAttrEnum) + : mElement(aElement), + mAttrEnum(aAttrEnum), + mIsConditionalProcessingAttribute(aIsConditionalProcessingAttribute) {} + + ~DOMSVGStringList(); + + SVGStringList& InternalList() const; + + void RemoveFromTearoffTable(); + + // Strong ref to our element to keep it alive. + RefPtr<dom::SVGElement> mElement; + + uint8_t mAttrEnum; + + bool mIsConditionalProcessingAttribute; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGSTRINGLIST_H_ diff --git a/dom/svg/DOMSVGTransform.cpp b/dom/svg/DOMSVGTransform.cpp new file mode 100644 index 0000000000..bc96edeb8d --- /dev/null +++ b/dom/svg/DOMSVGTransform.cpp @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGTransform.h" + +#include "mozAutoDocUpdate.h" +#include "mozilla/dom/DOMMatrix.h" +#include "mozilla/dom/DOMMatrixBinding.h" +#include "mozilla/dom/SVGMatrix.h" +#include "mozilla/dom/SVGTransformBinding.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Maybe.h" +#include "nsError.h" +#include "SVGAnimatedTransformList.h" +#include "SVGAttrTearoffTable.h" + +namespace { +const double kRadPerDegree = 2.0 * M_PI / 360.0; +} // namespace + +namespace mozilla::dom { + +using namespace SVGTransform_Binding; + +static SVGAttrTearoffTable<DOMSVGTransform, SVGMatrix>& +SVGMatrixTearoffTable() { + static SVGAttrTearoffTable<DOMSVGTransform, SVGMatrix> sSVGMatrixTearoffTable; + return sSVGMatrixTearoffTable; +} + +//---------------------------------------------------------------------- + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our list's weak ref to us to be safe. (The other option would be to +// not unlink and rely on the breaking of the other edges in the cycle, as +// NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGTransform) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGTransform) + // We may not belong to a list, so we must null check tmp->mList. + if (tmp->mList) { + tmp->mList->mItems[tmp->mListIndex] = nullptr; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mList) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGTransform) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList) + SVGMatrix* matrix = SVGMatrixTearoffTable().GetTearoff(tmp); + CycleCollectionNoteChild(cb, matrix, "matrix"); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGTransform) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +JSObject* DOMSVGTransform::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGTransform_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Ctors: + +DOMSVGTransform::DOMSVGTransform(DOMSVGTransformList* aList, + uint32_t aListIndex, bool aIsAnimValItem) + : mList(aList), + mListIndex(aListIndex), + mIsAnimValItem(aIsAnimValItem), + mTransform(nullptr) { + // These shifts are in sync with the members in the header. + MOZ_ASSERT(aList && aListIndex <= MaxListIndex(), "bad arg"); + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!"); +} + +DOMSVGTransform::DOMSVGTransform() + : mList(nullptr), + mListIndex(0), + mIsAnimValItem(false), + mTransform(new SVGTransform()) // Default ctor for objects not in a + // list initialises to matrix type with + // identity matrix +{} + +DOMSVGTransform::DOMSVGTransform(const gfxMatrix& aMatrix) + : mList(nullptr), + mListIndex(0), + mIsAnimValItem(false), + mTransform(new SVGTransform(aMatrix)) {} + +DOMSVGTransform::DOMSVGTransform(const DOMMatrix2DInit& aMatrix, + ErrorResult& rv) + : mList(nullptr), + mListIndex(0), + mIsAnimValItem(false), + mTransform(new SVGTransform()) { + SetMatrix(aMatrix, rv); +} + +DOMSVGTransform::DOMSVGTransform(const SVGTransform& aTransform) + : mList(nullptr), + mListIndex(0), + mIsAnimValItem(false), + mTransform(new SVGTransform(aTransform)) {} + +DOMSVGTransform::~DOMSVGTransform() { + SVGMatrix* matrix = SVGMatrixTearoffTable().GetTearoff(this); + if (matrix) { + SVGMatrixTearoffTable().RemoveTearoff(this); + NS_RELEASE(matrix); + } + // Our mList's weak ref to us must be nulled out when we die. If GC has + // unlinked us using the cycle collector code, then that has already + // happened, and mList is null. + if (mList) { + mList->mItems[mListIndex] = nullptr; + } +} + +uint16_t DOMSVGTransform::Type() const { return Transform().Type(); } + +SVGMatrix* DOMSVGTransform::GetMatrix() { + SVGMatrix* wrapper = SVGMatrixTearoffTable().GetTearoff(this); + if (!wrapper) { + NS_ADDREF(wrapper = new SVGMatrix(*this)); + SVGMatrixTearoffTable().AddTearoff(this, wrapper); + } + return wrapper; +} + +float DOMSVGTransform::Angle() const { return Transform().Angle(); } + +void DOMSVGTransform::SetMatrix(const DOMMatrix2DInit& aMatrix, + ErrorResult& aRv) { + if (mIsAnimValItem) { + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + RefPtr<DOMMatrixReadOnly> matrix = + DOMMatrixReadOnly::FromMatrix(GetParentObject(), aMatrix, aRv); + if (aRv.Failed()) { + return; + } + const gfxMatrix* matrix2D = matrix->GetInternal2D(); + if (!matrix2D->IsFinite()) { + aRv.ThrowTypeError<MSG_NOT_FINITE>("Matrix setter"); + return; + } + SetMatrix(*matrix2D); +} + +void DOMSVGTransform::SetTranslate(float tx, float ty, ErrorResult& rv) { + if (mIsAnimValItem) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (Transform().Type() == SVG_TRANSFORM_TRANSLATE && Matrixgfx()._31 == tx && + Matrixgfx()._32 == ty) { + return; + } + + AutoChangeTransformListNotifier notifier(this); + Transform().SetTranslate(tx, ty); +} + +void DOMSVGTransform::SetScale(float sx, float sy, ErrorResult& rv) { + if (mIsAnimValItem) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (Transform().Type() == SVG_TRANSFORM_SCALE && Matrixgfx()._11 == sx && + Matrixgfx()._22 == sy) { + return; + } + AutoChangeTransformListNotifier notifier(this); + Transform().SetScale(sx, sy); +} + +void DOMSVGTransform::SetRotate(float angle, float cx, float cy, + ErrorResult& rv) { + if (mIsAnimValItem) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (Transform().Type() == SVG_TRANSFORM_ROTATE) { + float currentCx, currentCy; + Transform().GetRotationOrigin(currentCx, currentCy); + if (Transform().Angle() == angle && currentCx == cx && currentCy == cy) { + return; + } + } + + AutoChangeTransformListNotifier notifier(this); + Transform().SetRotate(angle, cx, cy); +} + +void DOMSVGTransform::SetSkewX(float angle, ErrorResult& rv) { + if (mIsAnimValItem) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (Transform().Type() == SVG_TRANSFORM_SKEWX && + Transform().Angle() == angle) { + return; + } + + if (!IsFinite(tan(angle * kRadPerDegree))) { + rv.ThrowRangeError<MSG_INVALID_TRANSFORM_ANGLE_ERROR>(); + return; + } + + AutoChangeTransformListNotifier notifier(this); + DebugOnly<nsresult> result = Transform().SetSkewX(angle); + MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewX unexpectedly failed"); +} + +void DOMSVGTransform::SetSkewY(float angle, ErrorResult& rv) { + if (mIsAnimValItem) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (Transform().Type() == SVG_TRANSFORM_SKEWY && + Transform().Angle() == angle) { + return; + } + + if (!IsFinite(tan(angle * kRadPerDegree))) { + rv.ThrowRangeError<MSG_INVALID_TRANSFORM_ANGLE_ERROR>(); + return; + } + + AutoChangeTransformListNotifier notifier(this); + DebugOnly<nsresult> result = Transform().SetSkewY(angle); + MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewY unexpectedly failed"); +} + +//---------------------------------------------------------------------- +// List management methods: + +void DOMSVGTransform::InsertingIntoList(DOMSVGTransformList* aList, + uint32_t aListIndex, + bool aIsAnimValItem) { + MOZ_ASSERT(!HasOwner(), "Inserting item that is already in a list"); + + mList = aList; + mListIndex = aListIndex; + mIsAnimValItem = aIsAnimValItem; + mTransform = nullptr; + + MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGLength!"); +} + +void DOMSVGTransform::RemovingFromList() { + MOZ_ASSERT(!mTransform, + "Item in list also has another non-list value associated with it"); + + mTransform = MakeUnique<SVGTransform>(InternalItem()); + mList = nullptr; + mIsAnimValItem = false; +} + +SVGTransform& DOMSVGTransform::InternalItem() { + SVGAnimatedTransformList* alist = Element()->GetAnimatedTransformList(); + return mIsAnimValItem && alist->mAnimVal ? (*alist->mAnimVal)[mListIndex] + : alist->mBaseVal[mListIndex]; +} + +const SVGTransform& DOMSVGTransform::InternalItem() const { + return const_cast<DOMSVGTransform*>(this)->InternalItem(); +} + +#ifdef DEBUG +bool DOMSVGTransform::IndexIsValid() { + SVGAnimatedTransformList* alist = Element()->GetAnimatedTransformList(); + return (mIsAnimValItem && mListIndex < alist->GetAnimValue().Length()) || + (!mIsAnimValItem && mListIndex < alist->GetBaseValue().Length()); +} +#endif // DEBUG + +//---------------------------------------------------------------------- +// Interface for SVGMatrix's use + +void DOMSVGTransform::SetMatrix(const gfxMatrix& aMatrix) { + MOZ_ASSERT(!mIsAnimValItem, "Attempting to modify read-only transform"); + + if (Transform().Type() == SVG_TRANSFORM_MATRIX && + SVGTransform::MatricesEqual(Matrixgfx(), aMatrix)) { + return; + } + + AutoChangeTransformListNotifier notifier(this); + Transform().SetMatrix(aMatrix); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGTransform.h b/dom/svg/DOMSVGTransform.h new file mode 100644 index 0000000000..ceee17ff1f --- /dev/null +++ b/dom/svg/DOMSVGTransform.h @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGTRANSFORM_H_ +#define DOM_SVG_DOMSVGTRANSFORM_H_ + +#include "DOMSVGTransformList.h" +#include "gfxMatrix.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsID.h" +#include "SVGTransform.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" + +#define MOZ_SVG_LIST_INDEX_BIT_COUNT 31 // supports > 2 billion list items + +namespace mozilla::dom { + +struct DOMMatrix2DInit; +class SVGElement; +class SVGMatrix; + +/** + * DOM wrapper for an SVG transform. See DOMSVGLength.h. + */ +class DOMSVGTransform final : public nsWrapperCache { + template <class T> + friend class AutoChangeTransformListNotifier; + + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGTransform) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGTransform) + + /** + * Generic ctor for DOMSVGTransform objects that are created for an attribute. + */ + DOMSVGTransform(DOMSVGTransformList* aList, uint32_t aListIndex, + bool aIsAnimValItem); + + /** + * Ctors for creating the objects returned by: + * SVGSVGElement.createSVGTransform(), + * SVGSVGElement.createSVGTransformFromMatrix(in DOMMatrix2DInit matrix), + * SVGTransformList.createSVGTransformFromMatrix(in DOMMatrix2DInit matrix) + * which do not initially belong to an attribute. + */ + DOMSVGTransform(); + explicit DOMSVGTransform(const gfxMatrix& aMatrix); + DOMSVGTransform(const DOMMatrix2DInit& aMatrix, ErrorResult& rv); + + /** + * Ctor for creating an unowned copy. Used with Clone(). + */ + explicit DOMSVGTransform(const SVGTransform& aTransform); + + /** + * Create an unowned copy of an owned transform. The caller is responsible for + * the first AddRef(). + */ + DOMSVGTransform* Clone() { + NS_ASSERTION(mList, "unexpected caller"); + return new DOMSVGTransform(InternalItem()); + } + + bool IsInList() const { return !!mList; } + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool IsAnimating() const { return mList && mList->IsAnimating(); } + + /** + * In future, if this class is used for non-list transforms, this will be + * different to IsInList(). + */ + bool HasOwner() const { return !!mList; } + + /** + * This method is called to notify this DOM object that it is being inserted + * into a list, and give it the information it needs as a result. + * + * This object MUST NOT already belong to a list when this method is called. + * That's not to say that script can't move these DOM objects between + * lists - it can - it's just that the logic to handle that (and send out + * the necessary notifications) is located elsewhere (in + * DOMSVGTransformList).) + */ + void InsertingIntoList(DOMSVGTransformList* aList, uint32_t aListIndex, + bool aIsAnimValItem); + + static uint32_t MaxListIndex() { + return (1U << MOZ_SVG_LIST_INDEX_BIT_COUNT) - 1; + } + + /// This method is called to notify this object that its list index changed. + void UpdateListIndex(uint32_t aListIndex) { mListIndex = aListIndex; } + + /** + * This method is called to notify this DOM object that it is about to be + * removed from its current DOM list so that it can first make a copy of its + * internal counterpart's values. (If it didn't do this, then it would + * "lose" its value on being removed.) + */ + void RemovingFromList(); + + SVGTransform ToSVGTransform() const { return Transform(); } + + // WebIDL + DOMSVGTransformList* GetParentObject() const { return mList; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + uint16_t Type() const; + dom::SVGMatrix* GetMatrix(); + float Angle() const; + void SetMatrix(const DOMMatrix2DInit& matrix, ErrorResult& rv); + void SetTranslate(float tx, float ty, ErrorResult& rv); + void SetScale(float sx, float sy, ErrorResult& rv); + void SetRotate(float angle, float cx, float cy, ErrorResult& rv); + void SetSkewX(float angle, ErrorResult& rv); + void SetSkewY(float angle, ErrorResult& rv); + + protected: + ~DOMSVGTransform(); + + // Interface for SVGMatrix's use + friend class dom::SVGMatrix; + bool IsAnimVal() const { return mIsAnimValItem; } + const gfxMatrix& Matrixgfx() const { return Transform().GetMatrix(); } + void SetMatrix(const gfxMatrix& aMatrix); + + private: + SVGElement* Element() { return mList->Element(); } + + /** + * Get a reference to the internal SVGTransform list item that this DOM + * wrapper object currently wraps. + */ + SVGTransform& InternalItem(); + const SVGTransform& InternalItem() const; + +#ifdef DEBUG + bool IndexIsValid(); +#endif + + const SVGTransform& Transform() const { + return HasOwner() ? InternalItem() : *mTransform; + } + SVGTransform& Transform() { + return HasOwner() ? InternalItem() : *mTransform; + } + + RefPtr<DOMSVGTransformList> mList; + + // Bounds for the following are checked in the ctor, so be sure to update + // that if you change the capacity of any of the following. + + uint32_t mListIndex : MOZ_SVG_LIST_INDEX_BIT_COUNT; + uint32_t mIsAnimValItem : 1; + + // Usually this class acts as a wrapper for an SVGTransform object which is + // part of a list and is accessed by going via the owning Element. + // + // However, in some circumstances, objects of this class may not be associated + // with any particular list and thus, no internal SVGTransform object. In + // that case we allocate an SVGTransform object on the heap to store the + // data. + UniquePtr<SVGTransform> mTransform; +}; + +} // namespace mozilla::dom + +#undef MOZ_SVG_LIST_INDEX_BIT_COUNT + +#endif // DOM_SVG_DOMSVGTRANSFORM_H_ diff --git a/dom/svg/DOMSVGTransformList.cpp b/dom/svg/DOMSVGTransformList.cpp new file mode 100644 index 0000000000..1943e33013 --- /dev/null +++ b/dom/svg/DOMSVGTransformList.cpp @@ -0,0 +1,389 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGTransformList.h" + +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGMatrix.h" +#include "mozilla/dom/SVGTransformListBinding.h" +#include "DOMSVGTransform.h" +#include "SVGAnimatedTransformList.h" +#include "nsError.h" +#include <algorithm> + +// local helper functions +namespace { + +void UpdateListIndicesFromIndex( + FallibleTArray<mozilla::dom::DOMSVGTransform*>& aItemsArray, + uint32_t aStartingIndex) { + uint32_t length = aItemsArray.Length(); + + for (uint32_t i = aStartingIndex; i < length; ++i) { + if (aItemsArray[i]) { + aItemsArray[i]->UpdateListIndex(i); + } + } +} + +} // namespace + +namespace mozilla::dom { + +// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to +// clear our SVGAnimatedTransformList's weak ref to us to be safe. (The other +// option would be to not unlink and rely on the breaking of the other edges in +// the cycle, as NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGTransformList) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGTransformList) + if (tmp->mAList) { + if (tmp->IsAnimValList()) { + tmp->mAList->mAnimVal = nullptr; + } else { + tmp->mAList->mBaseVal = nullptr; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAList) + } + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGTransformList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGTransformList) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGTransformList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGTransformList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGTransformList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +//---------------------------------------------------------------------- +// DOMSVGTransformList methods: + +JSObject* DOMSVGTransformList::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return mozilla::dom::SVGTransformList_Binding::Wrap(cx, this, aGivenProto); +} + +void DOMSVGTransformList::InternalListLengthWillChange(uint32_t aNewLength) { + uint32_t oldLength = mItems.Length(); + + if (aNewLength > DOMSVGTransform::MaxListIndex()) { + // It's safe to get out of sync with our internal list as long as we have + // FEWER items than it does. + aNewLength = DOMSVGTransform::MaxListIndex(); + } + + RefPtr<DOMSVGTransformList> kungFuDeathGrip; + if (aNewLength < oldLength) { + // RemovingFromList() might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + + // If our length will decrease, notify the items that will be removed: + for (uint32_t i = aNewLength; i < oldLength; ++i) { + if (mItems[i]) { + mItems[i]->RemovingFromList(); + } + } + + if (!mItems.SetLength(aNewLength, fallible)) { + // We silently ignore SetLength OOM failure since being out of sync is safe + // so long as we have *fewer* items than our internal list. + mItems.Clear(); + return; + } + + // If our length has increased, null out the new pointers: + for (uint32_t i = oldLength; i < aNewLength; ++i) { + mItems[i] = nullptr; + } +} + +SVGTransformList& DOMSVGTransformList::InternalList() const { + SVGAnimatedTransformList* alist = Element()->GetAnimatedTransformList(); + return IsAnimValList() && alist->mAnimVal ? *alist->mAnimVal + : alist->mBaseVal; +} + +//---------------------------------------------------------------------- +void DOMSVGTransformList::Clear(ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + if (LengthNoFlush() > 0) { + AutoChangeTransformListNotifier notifier(this); + // Notify any existing DOM items of removal *before* truncating the lists + // so that they can find their DOMSVGTransform internal counterparts and + // copy their values. This also notifies the animVal list: + mAList->InternalBaseValListWillChangeLengthTo(0); + + mItems.Clear(); + InternalList().Clear(); + } +} + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::Initialize( + DOMSVGTransform& newItem, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + // If newItem is already in a list we should insert a clone of newItem, and + // for consistency, this should happen even if *this* is the list that + // newItem is currently in. Note that in the case of newItem being in this + // list, the Clear() call before the InsertItemBefore() call would remove it + // from this list, and so the InsertItemBefore() call would not insert a + // clone of newItem, it would actually insert newItem. To prevent that from + // happening we have to do the clone here, if necessary. + + RefPtr<DOMSVGTransform> domItem = &newItem; + if (domItem->HasOwner()) { + domItem = newItem.Clone(); + } + + Clear(error); + MOZ_ASSERT(!error.Failed(), "How could this fail?"); + return InsertItemBefore(*domItem, 0, error); +} + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::GetItem( + uint32_t index, ErrorResult& error) { + bool found; + RefPtr<DOMSVGTransform> item = IndexedGetter(index, found, error); + if (!found) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + } + return item.forget(); +} + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::IndexedGetter( + uint32_t index, bool& found, ErrorResult& error) { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + found = index < LengthNoFlush(); + if (found) { + return GetItemAt(index); + } + return nullptr; +} + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::InsertItemBefore( + DOMSVGTransform& newItem, uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + index = std::min(index, LengthNoFlush()); + if (index >= DOMSVGTransform::MaxListIndex()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGTransform> domItem = &newItem; + if (newItem.HasOwner()) { + domItem = newItem.Clone(); // must do this before changing anything! + } + + // Ensure we have enough memory so we can avoid complex error handling below: + if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || + !InternalList().SetCapacity(InternalList().Length() + 1)) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + if (AnimListMirrorsBaseList()) { + if (!mAList->mAnimVal->mItems.SetCapacity( + mAList->mAnimVal->mItems.Length() + 1, fallible)) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + } + + AutoChangeTransformListNotifier notifier(this); + // Now that we know we're inserting, keep animVal list in sync as necessary. + MaybeInsertNullInAnimValListAt(index); + + InternalList().InsertItem(index, domItem->ToSVGTransform()); + MOZ_ALWAYS_TRUE(mItems.InsertElementAt(index, domItem.get(), fallible)); + + // This MUST come after the insertion into InternalList(), or else under the + // insertion into InternalList() the values read from domItem would be bad + // data from InternalList() itself!: + domItem->InsertingIntoList(this, index, IsAnimValList()); + + UpdateListIndicesFromIndex(mItems, index + 1); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::ReplaceItem( + DOMSVGTransform& newItem, uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (index >= LengthNoFlush()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + RefPtr<DOMSVGTransform> domItem = &newItem; + if (newItem.HasOwner()) { + domItem = newItem.Clone(); // must do this before changing anything! + } + + AutoChangeTransformListNotifier notifier(this); + if (mItems[index]) { + // Notify any existing DOM item of removal *before* modifying the lists so + // that the DOM item can copy the *old* value at its index: + mItems[index]->RemovingFromList(); + } + + InternalList()[index] = domItem->ToSVGTransform(); + mItems[index] = domItem; + + // This MUST come after the ToSVGPoint() call, otherwise that call + // would end up reading bad data from InternalList()! + domItem->InsertingIntoList(this, index, IsAnimValList()); + + return domItem.forget(); +} + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::RemoveItem( + uint32_t index, ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (index >= LengthNoFlush()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + AutoChangeTransformListNotifier notifier(this); + // Now that we know we're removing, keep animVal list in sync as necessary. + // Do this *before* touching InternalList() so the removed item can get its + // internal value. + MaybeRemoveItemFromAnimValListAt(index); + + // We have to return the removed item, so get it, creating it if necessary: + RefPtr<DOMSVGTransform> result = GetItemAt(index); + + // Notify the DOM item of removal *before* modifying the lists so that the + // DOM item can copy its *old* value: + result->RemovingFromList(); + + InternalList().RemoveItem(index); + mItems.RemoveElementAt(index); + + UpdateListIndicesFromIndex(mItems, index); + + return result.forget(); +} + +already_AddRefed<DOMSVGTransform> +DOMSVGTransformList::CreateSVGTransformFromMatrix(const DOMMatrix2DInit& matrix, + ErrorResult& rv) { + RefPtr<DOMSVGTransform> result = new DOMSVGTransform(matrix, rv); + return result.forget(); +} + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::Consolidate( + ErrorResult& error) { + if (IsAnimValList()) { + error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return nullptr; + } + + if (LengthNoFlush() == 0) { + return nullptr; + } + + // Note that SVG 1.1 says, "The consolidation operation creates new + // SVGTransform object as the first and only item in the list" hence, even if + // LengthNoFlush() == 1 we can't return that one item (after making it a + // matrix type). We must orphan the existing item and then make a new one. + + // First calculate our matrix + gfxMatrix mx = InternalList().GetConsolidationMatrix(); + + // Then orphan the existing items + Clear(error); + MOZ_ASSERT(!error.Failed(), "How could this fail?"); + + // And append the new transform + RefPtr<DOMSVGTransform> transform = new DOMSVGTransform(mx); + return InsertItemBefore(*transform, LengthNoFlush(), error); +} + +//---------------------------------------------------------------------- +// Implementation helpers: + +already_AddRefed<DOMSVGTransform> DOMSVGTransformList::GetItemAt( + uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length()); + + if (!mItems[aIndex]) { + mItems[aIndex] = new DOMSVGTransform(this, aIndex, IsAnimValList()); + } + RefPtr<DOMSVGTransform> result = mItems[aIndex]; + return result.forget(); +} + +void DOMSVGTransformList::MaybeInsertNullInAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + DOMSVGTransformList* animVal = mAList->mAnimVal; + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible)); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1); +} + +void DOMSVGTransformList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex) { + MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); + + if (!AnimListMirrorsBaseList()) { + return; + } + + // This needs to be a strong reference; otherwise, the RemovingFromList call + // below might drop the last reference to animVal before we're done with it. + RefPtr<DOMSVGTransformList> animVal = mAList->mAnimVal; + + MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); + MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), + "animVal list not in sync!"); + + if (animVal->mItems[aIndex]) { + animVal->mItems[aIndex]->RemovingFromList(); + } + animVal->mItems.RemoveElementAt(aIndex); + + UpdateListIndicesFromIndex(animVal->mItems, aIndex); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGTransformList.h b/dom/svg/DOMSVGTransformList.h new file mode 100644 index 0000000000..2bfb975c71 --- /dev/null +++ b/dom/svg/DOMSVGTransformList.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGTRANSFORMLIST_H_ +#define DOM_SVG_DOMSVGTRANSFORMLIST_H_ + +#include "DOMSVGAnimatedTransformList.h" +#include "mozAutoDocUpdate.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "SVGTransformList.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { +struct DOMMatrix2DInit; +class DOMSVGTransform; +class SVGElement; +class SVGMatrix; + +//---------------------------------------------------------------------- +// Helper class: AutoChangeTransformListNotifier +// Stack-based helper class to pair calls to WillChangeTransformList and +// DidChangeTransformList. Used by DOMSVGTransform and DOMSVGTransformList. +template <class T> +class MOZ_RAII AutoChangeTransformListNotifier { + public: + explicit AutoChangeTransformListNotifier(T* aValue) : mValue(aValue) { + MOZ_ASSERT(mValue, "Expecting non-null value"); + // If we don't have an owner then changes don't affect anything else. + if (mValue->HasOwner()) { + mUpdateBatch.emplace(mValue->Element()->GetComposedDoc(), true); + mEmptyOrOldValue = + mValue->Element()->WillChangeTransformList(mUpdateBatch.ref()); + } + } + + ~AutoChangeTransformListNotifier() { + if (mValue->HasOwner()) { + mValue->Element()->DidChangeTransformList(mEmptyOrOldValue, + mUpdateBatch.ref()); + if (mValue->IsAnimating()) { + mValue->Element()->AnimationNeedsResample(); + } + } + } + + private: + T* const mValue; + Maybe<mozAutoDocUpdate> mUpdateBatch; + nsAttrValue mEmptyOrOldValue; +}; + +/** + * Class DOMSVGTransformList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGTransformList objects. + * + * See the architecture comment in DOMSVGAnimatedTransformList.h. + */ +class DOMSVGTransformList final : public nsISupports, public nsWrapperCache { + template <class T> + friend class AutoChangeTransformListNotifier; + friend class dom::DOMSVGTransform; + + ~DOMSVGTransformList() { + // Our mAList's weak ref to us must be nulled out when we die. If GC has + // unlinked us using the cycle collector code, then that has already + // happened, and mAList is null. + if (mAList) { + (IsAnimValList() ? mAList->mAnimVal : mAList->mBaseVal) = nullptr; + } + } + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGTransformList) + + DOMSVGTransformList(dom::DOMSVGAnimatedTransformList* aAList, + const SVGTransformList& aInternalList) + : mAList(aAList) { + // aInternalList must be passed in explicitly because we can't use + // InternalList() here. (Because it depends on IsAnimValList, which depends + // on this object having been assigned to aAList's mBaseVal or mAnimVal, + // which hasn't happened yet.) + + InternalListLengthWillChange(aInternalList.Length()); // Sync mItems + } + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + nsISupports* GetParentObject() { return static_cast<nsIContent*>(Element()); } + + /** + * This will normally be the same as InternalList().Length(), except if we've + * hit OOM in which case our length will be zero. + */ + uint32_t LengthNoFlush() const { + MOZ_ASSERT(mItems.IsEmpty() || mItems.Length() == InternalList().Length(), + "DOM wrapper's list length is out of sync"); + return mItems.Length(); + } + + /// Called to notify us to synchronize our length and detach excess items. + void InternalListLengthWillChange(uint32_t aNewLength); + + /* + * List classes always have an owner. We need this so that templates that work + * on lists and elements can check ownership where elements may be unowned. + */ + bool HasOwner() const { return true; } + + /** + * Returns true if our attribute is animating (in which case our animVal is + * not simply a mirror of our baseVal). + */ + bool IsAnimating() const { return mAList->IsAnimating(); } + /** + * Returns true if there is an animated list mirroring the base list. + */ + bool AnimListMirrorsBaseList() const { + return mAList->mAnimVal && !mAList->IsAnimating(); + } + + uint32_t NumberOfItems() const { + if (IsAnimValList()) { + Element()->FlushAnimations(); + } + return LengthNoFlush(); + } + void Clear(ErrorResult& error); + already_AddRefed<dom::DOMSVGTransform> Initialize( + dom::DOMSVGTransform& newItem, ErrorResult& error); + already_AddRefed<dom::DOMSVGTransform> GetItem(uint32_t index, + ErrorResult& error); + already_AddRefed<dom::DOMSVGTransform> IndexedGetter(uint32_t index, + bool& found, + ErrorResult& error); + already_AddRefed<dom::DOMSVGTransform> InsertItemBefore( + dom::DOMSVGTransform& newItem, uint32_t index, ErrorResult& error); + already_AddRefed<dom::DOMSVGTransform> ReplaceItem( + dom::DOMSVGTransform& newItem, uint32_t index, ErrorResult& error); + already_AddRefed<dom::DOMSVGTransform> RemoveItem(uint32_t index, + ErrorResult& error); + already_AddRefed<dom::DOMSVGTransform> AppendItem( + dom::DOMSVGTransform& newItem, ErrorResult& error) { + return InsertItemBefore(newItem, LengthNoFlush(), error); + } + already_AddRefed<dom::DOMSVGTransform> CreateSVGTransformFromMatrix( + const DOMMatrix2DInit& aMatrix, ErrorResult& aRv); + already_AddRefed<dom::DOMSVGTransform> Consolidate(ErrorResult& error); + uint32_t Length() const { return NumberOfItems(); } + + private: + dom::SVGElement* Element() const { return mAList->mElement; } + + /// Used to determine if this list is the baseVal or animVal list. + bool IsAnimValList() const { + MOZ_ASSERT(this == mAList->mBaseVal || this == mAList->mAnimVal, + "Calling IsAnimValList() too early?!"); + return this == mAList->mAnimVal; + } + + /** + * Get a reference to this object's corresponding internal SVGTransformList. + * + * To simplify the code we just have this one method for obtaining both + * baseVal and animVal internal lists. This means that animVal lists don't + * get const protection, but our setter methods guard against changing + * animVal lists. + */ + SVGTransformList& InternalList() const; + + /// Returns the DOMSVGTransform at aIndex, creating it if necessary. + already_AddRefed<dom::DOMSVGTransform> GetItemAt(uint32_t aIndex); + + void MaybeInsertNullInAnimValListAt(uint32_t aIndex); + void MaybeRemoveItemFromAnimValListAt(uint32_t aIndex); + + // Weak refs to our DOMSVGTransform items. The items are friends and take care + // of clearing our pointer to them when they die. + FallibleTArray<dom::DOMSVGTransform*> mItems; + + RefPtr<dom::DOMSVGAnimatedTransformList> mAList; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGTRANSFORMLIST_H_ diff --git a/dom/svg/SVGAElement.cpp b/dom/svg/SVGAElement.cpp new file mode 100644 index 0000000000..a63c036de0 --- /dev/null +++ b/dom/svg/SVGAElement.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGAElement.h" + +#include "mozilla/Attributes.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/dom/SVGAElementBinding.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "nsIContentInlines.h" +#include "nsIURI.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(A) + +namespace mozilla::dom { + +JSObject* SVGAElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGAElement::sStringInfo[3] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}, + {nsGkAtoms::target, kNameSpaceID_None, true}}; + +// static +const DOMTokenListSupportedToken SVGAElement::sSupportedRelValues[] = { + "noreferrer", "noopener", nullptr}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAElement) + NS_INTERFACE_MAP_ENTRY(Link) +NS_INTERFACE_MAP_END_INHERITING(SVGAElementBase) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAElement, SVGAElementBase, mRelList) + +NS_IMPL_ADDREF_INHERITED(SVGAElement, SVGAElementBase) +NS_IMPL_RELEASE_INHERITED(SVGAElement, SVGAElementBase) + +//---------------------------------------------------------------------- +// Implementation + +SVGAElement::SVGAElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGAElementBase(std::move(aNodeInfo)), Link(this) {} + +already_AddRefed<DOMSVGAnimatedString> SVGAElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsINode methods + +void SVGAElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + Element::GetEventTargetParent(aVisitor); + + GetEventTargetParentForLinks(aVisitor); +} + +nsresult SVGAElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { + return PostHandleEventForLinks(aVisitor); +} + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGAElement::Target() { + return mStringAttributes[TARGET].ToDOMAnimatedString(this); +} + +void SVGAElement::GetDownload(nsAString& aDownload) { + GetAttr(nsGkAtoms::download, aDownload); +} + +void SVGAElement::SetDownload(const nsAString& aDownload, ErrorResult& rv) { + SetAttr(nsGkAtoms::download, aDownload, rv); +} + +void SVGAElement::GetPing(nsAString& aPing) { GetAttr(nsGkAtoms::ping, aPing); } + +void SVGAElement::SetPing(const nsAString& aPing, ErrorResult& rv) { + SetAttr(nsGkAtoms::ping, aPing, rv); +} + +void SVGAElement::GetRel(nsAString& aRel) { GetAttr(nsGkAtoms::rel, aRel); } + +void SVGAElement::SetRel(const nsAString& aRel, ErrorResult& rv) { + SetAttr(nsGkAtoms::rel, aRel, rv); +} + +void SVGAElement::GetReferrerPolicy(nsAString& aPolicy) { + GetEnumAttr(nsGkAtoms::referrerpolicy, "", aPolicy); +} + +void SVGAElement::SetReferrerPolicy(const nsAString& aPolicy, + mozilla::ErrorResult& rv) { + SetAttr(nsGkAtoms::referrerpolicy, aPolicy, rv); +} + +nsDOMTokenList* SVGAElement::RelList() { + if (!mRelList) { + mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues); + } + return mRelList; +} + +void SVGAElement::GetHreflang(nsAString& aHreflang) { + GetAttr(nsGkAtoms::hreflang, aHreflang); +} + +void SVGAElement::SetHreflang(const nsAString& aHreflang, + mozilla::ErrorResult& rv) { + SetAttr(nsGkAtoms::hreflang, aHreflang, rv); +} + +void SVGAElement::GetType(nsAString& aType) { GetAttr(nsGkAtoms::type, aType); } + +void SVGAElement::SetType(const nsAString& aType, mozilla::ErrorResult& rv) { + SetAttr(nsGkAtoms::type, aType, rv); +} + +void SVGAElement::GetText(nsAString& aText, mozilla::ErrorResult& rv) const { + if (NS_WARN_IF( + !nsContentUtils::GetNodeTextContent(this, true, aText, fallible))) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + } +} + +void SVGAElement::SetText(const nsAString& aText, mozilla::ErrorResult& rv) { + rv = nsContentUtils::SetNodeTextContent(this, aText, false); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGAElement::BindToTree(BindContext& aContext, nsINode& aParent) { + Link::ResetLinkState(false, Link::ElementHasHref()); + + nsresult rv = SVGAElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (Document* doc = aContext.GetComposedDoc()) { + doc->RegisterPendingLinkUpdate(this); + } + + return NS_OK; +} + +void SVGAElement::UnbindFromTree(bool aNullParent) { + // Without removing the link state we risk a dangling pointer + // in the mStyledLinks hashtable + Link::ResetLinkState(false, Link::ElementHasHref()); + + SVGAElementBase::UnbindFromTree(aNullParent); +} + +NS_IMETHODIMP_(bool) +SVGAElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return FindAttributeDependence(name, map) || + SVGAElementBase::IsAttributeMapped(name); +} + +int32_t SVGAElement::TabIndexDefault() { return 0; } + +bool SVGAElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) { + bool isFocusable = false; + if (IsSVGFocusable(&isFocusable, aTabIndex)) { + return isFocusable; + } + + if (!OwnerDoc()->LinkHandlingEnabled()) { + return false; + } + + // Links that are in an editable region should never be focusable, even if + // they are in a contenteditable="false" region. + if (nsContentUtils::IsNodeInEditableRegion(this)) { + if (aTabIndex) { + *aTabIndex = -1; + } + return false; + } + + if (GetTabIndexAttrValue().isNothing()) { + // check whether we're actually a link + if (!IsLink()) { + // Not tabbable or focusable without href (bug 17605), unless + // forced to be via presence of nonnegative tabindex attribute + if (aTabIndex) { + *aTabIndex = -1; + } + return false; + } + } + + if (aTabIndex && (sTabFocusModel & eTabFocus_linksMask) == 0) { + *aTabIndex = -1; + } + + return true; +} + +bool SVGAElement::HasHref() const { + // Currently our SMIL implementation does not modify the DOM attributes. Once + // we implement the SVG 2 SMIL behaviour this can be removed. + return mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet(); +} + +already_AddRefed<nsIURI> SVGAElement::GetHrefURI() const { + // Optimization: check for href first for early return + bool useBareHref = mStringAttributes[HREF].IsExplicitlySet(); + if (useBareHref || mStringAttributes[XLINK_HREF].IsExplicitlySet()) { + // Get absolute URI + nsAutoString str; + const uint8_t idx = useBareHref ? HREF : XLINK_HREF; + mStringAttributes[idx].GetAnimValue(str, this); + nsCOMPtr<nsIURI> uri; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), str, + OwnerDoc(), GetBaseURI()); + return uri.forget(); + } + return nullptr; +} + +void SVGAElement::GetLinkTarget(nsAString& aTarget) { + mStringAttributes[TARGET].GetAnimValue(aTarget, this); + if (aTarget.IsEmpty()) { + static Element::AttrValuesArray sShowVals[] = {nsGkAtoms::_new, + nsGkAtoms::replace, nullptr}; + + switch (FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::show, sShowVals, + eCaseMatters)) { + case 0: + aTarget.AssignLiteral("_blank"); + return; + case 1: + return; + } + Document* ownerDoc = OwnerDoc(); + if (ownerDoc) { + ownerDoc->GetBaseTarget(aTarget); + } + } +} + +ElementState SVGAElement::IntrinsicState() const { + return Link::LinkState() | SVGAElementBase::IntrinsicState(); +} + +nsresult SVGAElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + if (aName == nsGkAtoms::href && (aNameSpaceID == kNameSpaceID_XLink || + aNameSpaceID == kNameSpaceID_None)) { + // We can't assume that null aValue means we no longer have an href, because + // we could be unsetting xlink:href but still have a null-namespace href, or + // vice versa. But we can fast-path the case when we _do_ have a new value. + Link::ResetLinkState(aNotify, aValue || Link::ElementHasHref()); + } + + return SVGAElementBase::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue, + aMaybeScriptedPrincipal, aNotify); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGAElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGAElement.h b/dom/svg/SVGAElement.h new file mode 100644 index 0000000000..21811ddd40 --- /dev/null +++ b/dom/svg/SVGAElement.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGAELEMENT_H_ +#define DOM_SVG_SVGAELEMENT_H_ + +#include "Link.h" +#include "nsDOMTokenList.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGGraphicsElement.h" + +nsresult NS_NewSVGAElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { + +class EventChainPostVisitor; +class EventChainPreVisitor; + +namespace dom { + +using SVGAElementBase = SVGGraphicsElement; + +class SVGAElement final : public SVGAElementBase, public Link { + protected: + using Element::GetText; + + explicit SVGAElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + friend nsresult(::NS_NewSVGAElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGAElement, SVGAElementBase) + + // nsINode interface methods + void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; + MOZ_CAN_RUN_SCRIPT + nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override; + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // nsIContent + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent = true) override; + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + int32_t TabIndexDefault() override; + bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override; + + void GetLinkTarget(nsAString& aTarget) override; + already_AddRefed<nsIURI> GetHrefURI() const override; + bool HasHref() const; + + ElementState IntrinsicState() const override; + virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> Href(); + already_AddRefed<DOMSVGAnimatedString> Target(); + void GetDownload(nsAString& aDownload); + void SetDownload(const nsAString& aDownload, ErrorResult& rv); + void GetPing(nsAString& aPing); + void SetPing(const nsAString& aPing, mozilla::ErrorResult& rv); + void GetRel(nsAString& aRel); + void SetRel(const nsAString& aRel, mozilla::ErrorResult& rv); + void SetReferrerPolicy(const nsAString& aPolicy, mozilla::ErrorResult& rv); + void GetReferrerPolicy(nsAString& aPolicy); + nsDOMTokenList* RelList(); + void GetHreflang(nsAString& aHreflang); + void SetHreflang(const nsAString& aHreflang, mozilla::ErrorResult& rv); + void GetType(nsAString& aType); + void SetType(const nsAString& aType, mozilla::ErrorResult& rv); + void GetText(nsAString& aText, mozilla::ErrorResult& rv) const; + void SetText(const nsAString& aText, mozilla::ErrorResult& rv); + + void NodeInfoChanged(Document* aOldDoc) final { + ClearHasPendingLinkUpdate(); + SVGAElementBase::NodeInfoChanged(aOldDoc); + } + + NS_IMPL_FROMNODE_WITH_TAG(SVGAElement, kNameSpaceID_SVG, nsGkAtoms::a); + + protected: + virtual ~SVGAElement() = default; + + StringAttributesInfo GetStringInfo() override; + + enum { HREF, XLINK_HREF, TARGET }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; + + RefPtr<nsDOMTokenList> mRelList; + static DOMTokenListSupportedToken sSupportedRelValues[]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGAELEMENT_H_ diff --git a/dom/svg/SVGAnimateElement.cpp b/dom/svg/SVGAnimateElement.cpp new file mode 100644 index 0000000000..7d7a8267f2 --- /dev/null +++ b/dom/svg/SVGAnimateElement.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGAnimateElement.h" +#include "mozilla/dom/SVGAnimateElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Animate) + +namespace mozilla::dom { + +JSObject* SVGAnimateElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimateElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGAnimateElement::SVGAnimateElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGAnimationElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAnimateElement) + +//---------------------------------------------------------------------- + +SMILAnimationFunction& SVGAnimateElement::AnimationFunction() { + return mAnimationFunction; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGAnimateElement.h b/dom/svg/SVGAnimateElement.h new file mode 100644 index 0000000000..1b71d4e129 --- /dev/null +++ b/dom/svg/SVGAnimateElement.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEELEMENT_H_ +#define DOM_SVG_SVGANIMATEELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/SMILAnimationFunction.h" + +nsresult NS_NewSVGAnimateElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGAnimateElement final : public SVGAnimationElement { + protected: + explicit SVGAnimateElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + SMILAnimationFunction mAnimationFunction; + friend nsresult(::NS_NewSVGAnimateElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsINode + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGAnimationElement + SMILAnimationFunction& AnimationFunction() override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGANIMATEELEMENT_H_ diff --git a/dom/svg/SVGAnimateMotionElement.cpp b/dom/svg/SVGAnimateMotionElement.cpp new file mode 100644 index 0000000000..4fc197a9cb --- /dev/null +++ b/dom/svg/SVGAnimateMotionElement.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGAnimateMotionElement.h" +#include "mozilla/dom/SVGAnimateMotionElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(AnimateMotion) + +namespace mozilla::dom { + +JSObject* SVGAnimateMotionElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimateMotionElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGAnimateMotionElement::SVGAnimateMotionElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGAnimationElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAnimateMotionElement) + +//---------------------------------------------------------------------- + +SMILAnimationFunction& SVGAnimateMotionElement::AnimationFunction() { + return mAnimationFunction; +} + +bool SVGAnimateMotionElement::GetTargetAttributeName( + int32_t* aNamespaceID, nsAtom** aLocalName) const { + // <animateMotion> doesn't take an attributeName, since it doesn't target an + // 'attribute' per se. We'll use a unique dummy attribute-name so that our + // SMILTargetIdentifier logic (which requires a attribute name) still works. + *aNamespaceID = kNameSpaceID_None; + *aLocalName = nsGkAtoms::mozAnimateMotionDummyAttr; + return true; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGAnimateMotionElement.h b/dom/svg/SVGAnimateMotionElement.h new file mode 100644 index 0000000000..a02cd160a7 --- /dev/null +++ b/dom/svg/SVGAnimateMotionElement.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEMOTIONELEMENT_H_ +#define DOM_SVG_SVGANIMATEMOTIONELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGAnimationElement.h" +#include "SVGMotionSMILAnimationFunction.h" + +nsresult NS_NewSVGAnimateMotionElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGAnimateMotionElement final : public SVGAnimationElement { + protected: + explicit SVGAnimateMotionElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + SVGMotionSMILAnimationFunction mAnimationFunction; + friend nsresult(::NS_NewSVGAnimateMotionElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsINode specializations + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGAnimationElement + SMILAnimationFunction& AnimationFunction() override; + virtual bool GetTargetAttributeName(int32_t* aNamespaceID, + nsAtom** aLocalName) const override; + + // SVGElement + nsStaticAtom* GetPathDataAttrName() const override { return nsGkAtoms::path; } + + // Utility method to let our <mpath> children tell us when they've changed, + // so we can make sure our mAnimationFunction is marked as having changed. + void MpathChanged() { mAnimationFunction.MpathChanged(); } +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGANIMATEMOTIONELEMENT_H_ diff --git a/dom/svg/SVGAnimateTransformElement.cpp b/dom/svg/SVGAnimateTransformElement.cpp new file mode 100644 index 0000000000..b9d5628149 --- /dev/null +++ b/dom/svg/SVGAnimateTransformElement.cpp @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGAnimateTransformElement.h" +#include "mozilla/dom/SVGAnimateTransformElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(AnimateTransform) + +namespace mozilla::dom { + +JSObject* SVGAnimateTransformElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGAnimateTransformElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGAnimateTransformElement::SVGAnimateTransformElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGAnimationElement(std::move(aNodeInfo)) {} + +bool SVGAnimateTransformElement::ParseAttribute( + int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) { + // 'type' is an <animateTransform>-specific attribute, and we'll handle it + // specially. + if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::type) { + aResult.ParseAtom(aValue); + nsAtom* atom = aResult.GetAtomValue(); + if (atom != nsGkAtoms::translate && atom != nsGkAtoms::scale && + atom != nsGkAtoms::rotate && atom != nsGkAtoms::skewX && + atom != nsGkAtoms::skewY) { + ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue); + } + return true; + } + + return SVGAnimationElement::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAnimateTransformElement) + +//---------------------------------------------------------------------- + +SMILAnimationFunction& SVGAnimateTransformElement::AnimationFunction() { + return mAnimationFunction; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGAnimateTransformElement.h b/dom/svg/SVGAnimateTransformElement.h new file mode 100644 index 0000000000..ae0a7bc947 --- /dev/null +++ b/dom/svg/SVGAnimateTransformElement.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATETRANSFORMELEMENT_H_ +#define DOM_SVG_SVGANIMATETRANSFORMELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/SMILAnimationFunction.h" + +nsresult NS_NewSVGAnimateTransformElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGAnimateTransformElement final : public SVGAnimationElement { + protected: + explicit SVGAnimateTransformElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + SMILAnimationFunction mAnimationFunction; + friend nsresult(::NS_NewSVGAnimateTransformElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsINode specializations + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // Element specializations + virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + + // SVGAnimationElement + SMILAnimationFunction& AnimationFunction() override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGANIMATETRANSFORMELEMENT_H_ diff --git a/dom/svg/SVGAnimatedBoolean.cpp b/dom/svg/SVGAnimatedBoolean.cpp new file mode 100644 index 0000000000..16fff045ec --- /dev/null +++ b/dom/svg/SVGAnimatedBoolean.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedBoolean.h" + +#include "DOMSVGAnimatedBoolean.h" +#include "nsError.h" +#include "SMILBoolType.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/SMILValue.h" + +using namespace mozilla::dom; + +namespace mozilla { + +/* Implementation */ + +//---------------------------------------------------------------------- +// Helper class: AutoChangeBooleanNotifier +// Stack-based helper class to ensure DidChangeBoolean is called. +class MOZ_RAII AutoChangeBooleanNotifier { + public: + AutoChangeBooleanNotifier(SVGAnimatedBoolean* aBoolean, + SVGElement* aSVGElement, bool aDoSetAttr = true) + : mBoolean(aBoolean), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mBoolean, "Expecting non-null boolean"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + } + + ~AutoChangeBooleanNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangeBoolean(mBoolean->mAttrEnum); + } + if (mBoolean->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedBoolean* const mBoolean; + SVGElement* const mSVGElement; + bool mDoSetAttr; +}; + +static inline SVGAttrTearoffTable<SVGAnimatedBoolean, DOMSVGAnimatedBoolean>& +SVGAnimatedBooleanTearoffTable() { + static SVGAttrTearoffTable<SVGAnimatedBoolean, DOMSVGAnimatedBoolean> + sSVGAnimatedBooleanTearoffTable; + return sSVGAnimatedBooleanTearoffTable; +} + +static bool GetValueFromString(const nsAString& aValueAsString, bool& aValue) { + if (aValueAsString.EqualsLiteral("true")) { + aValue = true; + return true; + } + if (aValueAsString.EqualsLiteral("false")) { + aValue = false; + return true; + } + return false; +} + +static nsresult GetValueFromAtom(const nsAtom* aValueAsAtom, bool* aValue) { + if (aValueAsAtom == nsGkAtoms::_true) { + *aValue = true; + return NS_OK; + } + if (aValueAsAtom == nsGkAtoms::_false) { + *aValue = false; + return NS_OK; + } + return NS_ERROR_DOM_SYNTAX_ERR; +} + +nsresult SVGAnimatedBoolean::SetBaseValueAtom(const nsAtom* aValue, + SVGElement* aSVGElement) { + bool val = false; + + nsresult rv = GetValueFromAtom(aValue, &val); + if (NS_FAILED(rv)) { + return rv; + } + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + AutoChangeBooleanNotifier notifier(this, aSVGElement, false); + + mBaseVal = val; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } + + return NS_OK; +} + +nsAtom* SVGAnimatedBoolean::GetBaseValueAtom() const { + return mBaseVal ? nsGkAtoms::_true : nsGkAtoms::_false; +} + +void SVGAnimatedBoolean::SetBaseValue(bool aValue, SVGElement* aSVGElement) { + if (aValue == mBaseVal) { + return; + } + + AutoChangeBooleanNotifier notifier(this, aSVGElement); + + mBaseVal = aValue; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } +} + +void SVGAnimatedBoolean::SetAnimValue(bool aValue, SVGElement* aSVGElement) { + if (mIsAnimated && mAnimVal == aValue) { + return; + } + mAnimVal = aValue; + mIsAnimated = true; + aSVGElement->DidAnimateBoolean(mAttrEnum); +} + +already_AddRefed<DOMSVGAnimatedBoolean> +SVGAnimatedBoolean::ToDOMAnimatedBoolean(SVGElement* aSVGElement) { + RefPtr<DOMSVGAnimatedBoolean> domAnimatedBoolean = + SVGAnimatedBooleanTearoffTable().GetTearoff(this); + if (!domAnimatedBoolean) { + domAnimatedBoolean = new DOMSVGAnimatedBoolean(this, aSVGElement); + SVGAnimatedBooleanTearoffTable().AddTearoff(this, domAnimatedBoolean); + } + + return domAnimatedBoolean.forget(); +} + +DOMSVGAnimatedBoolean::~DOMSVGAnimatedBoolean() { + SVGAnimatedBooleanTearoffTable().RemoveTearoff(mVal); +} + +UniquePtr<SMILAttr> SVGAnimatedBoolean::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILBool>(this, aSVGElement); +} + +nsresult SVGAnimatedBoolean::SMILBool::ValueFromString( + const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + bool value; + if (!GetValueFromString(aStr, value)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + SMILValue val(SMILBoolType::Singleton()); + val.mU.mBool = value; + aValue = val; + aPreventCachingOfSandwich = false; + + return NS_OK; +} + +SMILValue SVGAnimatedBoolean::SMILBool::GetBaseValue() const { + SMILValue val(SMILBoolType::Singleton()); + val.mU.mBool = mVal->mBaseVal; + return val; +} + +void SVGAnimatedBoolean::SMILBool::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal = mVal->mBaseVal; + mSVGElement->DidAnimateBoolean(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedBoolean::SMILBool::SetAnimValue(const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILBoolType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILBoolType::Singleton()) { + mVal->SetAnimValue(uint16_t(aValue.mU.mBool), mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedBoolean.h b/dom/svg/SVGAnimatedBoolean.h new file mode 100644 index 0000000000..db6dbb1189 --- /dev/null +++ b/dom/svg/SVGAnimatedBoolean.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDBOOLEAN_H_ +#define DOM_SVG_SVGANIMATEDBOOLEAN_H_ + +#include "nsError.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" + +class nsAtom; + +namespace mozilla { + +class SMILValue; + +namespace dom { +class DOMSVGAnimatedBoolean; +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +class SVGAnimatedBoolean { + public: + friend class AutoChangeBooleanNotifier; + using SVGElement = dom::SVGElement; + + void Init(uint8_t aAttrEnum = 0xff, bool aValue = false) { + mAnimVal = mBaseVal = aValue; + mAttrEnum = aAttrEnum; + mIsAnimated = false; + } + + nsresult SetBaseValueAtom(const nsAtom* aValue, SVGElement* aSVGElement); + nsAtom* GetBaseValueAtom() const; + + void SetBaseValue(bool aValue, SVGElement* aSVGElement); + bool GetBaseValue() const { return mBaseVal; } + + void SetAnimValue(bool aValue, SVGElement* aSVGElement); + bool GetAnimValue() const { return mAnimVal; } + + already_AddRefed<dom::DOMSVGAnimatedBoolean> ToDOMAnimatedBoolean( + SVGElement* aSVGElement); + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + bool mAnimVal; + bool mBaseVal; + bool mIsAnimated; + uint8_t mAttrEnum; // element specified tracking for attribute + + public: + struct SMILBool : public SMILAttr { + public: + SMILBool(SVGAnimatedBoolean* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedBoolean* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDBOOLEAN_H_ diff --git a/dom/svg/SVGAnimatedClass.cpp b/dom/svg/SVGAnimatedClass.cpp new file mode 100644 index 0000000000..e119b2e697 --- /dev/null +++ b/dom/svg/SVGAnimatedClass.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedClass.h" + +#include <utility> + +#include "DOMSVGAnimatedString.h" +#include "SMILStringType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla { + +void SVGAnimatedClass::SetBaseValue(const nsAString& aValue, + SVGElement* aSVGElement, bool aDoSetAttr) { + NS_ASSERTION(aSVGElement, "Null element passed to SetBaseValue"); + + aSVGElement->SetMayHaveClass(); + if (aDoSetAttr) { + aSVGElement->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, aValue, true); + } + if (mAnimVal) { + aSVGElement->AnimationNeedsResample(); + } +} + +void SVGAnimatedClass::GetBaseValue(nsAString& aValue, + const SVGElement* aSVGElement) const { + aSVGElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aValue); +} + +void SVGAnimatedClass::GetAnimValue(nsAString& aResult, + const SVGElement* aSVGElement) const { + if (mAnimVal) { + aResult = *mAnimVal; + return; + } + + aSVGElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aResult); +} + +void SVGAnimatedClass::SetAnimValue(const nsAString& aValue, + SVGElement* aSVGElement) { + if (mAnimVal && mAnimVal->Equals(aValue)) { + return; + } + if (!mAnimVal) { + mAnimVal = MakeUnique<nsString>(); + } + *mAnimVal = aValue; + aSVGElement->SetMayHaveClass(); + aSVGElement->DidAnimateClass(); +} + +UniquePtr<SMILAttr> SVGAnimatedClass::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILString>(this, aSVGElement); +} + +nsresult SVGAnimatedClass::SMILString::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SMILValue val(SMILStringType::Singleton()); + + *static_cast<nsAString*>(val.mU.mPtr) = aStr; + aValue = std::move(val); + aPreventCachingOfSandwich = false; + return NS_OK; +} + +SMILValue SVGAnimatedClass::SMILString::GetBaseValue() const { + SMILValue val(SMILStringType::Singleton()); + mSVGElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, + *static_cast<nsAString*>(val.mU.mPtr)); + return val; +} + +void SVGAnimatedClass::SMILString::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->mAnimVal = nullptr; + mSVGElement->DidAnimateClass(); + } +} + +nsresult SVGAnimatedClass::SMILString::SetAnimValue(const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILStringType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILStringType::Singleton()) { + mVal->SetAnimValue(*static_cast<nsAString*>(aValue.mU.mPtr), mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedClass.h b/dom/svg/SVGAnimatedClass.h new file mode 100644 index 0000000000..bc510b1d4d --- /dev/null +++ b/dom/svg/SVGAnimatedClass.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDCLASS_H_ +#define DOM_SVG_SVGANIMATEDCLASS_H_ + +#include "nsCycleCollectionParticipant.h" +#include "mozilla/SVGAnimatedClassOrString.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class DOMSVGAnimatedString; +class SVGElement; +} // namespace dom + +class SVGAnimatedClass final : public SVGAnimatedClassOrString { + public: + using SVGElement = dom::SVGElement; + + void Init() { mAnimVal = nullptr; } + + void SetBaseValue(const nsAString& aValue, SVGElement* aSVGElement, + bool aDoSetAttr) override; + void GetBaseValue(nsAString& aValue, + const SVGElement* aSVGElement) const override; + + void SetAnimValue(const nsAString& aValue, SVGElement* aSVGElement); + void GetAnimValue(nsAString& aResult, + const SVGElement* aSVGElement) const override; + bool IsAnimated() const { return !!mAnimVal; } + + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + UniquePtr<nsString> mAnimVal; + + public: + struct SMILString : public SMILAttr { + public: + SMILString(SVGAnimatedClass* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedClass* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDCLASS_H_ diff --git a/dom/svg/SVGAnimatedClassOrString.cpp b/dom/svg/SVGAnimatedClassOrString.cpp new file mode 100644 index 0000000000..25a89f15ae --- /dev/null +++ b/dom/svg/SVGAnimatedClassOrString.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedClassOrString.h" + +#include "DOMSVGAnimatedString.h" +#include "SVGAttrTearoffTable.h" + +namespace mozilla { + +static SVGAttrTearoffTable<SVGAnimatedClassOrString, dom::DOMSVGAnimatedString> + sSVGAnimatedClassOrStringTearoffTable; + +already_AddRefed<dom::DOMSVGAnimatedString> +SVGAnimatedClassOrString::ToDOMAnimatedString(SVGElement* aSVGElement) { + RefPtr<dom::DOMSVGAnimatedString> domAnimatedString = + sSVGAnimatedClassOrStringTearoffTable.GetTearoff(this); + if (!domAnimatedString) { + domAnimatedString = new dom::DOMSVGAnimatedString(this, aSVGElement); + sSVGAnimatedClassOrStringTearoffTable.AddTearoff(this, domAnimatedString); + } + + return domAnimatedString.forget(); +} + +void SVGAnimatedClassOrString::RemoveTearoff() { + sSVGAnimatedClassOrStringTearoffTable.RemoveTearoff(this); +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedClassOrString.h b/dom/svg/SVGAnimatedClassOrString.h new file mode 100644 index 0000000000..a364209131 --- /dev/null +++ b/dom/svg/SVGAnimatedClassOrString.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDCLASSORSTRING_H_ +#define DOM_SVG_SVGANIMATEDCLASSORSTRING_H_ + +#include "nsStringFwd.h" +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { + +namespace dom { +class DOMSVGAnimatedString; +class SVGElement; +} // namespace dom + +class SVGAnimatedClassOrString { + public: + using SVGElement = dom::SVGElement; + + virtual void SetBaseValue(const nsAString& aValue, SVGElement* aSVGElement, + bool aDoSetAttr) = 0; + virtual void GetBaseValue(nsAString& aValue, + const SVGElement* aSVGElement) const = 0; + + virtual void GetAnimValue(nsAString& aResult, + const SVGElement* aSVGElement) const = 0; + + already_AddRefed<dom::DOMSVGAnimatedString> ToDOMAnimatedString( + SVGElement* aSVGElement); + + void RemoveTearoff(); +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDCLASSORSTRING_H_ diff --git a/dom/svg/SVGAnimatedEnumeration.cpp b/dom/svg/SVGAnimatedEnumeration.cpp new file mode 100644 index 0000000000..3024b9c416 --- /dev/null +++ b/dom/svg/SVGAnimatedEnumeration.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedEnumeration.h" + +#include "mozilla/dom/SVGElement.h" +#include "mozilla/SMILValue.h" +#include "nsAtom.h" +#include "nsError.h" +#include "SMILEnumType.h" +#include "SVGAttrTearoffTable.h" + +using namespace mozilla::dom; + +namespace mozilla { + +//---------------------------------------------------------------------- +// Helper class: AutoChangeEnumNotifier +// Stack-based helper class to ensure DidChangeEnum is called. +class MOZ_RAII AutoChangeEnumNotifier { + public: + AutoChangeEnumNotifier(SVGAnimatedEnumeration* aEnum, SVGElement* aSVGElement, + bool aDoSetAttr = true) + : mEnum(aEnum), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mEnum, "Expecting non-null enum"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + } + + ~AutoChangeEnumNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangeEnum(mEnum->mAttrEnum); + } + if (mEnum->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedEnumeration* const mEnum; + SVGElement* const mSVGElement; + bool mDoSetAttr; +}; + +static SVGAttrTearoffTable<SVGAnimatedEnumeration, + SVGAnimatedEnumeration::DOMAnimatedEnum> + sSVGAnimatedEnumTearoffTable; + +const SVGEnumMapping* SVGAnimatedEnumeration::GetMapping( + SVGElement* aSVGElement) { + SVGElement::EnumAttributesInfo info = aSVGElement->GetEnumInfo(); + + NS_ASSERTION(info.mCount > 0 && mAttrEnum < info.mCount, + "mapping request for a non-attrib enum"); + + return info.mInfos[mAttrEnum].mMapping; +} + +bool SVGAnimatedEnumeration::SetBaseValueAtom(const nsAtom* aValue, + SVGElement* aSVGElement) { + const SVGEnumMapping* mapping = GetMapping(aSVGElement); + + while (mapping && mapping->mKey) { + if (aValue == mapping->mKey) { + if (!mIsBaseSet || mBaseVal != mapping->mVal) { + mIsBaseSet = true; + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + AutoChangeEnumNotifier notifier(this, aSVGElement, false); + + mBaseVal = mapping->mVal; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } + } + return true; + } + mapping++; + } + + return false; +} + +nsAtom* SVGAnimatedEnumeration::GetBaseValueAtom(SVGElement* aSVGElement) { + const SVGEnumMapping* mapping = GetMapping(aSVGElement); + + while (mapping && mapping->mKey) { + if (mBaseVal == mapping->mVal) { + return mapping->mKey; + } + mapping++; + } + NS_ERROR("unknown enumeration value"); + return nsGkAtoms::_empty; +} + +void SVGAnimatedEnumeration::SetBaseValue(uint16_t aValue, + SVGElement* aSVGElement, + ErrorResult& aRv) { + const SVGEnumMapping* mapping = GetMapping(aSVGElement); + + while (mapping && mapping->mKey) { + if (mapping->mVal == aValue) { + if (!mIsBaseSet || mBaseVal != uint8_t(aValue)) { + mIsBaseSet = true; + AutoChangeEnumNotifier notifier(this, aSVGElement); + + mBaseVal = uint8_t(aValue); + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } + } + return; + } + mapping++; + } + return aRv.ThrowTypeError("Invalid SVGAnimatedEnumeration base value"); +} + +void SVGAnimatedEnumeration::SetAnimValue(uint16_t aValue, + SVGElement* aSVGElement) { + if (mIsAnimated && aValue == mAnimVal) { + return; + } + mAnimVal = aValue; + mIsAnimated = true; + aSVGElement->DidAnimateEnum(mAttrEnum); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGAnimatedEnumeration::ToDOMAnimatedEnum(SVGElement* aSVGElement) { + RefPtr<DOMAnimatedEnum> domAnimatedEnum = + sSVGAnimatedEnumTearoffTable.GetTearoff(this); + if (!domAnimatedEnum) { + domAnimatedEnum = new DOMAnimatedEnum(this, aSVGElement); + sSVGAnimatedEnumTearoffTable.AddTearoff(this, domAnimatedEnum); + } + + return domAnimatedEnum.forget(); +} + +SVGAnimatedEnumeration::DOMAnimatedEnum::~DOMAnimatedEnum() { + sSVGAnimatedEnumTearoffTable.RemoveTearoff(mVal); +} + +UniquePtr<SMILAttr> SVGAnimatedEnumeration::ToSMILAttr( + SVGElement* aSVGElement) { + return MakeUnique<SMILEnum>(this, aSVGElement); +} + +nsresult SVGAnimatedEnumeration::SMILEnum::ValueFromString( + const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + nsAtom* valAtom = NS_GetStaticAtom(aStr); + if (valAtom) { + const SVGEnumMapping* mapping = mVal->GetMapping(mSVGElement); + + while (mapping && mapping->mKey) { + if (valAtom == mapping->mKey) { + SMILValue val(SMILEnumType::Singleton()); + val.mU.mUint = mapping->mVal; + aValue = val; + aPreventCachingOfSandwich = false; + return NS_OK; + } + mapping++; + } + } + + // only a warning since authors may mistype attribute values + NS_WARNING("unknown enumeration key"); + return NS_ERROR_FAILURE; +} + +SMILValue SVGAnimatedEnumeration::SMILEnum::GetBaseValue() const { + SMILValue val(SMILEnumType::Singleton()); + val.mU.mUint = mVal->mBaseVal; + return val; +} + +void SVGAnimatedEnumeration::SMILEnum::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal = mVal->mBaseVal; + mSVGElement->DidAnimateEnum(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedEnumeration::SMILEnum::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILEnumType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILEnumType::Singleton()) { + MOZ_ASSERT(aValue.mU.mUint <= USHRT_MAX, + "Very large enumerated value - too big for uint16_t"); + mVal->SetAnimValue(uint16_t(aValue.mU.mUint), mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedEnumeration.h b/dom/svg/SVGAnimatedEnumeration.h new file mode 100644 index 0000000000..11e6dfe988 --- /dev/null +++ b/dom/svg/SVGAnimatedEnumeration.h @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDENUMERATION_H_ +#define DOM_SVG_SVGANIMATEDENUMERATION_H_ + +#include "DOMSVGAnimatedEnumeration.h" +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SVGElement.h" + +class nsAtom; + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +} // namespace dom + +using SVGEnumValue = uint8_t; + +struct SVGEnumMapping { + nsStaticAtom* const mKey; + const SVGEnumValue mVal; +}; + +class SVGAnimatedEnumeration { + public: + friend class AutoChangeEnumNotifier; + using SVGElement = dom::SVGElement; + + void Init(uint8_t aAttrEnum, uint16_t aValue) { + mAnimVal = mBaseVal = uint8_t(aValue); + mAttrEnum = aAttrEnum; + mIsAnimated = false; + mIsBaseSet = false; + } + + // Returns whether aValue corresponded to a key in our mapping (in which case + // we actually set the base value) or not (in which case we did not). + bool SetBaseValueAtom(const nsAtom* aValue, SVGElement* aSVGElement); + nsAtom* GetBaseValueAtom(SVGElement* aSVGElement); + void SetBaseValue(uint16_t aValue, SVGElement* aSVGElement, ErrorResult& aRv); + uint16_t GetBaseValue() const { return mBaseVal; } + + void SetAnimValue(uint16_t aValue, SVGElement* aSVGElement); + uint16_t GetAnimValue() const { return mAnimVal; } + bool IsExplicitlySet() const { return mIsAnimated || mIsBaseSet; } + + already_AddRefed<dom::DOMSVGAnimatedEnumeration> ToDOMAnimatedEnum( + SVGElement* aSVGElement); + + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + SVGEnumValue mAnimVal; + SVGEnumValue mBaseVal; + uint8_t mAttrEnum; // element specified tracking for attribute + bool mIsAnimated; + bool mIsBaseSet; + + const SVGEnumMapping* GetMapping(SVGElement* aSVGElement); + + public: + // DOM wrapper class for the (DOM)SVGAnimatedEnumeration interface where the + // wrapped class is SVGAnimatedEnumeration. + struct DOMAnimatedEnum final : public dom::DOMSVGAnimatedEnumeration { + DOMAnimatedEnum(SVGAnimatedEnumeration* aVal, SVGElement* aSVGElement) + : dom::DOMSVGAnimatedEnumeration(aSVGElement), mVal(aVal) {} + virtual ~DOMAnimatedEnum(); + + SVGAnimatedEnumeration* mVal; // kept alive because it belongs to content + + using dom::DOMSVGAnimatedEnumeration::SetBaseVal; + uint16_t BaseVal() override { return mVal->GetBaseValue(); } + void SetBaseVal(uint16_t aBaseVal, ErrorResult& aRv) override { + mVal->SetBaseValue(aBaseVal, mSVGElement, aRv); + } + uint16_t AnimVal() override { + // Script may have modified animation parameters or timeline -- DOM + // getters need to flush any resample requests to reflect these + // modifications. + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(); + } + }; + + struct SMILEnum : public SMILAttr { + public: + SMILEnum(SVGAnimatedEnumeration* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedEnumeration* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDENUMERATION_H_ diff --git a/dom/svg/SVGAnimatedInteger.cpp b/dom/svg/SVGAnimatedInteger.cpp new file mode 100644 index 0000000000..2815e4df11 --- /dev/null +++ b/dom/svg/SVGAnimatedInteger.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedInteger.h" + +#include "nsError.h" +#include "SMILIntegerType.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGContentUtils.h" + +using namespace mozilla::dom; + +namespace mozilla { + +/* Implementation */ + +//---------------------------------------------------------------------- +// Helper class: AutoChangeIntegerNotifier +// Stack-based helper class ensure DidChangeInteger is called. +class MOZ_RAII AutoChangeIntegerNotifier { + public: + AutoChangeIntegerNotifier(SVGAnimatedInteger* aInteger, + SVGElement* aSVGElement, bool aDoSetAttr = true) + : mInteger(aInteger), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mInteger, "Expecting non-null integer"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + } + + ~AutoChangeIntegerNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangeInteger(mInteger->mAttrEnum); + } + if (mInteger->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedInteger* const mInteger; + SVGElement* const mSVGElement; + bool mDoSetAttr; +}; + +static SVGAttrTearoffTable<SVGAnimatedInteger, + SVGAnimatedInteger::DOMAnimatedInteger> + sSVGAnimatedIntegerTearoffTable; + +nsresult SVGAnimatedInteger::SetBaseValueString(const nsAString& aValueAsString, + SVGElement* aSVGElement) { + bool success; + auto token = SVGContentUtils::GetAndEnsureOneToken(aValueAsString, success); + + if (!success) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + int32_t value; + + if (!SVGContentUtils::ParseInteger(token, value)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + AutoChangeIntegerNotifier notifier(this, aSVGElement, false); + + mIsBaseSet = true; + mBaseVal = value; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } + return NS_OK; +} + +void SVGAnimatedInteger::GetBaseValueString(nsAString& aValueAsString) { + aValueAsString.Truncate(); + aValueAsString.AppendInt(mBaseVal); +} + +void SVGAnimatedInteger::SetBaseValue(int aValue, SVGElement* aSVGElement) { + // We can't just rely on SetParsedAttrValue (as called by DidChangeInteger) + // detecting redundant changes since it will compare false if the existing + // attribute value has an associated serialized version (a string value) even + // if the integers match due to the way integers are stored in nsAttrValue. + if (aValue == mBaseVal && mIsBaseSet) { + return; + } + + AutoChangeIntegerNotifier notifier(this, aSVGElement); + + mBaseVal = aValue; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } +} + +void SVGAnimatedInteger::SetAnimValue(int aValue, SVGElement* aSVGElement) { + if (mIsAnimated && aValue == mAnimVal) { + return; + } + mAnimVal = aValue; + mIsAnimated = true; + aSVGElement->DidAnimateInteger(mAttrEnum); +} + +already_AddRefed<DOMSVGAnimatedInteger> +SVGAnimatedInteger::ToDOMAnimatedInteger(SVGElement* aSVGElement) { + RefPtr<DOMAnimatedInteger> domAnimatedInteger = + sSVGAnimatedIntegerTearoffTable.GetTearoff(this); + if (!domAnimatedInteger) { + domAnimatedInteger = new DOMAnimatedInteger(this, aSVGElement); + sSVGAnimatedIntegerTearoffTable.AddTearoff(this, domAnimatedInteger); + } + + return domAnimatedInteger.forget(); +} + +SVGAnimatedInteger::DOMAnimatedInteger::~DOMAnimatedInteger() { + sSVGAnimatedIntegerTearoffTable.RemoveTearoff(mVal); +} + +UniquePtr<SMILAttr> SVGAnimatedInteger::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILInteger>(this, aSVGElement); +} + +nsresult SVGAnimatedInteger::SMILInteger::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + int32_t val; + + if (!SVGContentUtils::ParseInteger(aStr, val)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + SMILValue smilVal(SMILIntegerType::Singleton()); + smilVal.mU.mInt = val; + aValue = smilVal; + aPreventCachingOfSandwich = false; + return NS_OK; +} + +SMILValue SVGAnimatedInteger::SMILInteger::GetBaseValue() const { + SMILValue val(SMILIntegerType::Singleton()); + val.mU.mInt = mVal->mBaseVal; + return val; +} + +void SVGAnimatedInteger::SMILInteger::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal = mVal->mBaseVal; + mSVGElement->DidAnimateInteger(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedInteger::SMILInteger::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILIntegerType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILIntegerType::Singleton()) { + mVal->SetAnimValue(int(aValue.mU.mInt), mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedInteger.h b/dom/svg/SVGAnimatedInteger.h new file mode 100644 index 0000000000..156f6da045 --- /dev/null +++ b/dom/svg/SVGAnimatedInteger.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDINTEGER_H_ +#define DOM_SVG_SVGANIMATEDINTEGER_H_ + +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "DOMSVGAnimatedInteger.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +} // namespace dom + +class SVGAnimatedInteger { + public: + friend class AutoChangeIntegerNotifier; + using SVGElement = dom::SVGElement; + + void Init(uint8_t aAttrEnum = 0xff, int32_t aValue = 0) { + mAnimVal = mBaseVal = aValue; + mAttrEnum = aAttrEnum; + mIsAnimated = false; + mIsBaseSet = false; + } + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement); + void GetBaseValueString(nsAString& aValue); + + void SetBaseValue(int32_t aValue, SVGElement* aSVGElement); + int32_t GetBaseValue() const { return mBaseVal; } + + void SetAnimValue(int aValue, SVGElement* aSVGElement); + int GetAnimValue() const { return mAnimVal; } + + // Returns true if the animated value of this integer has been explicitly + // set (either by animation, or by taking on the base value which has been + // explicitly set by markup or a DOM call), false otherwise. + // If this returns false, the animated value is still valid, that is, + // usable, and represents the default base value of the attribute. + bool IsExplicitlySet() const { return mIsAnimated || mIsBaseSet; } + + already_AddRefed<dom::DOMSVGAnimatedInteger> ToDOMAnimatedInteger( + SVGElement* aSVGElement); + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + int32_t mAnimVal; + int32_t mBaseVal; + uint8_t mAttrEnum; // element specified tracking for attribute + bool mIsAnimated; + bool mIsBaseSet; + + public: + struct DOMAnimatedInteger final : public dom::DOMSVGAnimatedInteger { + DOMAnimatedInteger(SVGAnimatedInteger* aVal, SVGElement* aSVGElement) + : dom::DOMSVGAnimatedInteger(aSVGElement), mVal(aVal) {} + virtual ~DOMAnimatedInteger(); + + SVGAnimatedInteger* mVal; // kept alive because it belongs to content + + int32_t BaseVal() override { return mVal->GetBaseValue(); } + void SetBaseVal(int32_t aValue) override { + mVal->SetBaseValue(aValue, mSVGElement); + } + + // Script may have modified animation parameters or timeline -- DOM getters + // need to flush any resample requests to reflect these modifications. + int32_t AnimVal() override { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(); + } + }; + + struct SMILInteger : public SMILAttr { + public: + SMILInteger(SVGAnimatedInteger* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedInteger* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDINTEGER_H_ diff --git a/dom/svg/SVGAnimatedIntegerPair.cpp b/dom/svg/SVGAnimatedIntegerPair.cpp new file mode 100644 index 0000000000..c336440a43 --- /dev/null +++ b/dom/svg/SVGAnimatedIntegerPair.cpp @@ -0,0 +1,250 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedIntegerPair.h" + +#include "nsCharSeparatedTokenizer.h" +#include "nsError.h" +#include "nsMathUtils.h" +#include "SVGAttrTearoffTable.h" +#include "SVGIntegerPairSMILType.h" +#include "mozAutoDocUpdate.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGContentUtils.h" + +using namespace mozilla::dom; + +namespace mozilla { + +//---------------------------------------------------------------------- +// Helper class: AutoChangeIntegerPairNotifier +// Stack-based helper class to pair calls to WillChangeIntegerPair and +// DidChangeIntegerPair. +class MOZ_RAII AutoChangeIntegerPairNotifier { + public: + AutoChangeIntegerPairNotifier(SVGAnimatedIntegerPair* aIntegerPair, + SVGElement* aSVGElement, bool aDoSetAttr = true) + : mIntegerPair(aIntegerPair), + mSVGElement(aSVGElement), + mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mIntegerPair, "Expecting non-null integerPair"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + + if (mDoSetAttr) { + mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true); + mEmptyOrOldValue = mSVGElement->WillChangeIntegerPair( + mIntegerPair->mAttrEnum, mUpdateBatch.ref()); + } + } + + ~AutoChangeIntegerPairNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangeIntegerPair(mIntegerPair->mAttrEnum, + mEmptyOrOldValue, mUpdateBatch.ref()); + } + if (mIntegerPair->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedIntegerPair* const mIntegerPair; + SVGElement* const mSVGElement; + Maybe<mozAutoDocUpdate> mUpdateBatch; + nsAttrValue mEmptyOrOldValue; + bool mDoSetAttr; +}; + +static SVGAttrTearoffTable<SVGAnimatedIntegerPair, + SVGAnimatedIntegerPair::DOMAnimatedInteger> + sSVGFirstAnimatedIntegerTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedIntegerPair, + SVGAnimatedIntegerPair::DOMAnimatedInteger> + sSVGSecondAnimatedIntegerTearoffTable; + +/* Implementation */ + +static nsresult ParseIntegerOptionalInteger(const nsAString& aValue, + int32_t aValues[2]) { + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional> + tokenizer(aValue, ','); + uint32_t i; + for (i = 0; i < 2 && tokenizer.hasMoreTokens(); ++i) { + if (!SVGContentUtils::ParseInteger(tokenizer.nextToken(), aValues[i])) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + } + if (i == 1) { + aValues[1] = aValues[0]; + } + + if (i == 0 || // Too few values. + tokenizer.hasMoreTokens() || // Too many values. + tokenizer.separatorAfterCurrentToken()) { // Trailing comma. + return NS_ERROR_DOM_SYNTAX_ERR; + } + + return NS_OK; +} + +nsresult SVGAnimatedIntegerPair::SetBaseValueString( + const nsAString& aValueAsString, SVGElement* aSVGElement) { + int32_t val[2]; + + nsresult rv = ParseIntegerOptionalInteger(aValueAsString, val); + + if (NS_FAILED(rv)) { + return rv; + } + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + AutoChangeIntegerPairNotifier notifier(this, aSVGElement, false); + + mBaseVal[0] = val[0]; + mBaseVal[1] = val[1]; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal[0] = mBaseVal[0]; + mAnimVal[1] = mBaseVal[1]; + } + return NS_OK; +} + +void SVGAnimatedIntegerPair::GetBaseValueString( + nsAString& aValueAsString) const { + aValueAsString.Truncate(); + aValueAsString.AppendInt(mBaseVal[0]); + if (mBaseVal[0] != mBaseVal[1]) { + aValueAsString.AppendLiteral(", "); + aValueAsString.AppendInt(mBaseVal[1]); + } +} + +void SVGAnimatedIntegerPair::SetBaseValue(int32_t aValue, PairIndex aPairIndex, + SVGElement* aSVGElement) { + uint32_t index = (aPairIndex == eFirst ? 0 : 1); + if (mIsBaseSet && mBaseVal[index] == aValue) { + return; + } + + AutoChangeIntegerPairNotifier notifier(this, aSVGElement); + + mBaseVal[index] = aValue; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal[index] = aValue; + } +} + +void SVGAnimatedIntegerPair::SetBaseValues(int32_t aValue1, int32_t aValue2, + SVGElement* aSVGElement) { + if (mIsBaseSet && mBaseVal[0] == aValue1 && mBaseVal[1] == aValue2) { + return; + } + + AutoChangeIntegerPairNotifier notifier(this, aSVGElement); + + mBaseVal[0] = aValue1; + mBaseVal[1] = aValue2; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal[0] = aValue1; + mAnimVal[1] = aValue2; + } +} + +void SVGAnimatedIntegerPair::SetAnimValue(const int32_t aValue[2], + SVGElement* aSVGElement) { + if (mIsAnimated && mAnimVal[0] == aValue[0] && mAnimVal[1] == aValue[1]) { + return; + } + mAnimVal[0] = aValue[0]; + mAnimVal[1] = aValue[1]; + mIsAnimated = true; + aSVGElement->DidAnimateIntegerPair(mAttrEnum); +} + +already_AddRefed<DOMSVGAnimatedInteger> +SVGAnimatedIntegerPair::ToDOMAnimatedInteger(PairIndex aIndex, + SVGElement* aSVGElement) { + RefPtr<DOMAnimatedInteger> domAnimatedInteger = + aIndex == eFirst ? sSVGFirstAnimatedIntegerTearoffTable.GetTearoff(this) + : sSVGSecondAnimatedIntegerTearoffTable.GetTearoff(this); + if (!domAnimatedInteger) { + domAnimatedInteger = new DOMAnimatedInteger(this, aIndex, aSVGElement); + if (aIndex == eFirst) { + sSVGFirstAnimatedIntegerTearoffTable.AddTearoff(this, domAnimatedInteger); + } else { + sSVGSecondAnimatedIntegerTearoffTable.AddTearoff(this, + domAnimatedInteger); + } + } + + return domAnimatedInteger.forget(); +} + +SVGAnimatedIntegerPair::DOMAnimatedInteger::~DOMAnimatedInteger() { + if (mIndex == eFirst) { + sSVGFirstAnimatedIntegerTearoffTable.RemoveTearoff(mVal); + } else { + sSVGSecondAnimatedIntegerTearoffTable.RemoveTearoff(mVal); + } +} + +UniquePtr<SMILAttr> SVGAnimatedIntegerPair::ToSMILAttr( + SVGElement* aSVGElement) { + return MakeUnique<SMILIntegerPair>(this, aSVGElement); +} + +nsresult SVGAnimatedIntegerPair::SMILIntegerPair::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + int32_t values[2]; + + nsresult rv = ParseIntegerOptionalInteger(aStr, values); + if (NS_FAILED(rv)) { + return rv; + } + + SMILValue val(SVGIntegerPairSMILType::Singleton()); + val.mU.mIntPair[0] = values[0]; + val.mU.mIntPair[1] = values[1]; + aValue = val; + aPreventCachingOfSandwich = false; + + return NS_OK; +} + +SMILValue SVGAnimatedIntegerPair::SMILIntegerPair::GetBaseValue() const { + SMILValue val(SVGIntegerPairSMILType::Singleton()); + val.mU.mIntPair[0] = mVal->mBaseVal[0]; + val.mU.mIntPair[1] = mVal->mBaseVal[1]; + return val; +} + +void SVGAnimatedIntegerPair::SMILIntegerPair::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal[0] = mVal->mBaseVal[0]; + mVal->mAnimVal[1] = mVal->mBaseVal[1]; + mSVGElement->DidAnimateIntegerPair(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedIntegerPair::SMILIntegerPair::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SVGIntegerPairSMILType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SVGIntegerPairSMILType::Singleton()) { + mVal->SetAnimValue(aValue.mU.mIntPair, mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedIntegerPair.h b/dom/svg/SVGAnimatedIntegerPair.h new file mode 100644 index 0000000000..41c368cd8c --- /dev/null +++ b/dom/svg/SVGAnimatedIntegerPair.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDINTEGERPAIR_H_ +#define DOM_SVG_SVGANIMATEDINTEGERPAIR_H_ + +#include "DOMSVGAnimatedInteger.h" +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +class SVGAnimatedIntegerPair { + public: + friend class AutoChangeIntegerPairNotifier; + using SVGElement = dom::SVGElement; + + enum PairIndex { eFirst, eSecond }; + + void Init(uint8_t aAttrEnum = 0xff, int32_t aValue1 = 0, + int32_t aValue2 = 0) { + mAnimVal[0] = mBaseVal[0] = aValue1; + mAnimVal[1] = mBaseVal[1] = aValue2; + mAttrEnum = aAttrEnum; + mIsAnimated = false; + mIsBaseSet = false; + } + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement); + void GetBaseValueString(nsAString& aValue) const; + + void SetBaseValue(int32_t aValue, PairIndex aPairIndex, + SVGElement* aSVGElement); + void SetBaseValues(int32_t aValue1, int32_t aValue2, SVGElement* aSVGElement); + int32_t GetBaseValue(PairIndex aIndex) const { + return mBaseVal[aIndex == eFirst ? 0 : 1]; + } + void SetAnimValue(const int32_t aValue[2], SVGElement* aSVGElement); + int32_t GetAnimValue(PairIndex aIndex) const { + return mAnimVal[aIndex == eFirst ? 0 : 1]; + } + + // Returns true if the animated value of this integer has been explicitly + // set (either by animation, or by taking on the base value which has been + // explicitly set by markup or a DOM call), false otherwise. + // If this returns false, the animated value is still valid, that is, + // usable, and represents the default base value of the attribute. + bool IsExplicitlySet() const { return mIsAnimated || mIsBaseSet; } + + already_AddRefed<dom::DOMSVGAnimatedInteger> ToDOMAnimatedInteger( + PairIndex aIndex, SVGElement* aSVGElement); + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + int32_t mAnimVal[2]; + int32_t mBaseVal[2]; + uint8_t mAttrEnum; // element specified tracking for attribute + bool mIsAnimated; + bool mIsBaseSet; + + public: + struct DOMAnimatedInteger final : public dom::DOMSVGAnimatedInteger { + DOMAnimatedInteger(SVGAnimatedIntegerPair* aVal, PairIndex aIndex, + SVGElement* aSVGElement) + : dom::DOMSVGAnimatedInteger(aSVGElement), mVal(aVal), mIndex(aIndex) {} + virtual ~DOMAnimatedInteger(); + + SVGAnimatedIntegerPair* mVal; // kept alive because it belongs to content + PairIndex mIndex; // are we the first or second integer + + int32_t BaseVal() override { return mVal->GetBaseValue(mIndex); } + void SetBaseVal(int32_t aValue) override { + mVal->SetBaseValue(aValue, mIndex, mSVGElement); + } + + // Script may have modified animation parameters or timeline -- DOM getters + // need to flush any resample requests to reflect these modifications. + int32_t AnimVal() override { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(mIndex); + } + }; + + struct SMILIntegerPair : public SMILAttr { + public: + SMILIntegerPair(SVGAnimatedIntegerPair* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedIntegerPair* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDINTEGERPAIR_H_ diff --git a/dom/svg/SVGAnimatedLength.cpp b/dom/svg/SVGAnimatedLength.cpp new file mode 100644 index 0000000000..ae1d51d813 --- /dev/null +++ b/dom/svg/SVGAnimatedLength.cpp @@ -0,0 +1,513 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedLength.h" + +#include "mozAutoDocUpdate.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/dom/SVGViewportElement.h" +#include "DOMSVGAnimatedLength.h" +#include "DOMSVGLength.h" +#include "LayoutLogging.h" +#include "nsContentUtils.h" +#include "nsIFrame.h" +#include "nsTextFormatter.h" +#include "SMILFloatType.h" +#include "SVGAttrTearoffTable.h" + +using namespace mozilla::dom; + +namespace mozilla { + +//---------------------------------------------------------------------- +// Helper class: AutoChangeLengthNotifier +// Stack-based helper class to pair calls to WillChangeLength and +// DidChangeLength. +class MOZ_RAII AutoChangeLengthNotifier { + public: + AutoChangeLengthNotifier(SVGAnimatedLength* aLength, SVGElement* aSVGElement, + bool aDoSetAttr = true) + : mLength(aLength), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mLength, "Expecting non-null length"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + + if (mDoSetAttr) { + mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true); + mEmptyOrOldValue = + mSVGElement->WillChangeLength(mLength->mAttrEnum, mUpdateBatch.ref()); + } + } + + ~AutoChangeLengthNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangeLength(mLength->mAttrEnum, mEmptyOrOldValue, + mUpdateBatch.ref()); + } + if (mLength->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedLength* const mLength; + SVGElement* const mSVGElement; + Maybe<mozAutoDocUpdate> mUpdateBatch; + nsAttrValue mEmptyOrOldValue; + bool mDoSetAttr; +}; + +static const nsStaticAtom* const unitMap[] = { + nullptr, /* SVG_LENGTHTYPE_UNKNOWN */ + nullptr, /* SVG_LENGTHTYPE_NUMBER */ + nsGkAtoms::percentage, + nsGkAtoms::em, + nsGkAtoms::ex, + nsGkAtoms::px, + nsGkAtoms::cm, + nsGkAtoms::mm, + nsGkAtoms::in, + nsGkAtoms::pt, + nsGkAtoms::pc}; + +static SVGAttrTearoffTable<SVGAnimatedLength, DOMSVGAnimatedLength> + sSVGAnimatedLengthTearoffTable; + +/* Helper functions */ + +static bool IsValidUnitType(uint16_t unit) { + return unit > SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN && + unit <= SVGLength_Binding::SVG_LENGTHTYPE_PC; +} + +static void GetUnitString(nsAString& unit, uint16_t unitType) { + if (IsValidUnitType(unitType)) { + if (unitMap[unitType]) { + unitMap[unitType]->ToString(unit); + } + return; + } + + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); +} + +static uint16_t GetUnitTypeForString(const nsAString& unitStr) { + if (unitStr.IsEmpty()) return SVGLength_Binding::SVG_LENGTHTYPE_NUMBER; + + nsAtom* unitAtom = NS_GetStaticAtom(unitStr); + if (unitAtom) { + for (uint32_t i = 0; i < ArrayLength(unitMap); i++) { + if (unitMap[i] == unitAtom) { + return i; + } + } + } + + return SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN; +} + +static void GetValueString(nsAString& aValueAsString, float aValue, + uint16_t aUnitType) { + nsTextFormatter::ssprintf(aValueAsString, u"%g", (double)aValue); + + nsAutoString unitString; + GetUnitString(unitString, aUnitType); + aValueAsString.Append(unitString); +} + +static bool GetValueFromString(const nsAString& aString, float& aValue, + uint16_t* aUnitType) { + bool success; + auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); + + if (!success) { + return false; + } + + RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(token); + + if (!SVGContentUtils::ParseNumber(iter, end, aValue)) { + return false; + } + const nsAString& units = Substring(iter.get(), end.get()); + *aUnitType = GetUnitTypeForString(units); + return IsValidUnitType(*aUnitType); +} + +static float FixAxisLength(float aLength) { + if (aLength == 0.0f) { + LAYOUT_WARNING("zero axis length"); + return 1e-20f; + } + return aLength; +} + +SVGElementMetrics::SVGElementMetrics(SVGElement* aSVGElement, + SVGViewportElement* aCtx) + : mSVGElement(aSVGElement), mCtx(aCtx) {} + +float SVGElementMetrics::GetEmLength() const { + return SVGContentUtils::GetFontSize(mSVGElement); +} + +float SVGElementMetrics::GetExLength() const { + return SVGContentUtils::GetFontXHeight(mSVGElement); +} + +float SVGElementMetrics::GetAxisLength(uint8_t aCtxType) const { + if (!EnsureCtx()) { + return 1; + } + + return FixAxisLength(mCtx->GetLength(aCtxType)); +} + +bool SVGElementMetrics::EnsureCtx() const { + if (!mCtx && mSVGElement) { + mCtx = mSVGElement->GetCtx(); + if (!mCtx && mSVGElement->IsSVGElement(nsGkAtoms::svg)) { + auto* e = static_cast<SVGViewportElement*>(mSVGElement); + + if (!e->IsInner()) { + // mSVGElement must be the outer svg element + mCtx = e; + } + } + } + return mCtx != nullptr; +} + +NonSVGFrameUserSpaceMetrics::NonSVGFrameUserSpaceMetrics(nsIFrame* aFrame) + : mFrame(aFrame) {} + +float NonSVGFrameUserSpaceMetrics::GetEmLength() const { + return SVGContentUtils::GetFontSize(mFrame); +} + +float NonSVGFrameUserSpaceMetrics::GetExLength() const { + return SVGContentUtils::GetFontXHeight(mFrame); +} + +gfx::Size NonSVGFrameUserSpaceMetrics::GetSize() const { + return SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(mFrame); +} + +float UserSpaceMetricsWithSize::GetAxisLength(uint8_t aCtxType) const { + gfx::Size size = GetSize(); + float length; + switch (aCtxType) { + case SVGContentUtils::X: + length = size.width; + break; + case SVGContentUtils::Y: + length = size.height; + break; + case SVGContentUtils::XY: + length = + SVGContentUtils::ComputeNormalizedHypotenuse(size.width, size.height); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown axis type"); + length = 1; + break; + } + return FixAxisLength(length); +} + +float SVGAnimatedLength::GetPixelsPerUnit(SVGElement* aSVGElement, + uint8_t aUnitType) const { + return GetPixelsPerUnit(SVGElementMetrics(aSVGElement), aUnitType); +} + +float SVGAnimatedLength::GetPixelsPerUnit(SVGViewportElement* aCtx, + uint8_t aUnitType) const { + return GetPixelsPerUnit(SVGElementMetrics(aCtx, aCtx), aUnitType); +} + +float SVGAnimatedLength::GetPixelsPerUnit(nsIFrame* aFrame, + uint8_t aUnitType) const { + nsIContent* content = aFrame->GetContent(); + if (content->IsSVGElement()) { + return GetPixelsPerUnit( + SVGElementMetrics(static_cast<SVGElement*>(content)), aUnitType); + } + return GetPixelsPerUnit(NonSVGFrameUserSpaceMetrics(aFrame), aUnitType); +} + +// See https://www.w3.org/TR/css-values-3/#absolute-lengths +static const float DPI = 96.0f; + +bool UserSpaceMetrics::ResolveAbsoluteUnit(uint8_t aUnitType, float& aRes) { + switch (aUnitType) { + case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER: + case SVGLength_Binding::SVG_LENGTHTYPE_PX: + aRes = 1; + break; + case SVGLength_Binding::SVG_LENGTHTYPE_MM: + aRes = DPI / MM_PER_INCH_FLOAT; + break; + case SVGLength_Binding::SVG_LENGTHTYPE_CM: + aRes = 10.0f * DPI / MM_PER_INCH_FLOAT; + break; + case SVGLength_Binding::SVG_LENGTHTYPE_IN: + aRes = DPI; + break; + case SVGLength_Binding::SVG_LENGTHTYPE_PT: + aRes = DPI / POINTS_PER_INCH_FLOAT; + break; + case SVGLength_Binding::SVG_LENGTHTYPE_PC: + aRes = 12.0f * DPI / POINTS_PER_INCH_FLOAT; + break; + default: + return false; + } + return true; +} + +float SVGAnimatedLength::GetPixelsPerUnit(const UserSpaceMetrics& aMetrics, + uint8_t aUnitType) const { + float res; + if (UserSpaceMetrics::ResolveAbsoluteUnit(aUnitType, res)) { + return res; + } + switch (aUnitType) { + case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE: + return aMetrics.GetAxisLength(mCtxType) / 100.0f; + case SVGLength_Binding::SVG_LENGTHTYPE_EMS: + return aMetrics.GetEmLength(); + case SVGLength_Binding::SVG_LENGTHTYPE_EXS: + return aMetrics.GetExLength(); + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); + return 0; + } +} + +void SVGAnimatedLength::SetBaseValueInSpecifiedUnits(float aValue, + SVGElement* aSVGElement, + bool aDoSetAttr) { + if (mIsBaseSet && mBaseVal == aValue) { + return; + } + + AutoChangeLengthNotifier notifier(this, aSVGElement, aDoSetAttr); + + mBaseVal = aValue; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } +} + +nsresult SVGAnimatedLength::ConvertToSpecifiedUnits(uint16_t unitType, + SVGElement* aSVGElement) { + if (!IsValidUnitType(unitType)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + + if (mIsBaseSet && mSpecifiedUnitType == uint8_t(unitType)) return NS_OK; + + float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, unitType); + if (pixelsPerUnit == 0.0f) { + return NS_ERROR_ILLEGAL_VALUE; + } + + float valueInUserUnits = + mBaseVal * GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType); + float valueInSpecifiedUnits = valueInUserUnits / pixelsPerUnit; + + if (!IsFinite(valueInSpecifiedUnits)) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // Even though we're not changing the visual effect this length will have + // on the document, we still need to send out notifications in case we have + // mutation listeners, since the actual string value of the attribute will + // change. + AutoChangeLengthNotifier notifier(this, aSVGElement); + + mSpecifiedUnitType = uint8_t(unitType); + // Setting aDoSetAttr to false here will ensure we don't call + // Will/DidChangeAngle a second time (and dispatch duplicate notifications). + SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement, false); + + return NS_OK; +} + +nsresult SVGAnimatedLength::NewValueSpecifiedUnits(uint16_t aUnitType, + float aValueInSpecifiedUnits, + SVGElement* aSVGElement) { + NS_ENSURE_FINITE(aValueInSpecifiedUnits, NS_ERROR_ILLEGAL_VALUE); + + if (!IsValidUnitType(aUnitType)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + + if (mIsBaseSet && mBaseVal == aValueInSpecifiedUnits && + mSpecifiedUnitType == uint8_t(aUnitType)) { + return NS_OK; + } + + AutoChangeLengthNotifier notifier(this, aSVGElement); + + mBaseVal = aValueInSpecifiedUnits; + mIsBaseSet = true; + mSpecifiedUnitType = uint8_t(aUnitType); + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } + return NS_OK; +} + +already_AddRefed<DOMSVGLength> SVGAnimatedLength::ToDOMBaseVal( + SVGElement* aSVGElement) { + return DOMSVGLength::GetTearOff(this, aSVGElement, false); +} + +already_AddRefed<DOMSVGLength> SVGAnimatedLength::ToDOMAnimVal( + SVGElement* aSVGElement) { + return DOMSVGLength::GetTearOff(this, aSVGElement, true); +} + +/* Implementation */ + +nsresult SVGAnimatedLength::SetBaseValueString(const nsAString& aValueAsString, + SVGElement* aSVGElement, + bool aDoSetAttr) { + float value; + uint16_t unitType; + + if (!GetValueFromString(aValueAsString, value, &unitType)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + if (mIsBaseSet && mBaseVal == float(value) && + mSpecifiedUnitType == uint8_t(unitType)) { + return NS_OK; + } + + AutoChangeLengthNotifier notifier(this, aSVGElement, aDoSetAttr); + + mBaseVal = value; + mIsBaseSet = true; + mSpecifiedUnitType = uint8_t(unitType); + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } + + return NS_OK; +} + +void SVGAnimatedLength::GetBaseValueString(nsAString& aValueAsString) const { + GetValueString(aValueAsString, mBaseVal, mSpecifiedUnitType); +} + +void SVGAnimatedLength::GetAnimValueString(nsAString& aValueAsString) const { + GetValueString(aValueAsString, mAnimVal, mSpecifiedUnitType); +} + +nsresult SVGAnimatedLength::SetBaseValue(float aValue, SVGElement* aSVGElement, + bool aDoSetAttr) { + float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType); + if (pixelsPerUnit == 0.0f) { + return NS_ERROR_ILLEGAL_VALUE; + } + + float valueInSpecifiedUnits = aValue / pixelsPerUnit; + if (!IsFinite(valueInSpecifiedUnits)) { + return NS_ERROR_ILLEGAL_VALUE; + } + + SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement, aDoSetAttr); + return NS_OK; +} + +void SVGAnimatedLength::SetAnimValueInSpecifiedUnits(float aValue, + SVGElement* aSVGElement) { + if (mAnimVal == aValue && mIsAnimated) { + return; + } + mAnimVal = aValue; + mIsAnimated = true; + aSVGElement->DidAnimateLength(mAttrEnum); +} + +nsresult SVGAnimatedLength::SetAnimValue(float aValue, + SVGElement* aSVGElement) { + float valueInSpecifiedUnits = + aValue / GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType); + + if (IsFinite(valueInSpecifiedUnits)) { + SetAnimValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement); + return NS_OK; + } + return NS_ERROR_ILLEGAL_VALUE; +} + +already_AddRefed<DOMSVGAnimatedLength> SVGAnimatedLength::ToDOMAnimatedLength( + SVGElement* aSVGElement) { + RefPtr<DOMSVGAnimatedLength> svgAnimatedLength = + sSVGAnimatedLengthTearoffTable.GetTearoff(this); + if (!svgAnimatedLength) { + svgAnimatedLength = new DOMSVGAnimatedLength(this, aSVGElement); + sSVGAnimatedLengthTearoffTable.AddTearoff(this, svgAnimatedLength); + } + + return svgAnimatedLength.forget(); +} + +DOMSVGAnimatedLength::~DOMSVGAnimatedLength() { + sSVGAnimatedLengthTearoffTable.RemoveTearoff(mVal); +} + +UniquePtr<SMILAttr> SVGAnimatedLength::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILLength>(this, aSVGElement); +} + +nsresult SVGAnimatedLength::SMILLength::ValueFromString( + const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + float value; + uint16_t unitType; + + if (!GetValueFromString(aStr, value, &unitType)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + SMILValue val(SMILFloatType::Singleton()); + val.mU.mDouble = value * mVal->GetPixelsPerUnit(mSVGElement, unitType); + aValue = val; + aPreventCachingOfSandwich = + (unitType == SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE || + unitType == SVGLength_Binding::SVG_LENGTHTYPE_EMS || + unitType == SVGLength_Binding::SVG_LENGTHTYPE_EXS); + + return NS_OK; +} + +SMILValue SVGAnimatedLength::SMILLength::GetBaseValue() const { + SMILValue val(SMILFloatType::Singleton()); + val.mU.mDouble = mVal->GetBaseValue(mSVGElement); + return val; +} + +void SVGAnimatedLength::SMILLength::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal = mVal->mBaseVal; + mSVGElement->DidAnimateLength(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedLength::SMILLength::SetAnimValue(const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILFloatType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILFloatType::Singleton()) { + return mVal->SetAnimValue(float(aValue.mU.mDouble), mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedLength.h b/dom/svg/SVGAnimatedLength.h new file mode 100644 index 0000000000..1cabfba040 --- /dev/null +++ b/dom/svg/SVGAnimatedLength.h @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDLENGTH_H_ +#define DOM_SVG_SVGANIMATEDLENGTH_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/gfx/Rect.h" +#include "nsCoord.h" +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "nsMathUtils.h" + +class mozAutoDocUpdate; +class nsIFrame; + +namespace mozilla { + +class AutoChangeLengthNotifier; +class SMILValue; + +namespace dom { +class DOMSVGAnimatedLength; +class DOMSVGLength; +class SVGAnimationElement; +class SVGViewportElement; + +class UserSpaceMetrics { + public: + static bool ResolveAbsoluteUnit(uint8_t aUnitType, float& aRes); + virtual ~UserSpaceMetrics() = default; + + virtual float GetEmLength() const = 0; + virtual float GetExLength() const = 0; + virtual float GetAxisLength(uint8_t aCtxType) const = 0; +}; + +class UserSpaceMetricsWithSize : public UserSpaceMetrics { + public: + virtual gfx::Size GetSize() const = 0; + float GetAxisLength(uint8_t aCtxType) const override; +}; + +class SVGElementMetrics : public UserSpaceMetrics { + public: + explicit SVGElementMetrics(SVGElement* aSVGElement, + SVGViewportElement* aCtx = nullptr); + + float GetEmLength() const override; + float GetExLength() const override; + float GetAxisLength(uint8_t aCtxType) const override; + + private: + bool EnsureCtx() const; + + SVGElement* mSVGElement; + mutable SVGViewportElement* mCtx; +}; + +class NonSVGFrameUserSpaceMetrics : public UserSpaceMetricsWithSize { + public: + explicit NonSVGFrameUserSpaceMetrics(nsIFrame* aFrame); + + float GetEmLength() const override; + float GetExLength() const override; + gfx::Size GetSize() const override; + + private: + nsIFrame* mFrame; +}; + +} // namespace dom + +class SVGAnimatedLength { + friend class AutoChangeLengthNotifier; + friend class dom::DOMSVGAnimatedLength; + friend class dom::DOMSVGLength; + using DOMSVGLength = dom::DOMSVGLength; + using SVGElement = dom::SVGElement; + using SVGViewportElement = dom::SVGViewportElement; + using UserSpaceMetrics = dom::UserSpaceMetrics; + + public: + void Init(uint8_t aCtxType = SVGContentUtils::XY, uint8_t aAttrEnum = 0xff, + float aValue = 0, + uint8_t aUnitType = dom::SVGLength_Binding::SVG_LENGTHTYPE_NUMBER) { + mAnimVal = mBaseVal = aValue; + mSpecifiedUnitType = aUnitType; + mAttrEnum = aAttrEnum; + mCtxType = aCtxType; + mIsAnimated = false; + mIsBaseSet = false; + } + + SVGAnimatedLength& operator=(const SVGAnimatedLength& aLength) { + mBaseVal = aLength.mBaseVal; + mAnimVal = aLength.mAnimVal; + mSpecifiedUnitType = aLength.mSpecifiedUnitType; + mIsAnimated = aLength.mIsAnimated; + mIsBaseSet = aLength.mIsBaseSet; + return *this; + } + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement, + bool aDoSetAttr); + void GetBaseValueString(nsAString& aValue) const; + void GetAnimValueString(nsAString& aValue) const; + + float GetBaseValue(SVGElement* aSVGElement) const { + return mBaseVal * GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType); + } + + float GetAnimValue(SVGElement* aSVGElement) const { + return mAnimVal * GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType); + } + float GetAnimValue(nsIFrame* aFrame) const { + return mAnimVal * GetPixelsPerUnit(aFrame, mSpecifiedUnitType); + } + float GetAnimValue(SVGViewportElement* aCtx) const { + return mAnimVal * GetPixelsPerUnit(aCtx, mSpecifiedUnitType); + } + float GetAnimValue(const UserSpaceMetrics& aMetrics) const { + return mAnimVal * GetPixelsPerUnit(aMetrics, mSpecifiedUnitType); + } + + uint8_t GetCtxType() const { return mCtxType; } + uint8_t GetSpecifiedUnitType() const { return mSpecifiedUnitType; } + bool IsPercentage() const { + return mSpecifiedUnitType == + dom::SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE; + } + float GetAnimValInSpecifiedUnits() const { return mAnimVal; } + float GetBaseValInSpecifiedUnits() const { return mBaseVal; } + + float GetBaseValue(SVGViewportElement* aCtx) const { + return mBaseVal * GetPixelsPerUnit(aCtx, mSpecifiedUnitType); + } + + bool HasBaseVal() const { return mIsBaseSet; } + // Returns true if the animated value of this length has been explicitly + // set (either by animation, or by taking on the base value which has been + // explicitly set by markup or a DOM call), false otherwise. + // If this returns false, the animated value is still valid, that is, + // usable, and represents the default base value of the attribute. + bool IsExplicitlySet() const { return mIsAnimated || mIsBaseSet; } + + bool IsAnimated() const { return mIsAnimated; } + + already_AddRefed<dom::DOMSVGAnimatedLength> ToDOMAnimatedLength( + SVGElement* aSVGElement); + + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + float mAnimVal; + float mBaseVal; + uint8_t mSpecifiedUnitType; + uint8_t mAttrEnum; // element specified tracking for attribute + uint8_t mCtxType; // X, Y or Unspecified + bool mIsAnimated : 1; + bool mIsBaseSet : 1; + + // These APIs returns the number of user-unit pixels per unit of the + // given type, in a given context (frame/element/etc). + float GetPixelsPerUnit(nsIFrame* aFrame, uint8_t aUnitType) const; + float GetPixelsPerUnit(const UserSpaceMetrics& aMetrics, + uint8_t aUnitType) const; + float GetPixelsPerUnit(SVGElement* aSVGElement, uint8_t aUnitType) const; + float GetPixelsPerUnit(SVGViewportElement* aCtx, uint8_t aUnitType) const; + + // SetBaseValue and SetAnimValue set the value in user units. This may fail + // if unit conversion fails e.g. conversion to ex or em units where the + // font-size is 0. + // SetBaseValueInSpecifiedUnits and SetAnimValueInSpecifiedUnits do not + // perform unit conversion and are therefore infallible. + nsresult SetBaseValue(float aValue, SVGElement* aSVGElement, bool aDoSetAttr); + void SetBaseValueInSpecifiedUnits(float aValue, SVGElement* aSVGElement, + bool aDoSetAttr); + nsresult SetAnimValue(float aValue, SVGElement* aSVGElement); + void SetAnimValueInSpecifiedUnits(float aValue, SVGElement* aSVGElement); + nsresult NewValueSpecifiedUnits(uint16_t aUnitType, + float aValueInSpecifiedUnits, + SVGElement* aSVGElement); + nsresult ConvertToSpecifiedUnits(uint16_t aUnitType, SVGElement* aSVGElement); + already_AddRefed<DOMSVGLength> ToDOMBaseVal(SVGElement* aSVGElement); + already_AddRefed<DOMSVGLength> ToDOMAnimVal(SVGElement* aSVGElement); + + public: + struct SMILLength : public SMILAttr { + public: + SMILLength(SVGAnimatedLength* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedLength* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDLENGTH_H_ diff --git a/dom/svg/SVGAnimatedLengthList.cpp b/dom/svg/SVGAnimatedLengthList.cpp new file mode 100644 index 0000000000..6f6dccbe6d --- /dev/null +++ b/dom/svg/SVGAnimatedLengthList.cpp @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedLengthList.h" + +#include <utility> + +#include "DOMSVGAnimatedLengthList.h" +#include "SVGLengthListSMILType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGLengthBinding.h" + +namespace mozilla { + +using namespace dom; + +nsresult SVGAnimatedLengthList::SetBaseValueString(const nsAString& aValue) { + SVGLengthList newBaseValue; + nsresult rv = newBaseValue.SetValueFromString(aValue); + if (NS_FAILED(rv)) { + return rv; + } + + DOMSVGAnimatedLengthList* domWrapper = + DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // We must send this notification *before* changing mBaseVal! If the length + // of our baseVal is being reduced, our baseVal's DOM wrapper list may have + // to remove DOM items from itself, and any removed DOM items need to copy + // their internal counterpart values *before* we change them. + // + domWrapper->InternalBaseValListWillChangeTo(newBaseValue); + } + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + + rv = mBaseVal.CopyFrom(newBaseValue); + if (NS_FAILED(rv) && domWrapper) { + // Attempting to increase mBaseVal's length failed - reduce domWrapper + // back to the same length: + domWrapper->InternalBaseValListWillChangeTo(mBaseVal); + } + return rv; +} + +void SVGAnimatedLengthList::ClearBaseValue(uint32_t aAttrEnum) { + DOMSVGAnimatedLengthList* domWrapper = + DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // We must send this notification *before* changing mBaseVal! (See above.) + domWrapper->InternalBaseValListWillChangeTo(SVGLengthList()); + } + mBaseVal.Clear(); + // Caller notifies +} + +nsresult SVGAnimatedLengthList::SetAnimValue(const SVGLengthList& aNewAnimValue, + SVGElement* aElement, + uint32_t aAttrEnum) { + DOMSVGAnimatedLengthList* domWrapper = + DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // A new animation may totally change the number of items in the animVal + // list, replacing what was essentially a mirror of the baseVal list, or + // else replacing and overriding an existing animation. When this happens + // we must try and keep our animVal's DOM wrapper in sync (see the comment + // in DOMSVGAnimatedLengthList::InternalBaseValListWillChangeTo). + // + // It's not possible for us to reliably distinguish between calls to this + // method that are setting a new sample for an existing animation, and + // calls that are setting the first sample of an animation that will + // override an existing animation. Happily it's cheap to just blindly + // notify our animVal's DOM wrapper of its internal counterpart's new value + // each time this method is called, so that's what we do. + // + // Note that we must send this notification *before* setting or changing + // mAnimVal! (See the comment in SetBaseValueString above.) + // + domWrapper->InternalAnimValListWillChangeTo(aNewAnimValue); + } + if (!mAnimVal) { + mAnimVal = MakeUnique<SVGLengthList>(); + } + nsresult rv = mAnimVal->CopyFrom(aNewAnimValue); + if (NS_FAILED(rv)) { + // OOM. We clear the animation, and, importantly, ClearAnimValue() ensures + // that mAnimVal and its DOM wrapper (if any) will have the same length! + ClearAnimValue(aElement, aAttrEnum); + return rv; + } + aElement->DidAnimateLengthList(aAttrEnum); + return NS_OK; +} + +void SVGAnimatedLengthList::ClearAnimValue(SVGElement* aElement, + uint32_t aAttrEnum) { + DOMSVGAnimatedLengthList* domWrapper = + DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // When all animation ends, animVal simply mirrors baseVal, which may have + // a different number of items to the last active animated value. We must + // keep the length of our animVal's DOM wrapper list in sync, and again we + // must do that before touching mAnimVal. See comments above. + // + domWrapper->InternalAnimValListWillChangeTo(mBaseVal); + } + mAnimVal = nullptr; + aElement->DidAnimateLengthList(aAttrEnum); +} + +UniquePtr<SMILAttr> SVGAnimatedLengthList::ToSMILAttr(SVGElement* aSVGElement, + uint8_t aAttrEnum, + uint8_t aAxis, + bool aCanZeroPadList) { + return MakeUnique<SMILAnimatedLengthList>(this, aSVGElement, aAttrEnum, aAxis, + aCanZeroPadList); +} + +nsresult SVGAnimatedLengthList::SMILAnimatedLengthList::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SMILValue val(&SVGLengthListSMILType::sSingleton); + SVGLengthListAndInfo* llai = static_cast<SVGLengthListAndInfo*>(val.mU.mPtr); + nsresult rv = llai->SetValueFromString(aStr); + if (NS_SUCCEEDED(rv)) { + llai->SetInfo(mElement, mAxis, mCanZeroPadList); + aValue = std::move(val); + + // If any of the lengths in the list depend on their context, then we must + // prevent caching of the entire animation sandwich. This is because the + // units of a length at a given index can change from sandwich layer to + // layer, and indeed even be different within a single sandwich layer. If + // any length in the result of an animation sandwich is the result of the + // addition of lengths where one or more of those lengths is context + // dependent, then naturally the resultant length is also context + // dependent, regardless of whether its actual unit is context dependent or + // not. Unfortunately normal invalidation mechanisms won't cause us to + // recalculate the result of the sandwich if the context changes, so we + // take the (substantial) performance hit of preventing caching of the + // sandwich layer, causing the animation sandwich to be recalculated every + // single sample. + + aPreventCachingOfSandwich = false; + for (uint32_t i = 0; i < llai->Length(); ++i) { + uint8_t unit = (*llai)[i].GetUnit(); + if (unit == SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE || + unit == SVGLength_Binding::SVG_LENGTHTYPE_EMS || + unit == SVGLength_Binding::SVG_LENGTHTYPE_EXS) { + aPreventCachingOfSandwich = true; + break; + } + } + } + return rv; +} + +SMILValue SVGAnimatedLengthList::SMILAnimatedLengthList::GetBaseValue() const { + // To benefit from Return Value Optimization and avoid copy constructor calls + // due to our use of return-by-value, we must return the exact same object + // from ALL return points. This function must only return THIS variable: + SMILValue val; + + SMILValue tmp(&SVGLengthListSMILType::sSingleton); + SVGLengthListAndInfo* llai = static_cast<SVGLengthListAndInfo*>(tmp.mU.mPtr); + nsresult rv = llai->CopyFrom(mVal->mBaseVal); + if (NS_SUCCEEDED(rv)) { + llai->SetInfo(mElement, mAxis, mCanZeroPadList); + val = std::move(tmp); + } + return val; +} + +nsresult SVGAnimatedLengthList::SMILAnimatedLengthList::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == &SVGLengthListSMILType::sSingleton, + "Unexpected type to assign animated value"); + if (aValue.mType == &SVGLengthListSMILType::sSingleton) { + mVal->SetAnimValue(*static_cast<SVGLengthListAndInfo*>(aValue.mU.mPtr), + mElement, mAttrEnum); + } + return NS_OK; +} + +void SVGAnimatedLengthList::SMILAnimatedLengthList::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->ClearAnimValue(mElement, mAttrEnum); + } +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedLengthList.h b/dom/svg/SVGAnimatedLengthList.h new file mode 100644 index 0000000000..220a7e24db --- /dev/null +++ b/dom/svg/SVGAnimatedLengthList.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDLENGTHLIST_H_ +#define DOM_SVG_SVGANIMATEDLENGTHLIST_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "SVGLengthList.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +/** + * Class SVGAnimatedLengthList + * + * This class is very different to the SVG DOM interface of the same name found + * in the SVG specification. This is a lightweight internal class - see + * DOMSVGAnimatedLengthList for the heavier DOM class that wraps instances of + * this class and implements the SVG specification's SVGAnimatedLengthList DOM + * interface. + * + * Except where noted otherwise, this class' methods take care of keeping the + * appropriate DOM wrappers in sync (see the comment in + * DOMSVGAnimatedLengthList::InternalBaseValListWillChangeTo) so that their + * consumers don't need to concern themselves with that. + */ +class SVGAnimatedLengthList { + // friends so that they can get write access to mBaseVal + friend class dom::DOMSVGLength; + friend class dom::DOMSVGLengthList; + + public: + SVGAnimatedLengthList() = default; + + SVGAnimatedLengthList& operator=(const SVGAnimatedLengthList& aOther) { + mBaseVal = aOther.mBaseVal; + if (aOther.mAnimVal) { + mAnimVal = MakeUnique<SVGLengthList>(*aOther.mAnimVal); + } + return *this; + } + + /** + * Because it's so important that mBaseVal and its DOMSVGLengthList wrapper + * (if any) be kept in sync (see the comment in + * DOMSVGAnimatedLengthList::InternalBaseValListWillChangeTo), this method + * returns a const reference. Only our friend classes may get mutable + * references to mBaseVal. + */ + const SVGLengthList& GetBaseValue() const { return mBaseVal; } + + nsresult SetBaseValueString(const nsAString& aValue); + + void ClearBaseValue(uint32_t aAttrEnum); + + const SVGLengthList& GetAnimValue() const { + return mAnimVal ? *mAnimVal : mBaseVal; + } + + nsresult SetAnimValue(const SVGLengthList& aNewAnimValue, + dom::SVGElement* aElement, uint32_t aAttrEnum); + + void ClearAnimValue(dom::SVGElement* aElement, uint32_t aAttrEnum); + + bool IsAnimating() const { return !!mAnimVal; } + + UniquePtr<SMILAttr> ToSMILAttr(dom::SVGElement* aSVGElement, + uint8_t aAttrEnum, uint8_t aAxis, + bool aCanZeroPadList); + + private: + // mAnimVal is a pointer to allow us to determine if we're being animated or + // not. Making it a non-pointer member and using mAnimVal.IsEmpty() to check + // if we're animating is not an option, since that would break animation *to* + // the empty string (<set to="">). + + SVGLengthList mBaseVal; + UniquePtr<SVGLengthList> mAnimVal; + + struct SMILAnimatedLengthList : public SMILAttr { + public: + SMILAnimatedLengthList(SVGAnimatedLengthList* aVal, + dom::SVGElement* aSVGElement, uint8_t aAttrEnum, + uint8_t aAxis, bool aCanZeroPadList) + : mVal(aVal), + mElement(aSVGElement), + mAttrEnum(aAttrEnum), + mAxis(aAxis), + mCanZeroPadList(aCanZeroPadList) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedLengthList* mVal; + dom::SVGElement* mElement; + uint8_t mAttrEnum; + uint8_t mAxis; + bool mCanZeroPadList; // See SVGLengthListAndInfo::CanZeroPadList + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDLENGTHLIST_H_ diff --git a/dom/svg/SVGAnimatedNumber.cpp b/dom/svg/SVGAnimatedNumber.cpp new file mode 100644 index 0000000000..246f49db74 --- /dev/null +++ b/dom/svg/SVGAnimatedNumber.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedNumber.h" + +#include "mozilla/Attributes.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGContentUtils.h" +#include "nsContentUtils.h" +#include "SMILFloatType.h" +#include "SVGAttrTearoffTable.h" + +using namespace mozilla::dom; + +namespace mozilla { + +/* Implementation */ + +//---------------------------------------------------------------------- +// Helper class: AutoChangeNumberNotifier +// Stack-based helper class to ensure DidChangeNumber is called. +class MOZ_RAII AutoChangeNumberNotifier { + public: + AutoChangeNumberNotifier(SVGAnimatedNumber* aNumber, SVGElement* aSVGElement) + : mNumber(aNumber), mSVGElement(aSVGElement) { + MOZ_ASSERT(mNumber, "Expecting non-null number"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + } + + ~AutoChangeNumberNotifier() { + mSVGElement->DidChangeNumber(mNumber->mAttrEnum); + if (mNumber->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedNumber* const mNumber; + SVGElement* const mSVGElement; +}; + +static SVGAttrTearoffTable<SVGAnimatedNumber, + SVGAnimatedNumber::DOMAnimatedNumber> + sSVGAnimatedNumberTearoffTable; + +static bool GetValueFromString(const nsAString& aString, + bool aPercentagesAllowed, float& aValue) { + bool success; + auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); + + if (!success) { + return false; + } + + RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(token); + + if (!SVGContentUtils::ParseNumber(iter, end, aValue)) { + return false; + } + + if (aPercentagesAllowed) { + const nsAString& units = Substring(iter.get(), end.get()); + if (units.EqualsLiteral("%")) { + aValue /= 100; + return true; + } + } + + return iter == end; +} + +nsresult SVGAnimatedNumber::SetBaseValueString(const nsAString& aValueAsString, + SVGElement* aSVGElement) { + float val; + + if (!GetValueFromString(aValueAsString, + aSVGElement->NumberAttrAllowsPercentage(mAttrEnum), + val)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + mBaseVal = val; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } else { + aSVGElement->AnimationNeedsResample(); + } + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + return NS_OK; +} + +void SVGAnimatedNumber::GetBaseValueString(nsAString& aValueAsString) { + aValueAsString.Truncate(); + aValueAsString.AppendFloat(mBaseVal); +} + +void SVGAnimatedNumber::SetBaseValue(float aValue, SVGElement* aSVGElement) { + if (mIsBaseSet && aValue == mBaseVal) { + return; + } + + AutoChangeNumberNotifier notifier(this, aSVGElement); + + mBaseVal = aValue; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } +} + +void SVGAnimatedNumber::SetAnimValue(float aValue, SVGElement* aSVGElement) { + if (mIsAnimated && aValue == mAnimVal) { + return; + } + mAnimVal = aValue; + mIsAnimated = true; + aSVGElement->DidAnimateNumber(mAttrEnum); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGAnimatedNumber::ToDOMAnimatedNumber( + SVGElement* aSVGElement) { + RefPtr<DOMAnimatedNumber> domAnimatedNumber = + sSVGAnimatedNumberTearoffTable.GetTearoff(this); + if (!domAnimatedNumber) { + domAnimatedNumber = new DOMAnimatedNumber(this, aSVGElement); + sSVGAnimatedNumberTearoffTable.AddTearoff(this, domAnimatedNumber); + } + + return domAnimatedNumber.forget(); +} + +SVGAnimatedNumber::DOMAnimatedNumber::~DOMAnimatedNumber() { + sSVGAnimatedNumberTearoffTable.RemoveTearoff(mVal); +} + +UniquePtr<SMILAttr> SVGAnimatedNumber::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILNumber>(this, aSVGElement); +} + +nsresult SVGAnimatedNumber::SMILNumber::ValueFromString( + const nsAString& aStr, + const mozilla::dom::SVGAnimationElement* /*aSrcElement*/, SMILValue& aValue, + bool& aPreventCachingOfSandwich) const { + float value; + + if (!GetValueFromString( + aStr, mSVGElement->NumberAttrAllowsPercentage(mVal->mAttrEnum), + value)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + SMILValue val(SMILFloatType::Singleton()); + val.mU.mDouble = value; + aValue = val; + aPreventCachingOfSandwich = false; + + return NS_OK; +} + +SMILValue SVGAnimatedNumber::SMILNumber::GetBaseValue() const { + SMILValue val(SMILFloatType::Singleton()); + val.mU.mDouble = mVal->mBaseVal; + return val; +} + +void SVGAnimatedNumber::SMILNumber::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal = mVal->mBaseVal; + mSVGElement->DidAnimateNumber(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedNumber::SMILNumber::SetAnimValue(const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILFloatType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILFloatType::Singleton()) { + mVal->SetAnimValue(float(aValue.mU.mDouble), mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedNumber.h b/dom/svg/SVGAnimatedNumber.h new file mode 100644 index 0000000000..34ed9049d3 --- /dev/null +++ b/dom/svg/SVGAnimatedNumber.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDNUMBER_H_ +#define DOM_SVG_SVGANIMATEDNUMBER_H_ + +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "nsMathUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/DOMSVGAnimatedNumber.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +} // namespace dom + +class SVGAnimatedNumber { + public: + friend class AutoChangeNumberNotifier; + using SVGElement = dom::SVGElement; + + void Init(uint8_t aAttrEnum = 0xff, float aValue = 0) { + mAnimVal = mBaseVal = aValue; + mAttrEnum = aAttrEnum; + mIsAnimated = false; + mIsBaseSet = false; + } + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement); + void GetBaseValueString(nsAString& aValue); + + void SetBaseValue(float aValue, SVGElement* aSVGElement); + float GetBaseValue() const { return mBaseVal; } + void SetAnimValue(float aValue, SVGElement* aSVGElement); + float GetAnimValue() const { return mAnimVal; } + + // Returns true if the animated value of this number has been explicitly + // set (either by animation, or by taking on the base value which has been + // explicitly set by markup or a DOM call), false otherwise. + // If this returns false, the animated value is still valid, that is, + // usable, and represents the default base value of the attribute. + bool IsExplicitlySet() const { return mIsAnimated || mIsBaseSet; } + + already_AddRefed<dom::DOMSVGAnimatedNumber> ToDOMAnimatedNumber( + SVGElement* aSVGElement); + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + float mAnimVal; + float mBaseVal; + uint8_t mAttrEnum; // element specified tracking for attribute + bool mIsAnimated; + bool mIsBaseSet; + + public: + // DOM wrapper class for the (DOM)SVGAnimatedNumber interface where the + // wrapped class is SVGAnimatedNumber. + struct DOMAnimatedNumber final : public dom::DOMSVGAnimatedNumber { + DOMAnimatedNumber(SVGAnimatedNumber* aVal, SVGElement* aSVGElement) + : dom::DOMSVGAnimatedNumber(aSVGElement), mVal(aVal) {} + virtual ~DOMAnimatedNumber(); + + SVGAnimatedNumber* mVal; // kept alive because it belongs to content + + float BaseVal() override { return mVal->GetBaseValue(); } + void SetBaseVal(float aValue) override { + MOZ_ASSERT(IsFinite(aValue)); + mVal->SetBaseValue(aValue, mSVGElement); + } + + // Script may have modified animation parameters or timeline -- DOM getters + // need to flush any resample requests to reflect these modifications. + float AnimVal() override { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(); + } + }; + + struct SMILNumber : public SMILAttr { + public: + SMILNumber(SVGAnimatedNumber* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedNumber* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + virtual nsresult ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDNUMBER_H_ diff --git a/dom/svg/SVGAnimatedNumberList.cpp b/dom/svg/SVGAnimatedNumberList.cpp new file mode 100644 index 0000000000..837d260bb6 --- /dev/null +++ b/dom/svg/SVGAnimatedNumberList.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedNumberList.h" + +#include <utility> + +#include "DOMSVGAnimatedNumberList.h" +#include "SVGNumberListSMILType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGElement.h" + +using namespace mozilla::dom; + +namespace mozilla { + +nsresult SVGAnimatedNumberList::SetBaseValueString(const nsAString& aValue) { + SVGNumberList newBaseValue; + nsresult rv = newBaseValue.SetValueFromString(aValue); + if (NS_FAILED(rv)) { + return rv; + } + + DOMSVGAnimatedNumberList* domWrapper = + DOMSVGAnimatedNumberList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // We must send this notification *before* changing mBaseVal! If the length + // of our baseVal is being reduced, our baseVal's DOM wrapper list may have + // to remove DOM items from itself, and any removed DOM items need to copy + // their internal counterpart values *before* we change them. + // + domWrapper->InternalBaseValListWillChangeTo(newBaseValue); + } + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + + mIsBaseSet = true; + rv = mBaseVal.CopyFrom(newBaseValue); + if (NS_FAILED(rv) && domWrapper) { + // Attempting to increase mBaseVal's length failed - reduce domWrapper + // back to the same length: + domWrapper->InternalBaseValListWillChangeTo(mBaseVal); + } + return rv; +} + +void SVGAnimatedNumberList::ClearBaseValue(uint32_t aAttrEnum) { + DOMSVGAnimatedNumberList* domWrapper = + DOMSVGAnimatedNumberList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // We must send this notification *before* changing mBaseVal! (See above.) + domWrapper->InternalBaseValListWillChangeTo(SVGNumberList()); + } + mBaseVal.Clear(); + mIsBaseSet = false; + // Caller notifies +} + +nsresult SVGAnimatedNumberList::SetAnimValue(const SVGNumberList& aNewAnimValue, + SVGElement* aElement, + uint32_t aAttrEnum) { + DOMSVGAnimatedNumberList* domWrapper = + DOMSVGAnimatedNumberList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // A new animation may totally change the number of items in the animVal + // list, replacing what was essentially a mirror of the baseVal list, or + // else replacing and overriding an existing animation. When this happens + // we must try and keep our animVal's DOM wrapper in sync (see the comment + // in DOMSVGAnimatedNumberList::InternalBaseValListWillChangeTo). + // + // It's not possible for us to reliably distinguish between calls to this + // method that are setting a new sample for an existing animation, and + // calls that are setting the first sample of an animation that will + // override an existing animation. Happily it's cheap to just blindly + // notify our animVal's DOM wrapper of its internal counterpart's new value + // each time this method is called, so that's what we do. + // + // Note that we must send this notification *before* setting or changing + // mAnimVal! (See the comment in SetBaseValueString above.) + // + domWrapper->InternalAnimValListWillChangeTo(aNewAnimValue); + } + if (!mAnimVal) { + mAnimVal = MakeUnique<SVGNumberList>(); + } + nsresult rv = mAnimVal->CopyFrom(aNewAnimValue); + if (NS_FAILED(rv)) { + // OOM. We clear the animation, and, importantly, ClearAnimValue() ensures + // that mAnimVal and its DOM wrapper (if any) will have the same length! + ClearAnimValue(aElement, aAttrEnum); + return rv; + } + aElement->DidAnimateNumberList(aAttrEnum); + return NS_OK; +} + +void SVGAnimatedNumberList::ClearAnimValue(SVGElement* aElement, + uint32_t aAttrEnum) { + DOMSVGAnimatedNumberList* domWrapper = + DOMSVGAnimatedNumberList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // When all animation ends, animVal simply mirrors baseVal, which may have + // a different number of items to the last active animated value. We must + // keep the length of our animVal's DOM wrapper list in sync, and again we + // must do that before touching mAnimVal. See comments above. + // + domWrapper->InternalAnimValListWillChangeTo(mBaseVal); + } + mAnimVal = nullptr; + aElement->DidAnimateNumberList(aAttrEnum); +} + +UniquePtr<SMILAttr> SVGAnimatedNumberList::ToSMILAttr(SVGElement* aSVGElement, + uint8_t aAttrEnum) { + return MakeUnique<SMILAnimatedNumberList>(this, aSVGElement, aAttrEnum); +} + +nsresult SVGAnimatedNumberList::SMILAnimatedNumberList::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SMILValue val(&SVGNumberListSMILType::sSingleton); + SVGNumberListAndInfo* nlai = static_cast<SVGNumberListAndInfo*>(val.mU.mPtr); + nsresult rv = nlai->SetValueFromString(aStr); + if (NS_SUCCEEDED(rv)) { + nlai->SetInfo(mElement); + aValue = std::move(val); + } + aPreventCachingOfSandwich = false; + return rv; +} + +SMILValue SVGAnimatedNumberList::SMILAnimatedNumberList::GetBaseValue() const { + // To benefit from Return Value Optimization and avoid copy constructor calls + // due to our use of return-by-value, we must return the exact same object + // from ALL return points. This function must only return THIS variable: + SMILValue val; + + SMILValue tmp(&SVGNumberListSMILType::sSingleton); + SVGNumberListAndInfo* nlai = static_cast<SVGNumberListAndInfo*>(tmp.mU.mPtr); + nsresult rv = nlai->CopyFrom(mVal->mBaseVal); + if (NS_SUCCEEDED(rv)) { + nlai->SetInfo(mElement); + std::swap(val, tmp); + } + return val; +} + +nsresult SVGAnimatedNumberList::SMILAnimatedNumberList::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == &SVGNumberListSMILType::sSingleton, + "Unexpected type to assign animated value"); + if (aValue.mType == &SVGNumberListSMILType::sSingleton) { + mVal->SetAnimValue(*static_cast<SVGNumberListAndInfo*>(aValue.mU.mPtr), + mElement, mAttrEnum); + } + return NS_OK; +} + +void SVGAnimatedNumberList::SMILAnimatedNumberList::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->ClearAnimValue(mElement, mAttrEnum); + } +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedNumberList.h b/dom/svg/SVGAnimatedNumberList.h new file mode 100644 index 0000000000..2bc0438ad5 --- /dev/null +++ b/dom/svg/SVGAnimatedNumberList.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDNUMBERLIST_H_ +#define DOM_SVG_SVGANIMATEDNUMBERLIST_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "SVGNumberList.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +/** + * Class SVGAnimatedNumberList + * + * This class is very different to the SVG DOM interface of the same name found + * in the SVG specification. This is a lightweight internal class - see + * DOMSVGAnimatedNumberList for the heavier DOM class that wraps instances of + * this class and implements the SVG specification's SVGAnimatedNumberList DOM + * interface. + * + * Except where noted otherwise, this class' methods take care of keeping the + * appropriate DOM wrappers in sync (see the comment in + * DOMSVGAnimatedNumberList::InternalBaseValListWillChangeTo) so that their + * consumers don't need to concern themselves with that. + */ +class SVGAnimatedNumberList { + // friends so that they can get write access to mBaseVal + friend class dom::DOMSVGNumber; + friend class dom::DOMSVGNumberList; + + public: + SVGAnimatedNumberList() = default; + + SVGAnimatedNumberList& operator=(const SVGAnimatedNumberList& aOther) { + mIsBaseSet = aOther.mIsBaseSet; + mBaseVal = aOther.mBaseVal; + if (aOther.mAnimVal) { + mAnimVal = MakeUnique<SVGNumberList>(*aOther.mAnimVal); + } + return *this; + } + + /** + * Because it's so important that mBaseVal and its DOMSVGNumberList wrapper + * (if any) be kept in sync (see the comment in + * DOMSVGAnimatedNumberList::InternalBaseValListWillChangeTo), this method + * returns a const reference. Only our friend classes may get mutable + * references to mBaseVal. + */ + const SVGNumberList& GetBaseValue() const { return mBaseVal; } + + nsresult SetBaseValueString(const nsAString& aValue); + + void ClearBaseValue(uint32_t aAttrEnum); + + const SVGNumberList& GetAnimValue() const { + return mAnimVal ? *mAnimVal : mBaseVal; + } + + nsresult SetAnimValue(const SVGNumberList& aNewAnimValue, + dom::SVGElement* aElement, uint32_t aAttrEnum); + + void ClearAnimValue(dom::SVGElement* aElement, uint32_t aAttrEnum); + + // Returns true if the animated value of this list has been explicitly + // set (either by animation, or by taking on the base value which has been + // explicitly set by markup or a DOM call), false otherwise. + // If this returns false, the animated value is still valid, that is, + // usable, and represents the default base value of the attribute. + bool IsExplicitlySet() const { return !!mAnimVal || mIsBaseSet; } + + bool IsAnimating() const { return !!mAnimVal; } + + UniquePtr<SMILAttr> ToSMILAttr(dom::SVGElement* aSVGElement, + uint8_t aAttrEnum); + + private: + // mAnimVal is a pointer to allow us to determine if we're being animated or + // not. Making it a non-pointer member and using mAnimVal.IsEmpty() to check + // if we're animating is not an option, since that would break animation *to* + // the empty string (<set to="">). + + SVGNumberList mBaseVal; + UniquePtr<SVGNumberList> mAnimVal; + bool mIsBaseSet = false; + + struct SMILAnimatedNumberList : public SMILAttr { + public: + SMILAnimatedNumberList(SVGAnimatedNumberList* aVal, + dom::SVGElement* aSVGElement, uint8_t aAttrEnum) + : mVal(aVal), mElement(aSVGElement), mAttrEnum(aAttrEnum) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedNumberList* mVal; + dom::SVGElement* mElement; + uint8_t mAttrEnum; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDNUMBERLIST_H_ diff --git a/dom/svg/SVGAnimatedNumberPair.cpp b/dom/svg/SVGAnimatedNumberPair.cpp new file mode 100644 index 0000000000..ffab25c6e9 --- /dev/null +++ b/dom/svg/SVGAnimatedNumberPair.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedNumberPair.h" + +#include "nsCharSeparatedTokenizer.h" +#include "SVGAttrTearoffTable.h" +#include "SVGNumberPairSMILType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGContentUtils.h" +#include "nsContentUtils.h" + +using namespace mozilla::dom; + +namespace mozilla { + +//---------------------------------------------------------------------- +// Helper class: AutoChangeNumberPairNotifier +// Stack-based helper class to pair calls to WillChangeNumberPair and +// DidChangeNumberPair. +class MOZ_RAII AutoChangeNumberPairNotifier { + public: + AutoChangeNumberPairNotifier(SVGAnimatedNumberPair* aNumberPair, + SVGElement* aSVGElement, bool aDoSetAttr = true) + : mNumberPair(aNumberPair), + mSVGElement(aSVGElement), + mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mNumberPair, "Expecting non-null numberPair"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + + if (mDoSetAttr) { + mEmptyOrOldValue = + mSVGElement->WillChangeNumberPair(mNumberPair->mAttrEnum); + } + } + + ~AutoChangeNumberPairNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangeNumberPair(mNumberPair->mAttrEnum, + mEmptyOrOldValue); + } + if (mNumberPair->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedNumberPair* const mNumberPair; + SVGElement* const mSVGElement; + nsAttrValue mEmptyOrOldValue; + bool mDoSetAttr; +}; + +static SVGAttrTearoffTable<SVGAnimatedNumberPair, + SVGAnimatedNumberPair::DOMAnimatedNumber> + sSVGFirstAnimatedNumberTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedNumberPair, + SVGAnimatedNumberPair::DOMAnimatedNumber> + sSVGSecondAnimatedNumberTearoffTable; + +static nsresult ParseNumberOptionalNumber(const nsAString& aValue, + float aValues[2]) { + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional> + tokenizer(aValue, ','); + uint32_t i; + for (i = 0; i < 2 && tokenizer.hasMoreTokens(); ++i) { + if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), aValues[i])) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + } + if (i == 1) { + aValues[1] = aValues[0]; + } + + if (i == 0 || // Too few values. + tokenizer.hasMoreTokens() || // Too many values. + tokenizer.separatorAfterCurrentToken()) { // Trailing comma. + return NS_ERROR_DOM_SYNTAX_ERR; + } + + return NS_OK; +} + +nsresult SVGAnimatedNumberPair::SetBaseValueString( + const nsAString& aValueAsString, SVGElement* aSVGElement) { + float val[2]; + + nsresult rv = ParseNumberOptionalNumber(aValueAsString, val); + if (NS_FAILED(rv)) { + return rv; + } + + // We don't need to call Will/DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + AutoChangeNumberPairNotifier notifier(this, aSVGElement, false); + + mBaseVal[0] = val[0]; + mBaseVal[1] = val[1]; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal[0] = mBaseVal[0]; + mAnimVal[1] = mBaseVal[1]; + } + + return NS_OK; +} + +void SVGAnimatedNumberPair::GetBaseValueString( + nsAString& aValueAsString) const { + aValueAsString.Truncate(); + aValueAsString.AppendFloat(mBaseVal[0]); + if (mBaseVal[0] != mBaseVal[1]) { + aValueAsString.AppendLiteral(", "); + aValueAsString.AppendFloat(mBaseVal[1]); + } +} + +void SVGAnimatedNumberPair::SetBaseValue(float aValue, PairIndex aPairIndex, + SVGElement* aSVGElement) { + uint32_t index = (aPairIndex == eFirst ? 0 : 1); + if (mIsBaseSet && mBaseVal[index] == aValue) { + return; + } + + AutoChangeNumberPairNotifier notifier(this, aSVGElement); + + mBaseVal[index] = aValue; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal[index] = aValue; + } +} + +void SVGAnimatedNumberPair::SetBaseValues(float aValue1, float aValue2, + SVGElement* aSVGElement) { + if (mIsBaseSet && mBaseVal[0] == aValue1 && mBaseVal[1] == aValue2) { + return; + } + + AutoChangeNumberPairNotifier notifier(this, aSVGElement); + + mBaseVal[0] = aValue1; + mBaseVal[1] = aValue2; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal[0] = aValue1; + mAnimVal[1] = aValue2; + } +} + +void SVGAnimatedNumberPair::SetAnimValue(const float aValue[2], + SVGElement* aSVGElement) { + if (mIsAnimated && mAnimVal[0] == aValue[0] && mAnimVal[1] == aValue[1]) { + return; + } + mAnimVal[0] = aValue[0]; + mAnimVal[1] = aValue[1]; + mIsAnimated = true; + aSVGElement->DidAnimateNumberPair(mAttrEnum); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGAnimatedNumberPair::ToDOMAnimatedNumber(PairIndex aIndex, + SVGElement* aSVGElement) { + RefPtr<DOMAnimatedNumber> domAnimatedNumber = + aIndex == eFirst ? sSVGFirstAnimatedNumberTearoffTable.GetTearoff(this) + : sSVGSecondAnimatedNumberTearoffTable.GetTearoff(this); + if (!domAnimatedNumber) { + domAnimatedNumber = new DOMAnimatedNumber(this, aIndex, aSVGElement); + if (aIndex == eFirst) { + sSVGFirstAnimatedNumberTearoffTable.AddTearoff(this, domAnimatedNumber); + } else { + sSVGSecondAnimatedNumberTearoffTable.AddTearoff(this, domAnimatedNumber); + } + } + + return domAnimatedNumber.forget(); +} + +SVGAnimatedNumberPair::DOMAnimatedNumber::~DOMAnimatedNumber() { + if (mIndex == eFirst) { + sSVGFirstAnimatedNumberTearoffTable.RemoveTearoff(mVal); + } else { + sSVGSecondAnimatedNumberTearoffTable.RemoveTearoff(mVal); + } +} + +UniquePtr<SMILAttr> SVGAnimatedNumberPair::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILNumberPair>(this, aSVGElement); +} + +nsresult SVGAnimatedNumberPair::SMILNumberPair::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + float values[2]; + + nsresult rv = ParseNumberOptionalNumber(aStr, values); + if (NS_FAILED(rv)) { + return rv; + } + + SMILValue val(&SVGNumberPairSMILType::sSingleton); + val.mU.mNumberPair[0] = values[0]; + val.mU.mNumberPair[1] = values[1]; + aValue = val; + aPreventCachingOfSandwich = false; + + return NS_OK; +} + +SMILValue SVGAnimatedNumberPair::SMILNumberPair::GetBaseValue() const { + SMILValue val(&SVGNumberPairSMILType::sSingleton); + val.mU.mNumberPair[0] = mVal->mBaseVal[0]; + val.mU.mNumberPair[1] = mVal->mBaseVal[1]; + return val; +} + +void SVGAnimatedNumberPair::SMILNumberPair::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal[0] = mVal->mBaseVal[0]; + mVal->mAnimVal[1] = mVal->mBaseVal[1]; + mSVGElement->DidAnimateNumberPair(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedNumberPair::SMILNumberPair::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == &SVGNumberPairSMILType::sSingleton, + "Unexpected type to assign animated value"); + if (aValue.mType == &SVGNumberPairSMILType::sSingleton) { + mVal->SetAnimValue(aValue.mU.mNumberPair, mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedNumberPair.h b/dom/svg/SVGAnimatedNumberPair.h new file mode 100644 index 0000000000..1642d6dc10 --- /dev/null +++ b/dom/svg/SVGAnimatedNumberPair.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDNUMBERPAIR_H_ +#define DOM_SVG_SVGANIMATEDNUMBERPAIR_H_ + +#include "DOMSVGAnimatedNumber.h" +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "nsMathUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +class SVGAnimatedNumberPair { + public: + friend class AutoChangeNumberPairNotifier; + using SVGElement = dom::SVGElement; + + enum PairIndex { eFirst, eSecond }; + + void Init(uint8_t aAttrEnum = 0xff, float aValue1 = 0, float aValue2 = 0) { + mAnimVal[0] = mBaseVal[0] = aValue1; + mAnimVal[1] = mBaseVal[1] = aValue2; + mAttrEnum = aAttrEnum; + mIsAnimated = false; + mIsBaseSet = false; + } + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement); + void GetBaseValueString(nsAString& aValue) const; + + void SetBaseValue(float aValue, PairIndex aPairIndex, + SVGElement* aSVGElement); + void SetBaseValues(float aValue1, float aValue2, SVGElement* aSVGElement); + float GetBaseValue(PairIndex aIndex) const { + return mBaseVal[aIndex == eFirst ? 0 : 1]; + } + void SetAnimValue(const float aValue[2], SVGElement* aSVGElement); + float GetAnimValue(PairIndex aIndex) const { + return mAnimVal[aIndex == eFirst ? 0 : 1]; + } + + // Returns true if the animated value of this number has been explicitly + // set (either by animation, or by taking on the base value which has been + // explicitly set by markup or a DOM call), false otherwise. + // If this returns false, the animated value is still valid, that is, + // usable, and represents the default base value of the attribute. + bool IsExplicitlySet() const { return mIsAnimated || mIsBaseSet; } + + already_AddRefed<dom::DOMSVGAnimatedNumber> ToDOMAnimatedNumber( + PairIndex aIndex, SVGElement* aSVGElement); + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + float mAnimVal[2]; + float mBaseVal[2]; + uint8_t mAttrEnum; // element specified tracking for attribute + bool mIsAnimated; + bool mIsBaseSet; + + public: + // DOM wrapper class for the (DOM)SVGAnimatedNumber interface where the + // wrapped class is SVGAnimatedNumberPair. + struct DOMAnimatedNumber final : public dom::DOMSVGAnimatedNumber { + DOMAnimatedNumber(SVGAnimatedNumberPair* aVal, PairIndex aIndex, + SVGElement* aSVGElement) + : dom::DOMSVGAnimatedNumber(aSVGElement), mVal(aVal), mIndex(aIndex) {} + virtual ~DOMAnimatedNumber(); + + SVGAnimatedNumberPair* mVal; // kept alive because it belongs to content + PairIndex mIndex; // are we the first or second number + + float BaseVal() override { return mVal->GetBaseValue(mIndex); } + void SetBaseVal(float aValue) override { + MOZ_ASSERT(IsFinite(aValue)); + mVal->SetBaseValue(aValue, mIndex, mSVGElement); + } + + // Script may have modified animation parameters or timeline -- DOM getters + // need to flush any resample requests to reflect these modifications. + float AnimVal() override { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(mIndex); + } + }; + + struct SMILNumberPair : public SMILAttr { + public: + SMILNumberPair(SVGAnimatedNumberPair* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedNumberPair* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDNUMBERPAIR_H_ diff --git a/dom/svg/SVGAnimatedOrient.cpp b/dom/svg/SVGAnimatedOrient.cpp new file mode 100644 index 0000000000..170377ac3f --- /dev/null +++ b/dom/svg/SVGAnimatedOrient.cpp @@ -0,0 +1,498 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedOrient.h" + +#include <utility> + +#include "DOMSVGAngle.h" +#include "DOMSVGAnimatedAngle.h" +#include "SVGAttrTearoffTable.h" +#include "SVGOrientSMILType.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGMarkerElement.h" +#include "mozAutoDocUpdate.h" +#include "nsContentUtils.h" +#include "nsTextFormatter.h" +#include "nsPrintfCString.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGAngle_Binding; +using namespace mozilla::dom::SVGMarkerElement_Binding; + +namespace mozilla { + +static const nsStaticAtom* const angleUnitMap[] = { + nullptr, /* SVG_ANGLETYPE_UNKNOWN */ + nullptr, /* SVG_ANGLETYPE_UNSPECIFIED */ + nsGkAtoms::deg, nsGkAtoms::rad, nsGkAtoms::grad}; + +static SVGAttrTearoffTable<SVGAnimatedOrient, DOMSVGAnimatedEnumeration> + sSVGAnimatedEnumTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedOrient, DOMSVGAnimatedAngle> + sSVGAnimatedAngleTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedOrient, DOMSVGAngle> + sBaseSVGAngleTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedOrient, DOMSVGAngle> + sAnimSVGAngleTearoffTable; + +/* Helper functions */ + +//---------------------------------------------------------------------- +// Helper class: AutoChangeOrientNotifier +// Stack-based helper class to pair calls to WillChangeOrient and +// DidChangeOrient with mozAutoDocUpdate. +class MOZ_RAII AutoChangeOrientNotifier { + public: + AutoChangeOrientNotifier(SVGAnimatedOrient* aOrient, SVGElement* aSVGElement, + bool aDoSetAttr = true) + : mOrient(aOrient), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mOrient, "Expecting non-null orient"); + if (mSVGElement && mDoSetAttr) { + mUpdateBatch.emplace(mSVGElement->GetComposedDoc(), true); + mEmptyOrOldValue = mSVGElement->WillChangeOrient(mUpdateBatch.ref()); + } + } + + ~AutoChangeOrientNotifier() { + if (mSVGElement) { + if (mDoSetAttr) { + mSVGElement->DidChangeOrient(mEmptyOrOldValue, mUpdateBatch.ref()); + } + if (mOrient->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + } + + private: + Maybe<mozAutoDocUpdate> mUpdateBatch; + SVGAnimatedOrient* const mOrient; + SVGElement* const mSVGElement; + nsAttrValue mEmptyOrOldValue; + bool mDoSetAttr; +}; + +static bool IsValidAngleUnitType(uint16_t unit) { + return unit > SVG_ANGLETYPE_UNKNOWN && unit <= SVG_ANGLETYPE_GRAD; +} + +static void GetAngleUnitString(nsAString& unit, uint16_t unitType) { + if (IsValidAngleUnitType(unitType)) { + if (angleUnitMap[unitType]) { + angleUnitMap[unitType]->ToString(unit); + } + return; + } + + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); +} + +static uint16_t GetAngleUnitTypeForString(const nsAString& unitStr) { + if (unitStr.IsEmpty()) return SVG_ANGLETYPE_UNSPECIFIED; + + nsStaticAtom* unitAtom = NS_GetStaticAtom(unitStr); + + if (unitAtom) { + for (uint32_t i = 0; i < ArrayLength(angleUnitMap); i++) { + if (angleUnitMap[i] == unitAtom) { + return i; + } + } + } + + return SVG_ANGLETYPE_UNKNOWN; +} + +static void GetAngleValueString(nsAString& aValueAsString, float aValue, + uint16_t aUnitType) { + nsTextFormatter::ssprintf(aValueAsString, u"%g", (double)aValue); + + nsAutoString unitString; + GetAngleUnitString(unitString, aUnitType); + aValueAsString.Append(unitString); +} + +/* static */ +bool SVGAnimatedOrient::GetValueFromString(const nsAString& aString, + float& aValue, uint16_t* aUnitType) { + bool success; + auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); + + if (!success) { + return false; + } + + RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(token); + + if (!SVGContentUtils::ParseNumber(iter, end, aValue)) { + return false; + } + + const nsAString& units = Substring(iter.get(), end.get()); + *aUnitType = GetAngleUnitTypeForString(units); + return IsValidAngleUnitType(*aUnitType); +} + +/* static */ +float SVGAnimatedOrient::GetDegreesPerUnit(uint8_t aUnit) { + switch (aUnit) { + case SVG_ANGLETYPE_UNSPECIFIED: + case SVG_ANGLETYPE_DEG: + return 1; + case SVG_ANGLETYPE_RAD: + return static_cast<float>(180.0 / M_PI); + case SVG_ANGLETYPE_GRAD: + return 90.0f / 100.0f; + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); + return 0; + } +} + +void SVGAnimatedOrient::SetBaseValueInSpecifiedUnits(float aValue, + SVGElement* aSVGElement) { + if (mBaseVal == aValue && mBaseType == SVG_MARKER_ORIENT_ANGLE) { + return; + } + + AutoChangeOrientNotifier notifier(this, aSVGElement); + + mBaseVal = aValue; + mBaseType = SVG_MARKER_ORIENT_ANGLE; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + mAnimType = mBaseType; + } +} + +nsresult SVGAnimatedOrient::ConvertToSpecifiedUnits(uint16_t unitType, + SVGElement* aSVGElement) { + if (!IsValidAngleUnitType(unitType)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + + if (mBaseValUnit == uint8_t(unitType) && + mBaseType == SVG_MARKER_ORIENT_ANGLE) { + return NS_OK; + } + + float valueInUserUnits = mBaseVal * GetDegreesPerUnit(mBaseValUnit); + SetBaseValue(valueInUserUnits, unitType, aSVGElement, true); + + return NS_OK; +} + +nsresult SVGAnimatedOrient::NewValueSpecifiedUnits(uint16_t aUnitType, + float aValueInSpecifiedUnits, + SVGElement* aSVGElement) { + NS_ENSURE_FINITE(aValueInSpecifiedUnits, NS_ERROR_ILLEGAL_VALUE); + + if (!IsValidAngleUnitType(aUnitType)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + + if (mBaseVal == aValueInSpecifiedUnits && + mBaseValUnit == uint8_t(aUnitType) && + mBaseType == SVG_MARKER_ORIENT_ANGLE) + return NS_OK; + + AutoChangeOrientNotifier notifier(this, aSVGElement); + + mBaseVal = aValueInSpecifiedUnits; + mBaseValUnit = uint8_t(aUnitType); + mBaseType = SVG_MARKER_ORIENT_ANGLE; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + mAnimValUnit = mBaseValUnit; + mAnimType = mBaseType; + } + return NS_OK; +} + +already_AddRefed<DOMSVGAngle> SVGAnimatedOrient::ToDOMBaseVal( + SVGElement* aSVGElement) { + RefPtr<DOMSVGAngle> domBaseVal = sBaseSVGAngleTearoffTable.GetTearoff(this); + if (!domBaseVal) { + domBaseVal = + new DOMSVGAngle(this, aSVGElement, DOMSVGAngle::AngleType::BaseValue); + sBaseSVGAngleTearoffTable.AddTearoff(this, domBaseVal); + } + + return domBaseVal.forget(); +} + +already_AddRefed<DOMSVGAngle> SVGAnimatedOrient::ToDOMAnimVal( + SVGElement* aSVGElement) { + RefPtr<DOMSVGAngle> domAnimVal = sAnimSVGAngleTearoffTable.GetTearoff(this); + if (!domAnimVal) { + domAnimVal = + new DOMSVGAngle(this, aSVGElement, DOMSVGAngle::AngleType::AnimValue); + sAnimSVGAngleTearoffTable.AddTearoff(this, domAnimVal); + } + + return domAnimVal.forget(); +} + +DOMSVGAngle::~DOMSVGAngle() { + switch (mType) { + case AngleType::BaseValue: + sBaseSVGAngleTearoffTable.RemoveTearoff(mVal); + break; + case AngleType::AnimValue: + sAnimSVGAngleTearoffTable.RemoveTearoff(mVal); + break; + default: + delete mVal; + } +} + +/* Implementation */ + +nsresult SVGAnimatedOrient::SetBaseValueString(const nsAString& aValueAsString, + SVGElement* aSVGElement, + bool aDoSetAttr) { + uint8_t type; + float value; + uint16_t unitType; + bool valueChanged = false; + + if (aValueAsString.EqualsLiteral("auto")) { + type = SVG_MARKER_ORIENT_AUTO; + if (type == mBaseType) { + return NS_OK; + } + } else if (aValueAsString.EqualsLiteral("auto-start-reverse")) { + type = SVG_MARKER_ORIENT_AUTO_START_REVERSE; + if (type == mBaseType) { + return NS_OK; + } + } else { + if (!GetValueFromString(aValueAsString, value, &unitType)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + if (mBaseVal == value && mBaseValUnit == uint8_t(unitType) && + mBaseType == SVG_MARKER_ORIENT_ANGLE) { + return NS_OK; + } + valueChanged = true; + } + + AutoChangeOrientNotifier notifier(this, aSVGElement, aDoSetAttr); + + if (valueChanged) { + mBaseVal = value; + mBaseValUnit = uint8_t(unitType); + mBaseType = SVG_MARKER_ORIENT_ANGLE; + } else { + mBaseVal = .0f; + mBaseValUnit = SVG_ANGLETYPE_UNSPECIFIED; + mBaseType = type; + } + + if (!mIsAnimated) { + mAnimVal = mBaseVal; + mAnimValUnit = mBaseValUnit; + mAnimType = mBaseType; + } + + return NS_OK; +} + +void SVGAnimatedOrient::GetBaseValueString(nsAString& aValueAsString) const { + switch (mBaseType) { + case SVG_MARKER_ORIENT_AUTO: + aValueAsString.AssignLiteral("auto"); + return; + case SVG_MARKER_ORIENT_AUTO_START_REVERSE: + aValueAsString.AssignLiteral("auto-start-reverse"); + return; + } + GetAngleValueString(aValueAsString, mBaseVal, mBaseValUnit); +} + +void SVGAnimatedOrient::GetBaseAngleValueString( + nsAString& aValueAsString) const { + GetAngleValueString(aValueAsString, mBaseVal, mBaseValUnit); +} + +void SVGAnimatedOrient::GetAnimAngleValueString( + nsAString& aValueAsString) const { + GetAngleValueString(aValueAsString, mAnimVal, mAnimValUnit); +} + +void SVGAnimatedOrient::SetBaseValue(float aValue, uint8_t aUnit, + SVGElement* aSVGElement, bool aDoSetAttr) { + float valueInSpecifiedUnits = aValue / GetDegreesPerUnit(aUnit); + if (aUnit == mBaseValUnit && mBaseVal == valueInSpecifiedUnits && + mBaseType == SVG_MARKER_ORIENT_ANGLE) { + return; + } + + AutoChangeOrientNotifier notifier(this, aSVGElement, aDoSetAttr); + + mBaseValUnit = aUnit; + mBaseVal = valueInSpecifiedUnits; + mBaseType = SVG_MARKER_ORIENT_ANGLE; + if (!mIsAnimated) { + mAnimValUnit = mBaseValUnit; + mAnimVal = mBaseVal; + mAnimType = mBaseType; + } +} + +void SVGAnimatedOrient::SetBaseType(SVGEnumValue aValue, + SVGElement* aSVGElement, ErrorResult& aRv) { + if (mBaseType == aValue) { + return; + } + if (aValue >= SVG_MARKER_ORIENT_AUTO && + aValue <= SVG_MARKER_ORIENT_AUTO_START_REVERSE) { + AutoChangeOrientNotifier notifier(this, aSVGElement); + + mBaseVal = .0f; + mBaseValUnit = SVG_ANGLETYPE_UNSPECIFIED; + mBaseType = aValue; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + mAnimValUnit = mBaseValUnit; + mAnimType = mBaseType; + } + return; + } + nsPrintfCString err("Invalid base value %u for marker orient", aValue); + aRv.ThrowTypeError(err); +} + +void SVGAnimatedOrient::SetAnimValue(float aValue, uint8_t aUnit, + SVGElement* aSVGElement) { + if (mIsAnimated && mAnimVal == aValue && mAnimValUnit == aUnit && + mAnimType == SVG_MARKER_ORIENT_ANGLE) { + return; + } + mAnimVal = aValue; + mAnimValUnit = aUnit; + mAnimType = SVG_MARKER_ORIENT_ANGLE; + mIsAnimated = true; + aSVGElement->DidAnimateOrient(); +} + +void SVGAnimatedOrient::SetAnimType(SVGEnumValue aValue, + SVGElement* aSVGElement) { + if (mIsAnimated && mAnimType == aValue) { + return; + } + mAnimVal = .0f; + mAnimValUnit = SVG_ANGLETYPE_UNSPECIFIED; + mAnimType = aValue; + mIsAnimated = true; + aSVGElement->DidAnimateOrient(); +} + +already_AddRefed<DOMSVGAnimatedAngle> SVGAnimatedOrient::ToDOMAnimatedAngle( + SVGElement* aSVGElement) { + RefPtr<DOMSVGAnimatedAngle> domAnimatedAngle = + sSVGAnimatedAngleTearoffTable.GetTearoff(this); + if (!domAnimatedAngle) { + domAnimatedAngle = new DOMSVGAnimatedAngle(this, aSVGElement); + sSVGAnimatedAngleTearoffTable.AddTearoff(this, domAnimatedAngle); + } + + return domAnimatedAngle.forget(); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGAnimatedOrient::ToDOMAnimatedEnum(SVGElement* aSVGElement) { + RefPtr<DOMSVGAnimatedEnumeration> domAnimatedEnum = + sSVGAnimatedEnumTearoffTable.GetTearoff(this); + if (!domAnimatedEnum) { + domAnimatedEnum = new DOMAnimatedEnum(this, aSVGElement); + sSVGAnimatedEnumTearoffTable.AddTearoff(this, domAnimatedEnum); + } + + return domAnimatedEnum.forget(); +} + +DOMSVGAnimatedAngle::~DOMSVGAnimatedAngle() { + sSVGAnimatedAngleTearoffTable.RemoveTearoff(mVal); +} + +SVGAnimatedOrient::DOMAnimatedEnum::~DOMAnimatedEnum() { + sSVGAnimatedEnumTearoffTable.RemoveTearoff(mVal); +} + +UniquePtr<SMILAttr> SVGAnimatedOrient::ToSMILAttr(SVGElement* aSVGElement) { + if (aSVGElement->NodeInfo()->Equals(nsGkAtoms::marker, kNameSpaceID_SVG)) { + return MakeUnique<SMILOrient>(this, aSVGElement); + } + // SMILOrient would not be useful for general angle attributes (also, + // "orient" is the only animatable <angle>-valued attribute in SVG 1.1). + MOZ_ASSERT_UNREACHABLE("Trying to animate unknown angle attribute."); + return nullptr; +} + +nsresult SVGAnimatedOrient::SMILOrient::ValueFromString( + const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SMILValue val(&SVGOrientSMILType::sSingleton); + if (aStr.EqualsLiteral("auto")) { + val.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_AUTO; + val.mU.mOrient.mAngle = .0f; + val.mU.mOrient.mUnit = SVG_ANGLETYPE_UNSPECIFIED; + } else if (aStr.EqualsLiteral("auto-start-reverse")) { + val.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_AUTO_START_REVERSE; + val.mU.mOrient.mAngle = .0f; + val.mU.mOrient.mUnit = SVG_ANGLETYPE_UNSPECIFIED; + } else { + float value; + uint16_t unitType; + if (!GetValueFromString(aStr, value, &unitType)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + val.mU.mOrient.mAngle = value; + val.mU.mOrient.mUnit = unitType; + val.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_ANGLE; + } + aValue = std::move(val); + aPreventCachingOfSandwich = false; + + return NS_OK; +} + +SMILValue SVGAnimatedOrient::SMILOrient::GetBaseValue() const { + SMILValue val(&SVGOrientSMILType::sSingleton); + val.mU.mOrient.mAngle = mOrient->GetBaseValInSpecifiedUnits(); + val.mU.mOrient.mUnit = mOrient->GetBaseValueUnit(); + val.mU.mOrient.mOrientType = mOrient->mBaseType; + return val; +} + +void SVGAnimatedOrient::SMILOrient::ClearAnimValue() { + if (mOrient->mIsAnimated) { + mOrient->mIsAnimated = false; + mOrient->mAnimVal = mOrient->mBaseVal; + mOrient->mAnimValUnit = mOrient->mBaseValUnit; + mOrient->mAnimType = mOrient->mBaseType; + mSVGElement->DidAnimateOrient(); + } +} + +nsresult SVGAnimatedOrient::SMILOrient::SetAnimValue(const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == &SVGOrientSMILType::sSingleton, + "Unexpected type to assign animated value"); + + if (aValue.mType == &SVGOrientSMILType::sSingleton) { + if (aValue.mU.mOrient.mOrientType == SVG_MARKER_ORIENT_AUTO || + aValue.mU.mOrient.mOrientType == SVG_MARKER_ORIENT_AUTO_START_REVERSE) { + mOrient->SetAnimType(aValue.mU.mOrient.mOrientType, mSVGElement); + } else { + mOrient->SetAnimValue(aValue.mU.mOrient.mAngle, aValue.mU.mOrient.mUnit, + mSVGElement); + } + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedOrient.h b/dom/svg/SVGAnimatedOrient.h new file mode 100644 index 0000000000..d2060ca18d --- /dev/null +++ b/dom/svg/SVGAnimatedOrient.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDORIENT_H_ +#define DOM_SVG_SVGANIMATEDORIENT_H_ + +#include "DOMSVGAnimatedEnumeration.h" +#include "nsError.h" +#include "SVGAnimatedEnumeration.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/dom/SVGAngleBinding.h" +#include "mozilla/dom/SVGMarkerElementBinding.h" +#include "mozilla/UniquePtr.h" + +class nsISupports; + +namespace mozilla { + +class SMILValue; + +namespace dom { +class DOMSVGAngle; +class DOMSVGAnimatedAngle; +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +class SVGAnimatedOrient { + friend class AutoChangeOrientNotifier; + friend class dom::DOMSVGAngle; + friend class dom::DOMSVGAnimatedAngle; + using SVGElement = dom::SVGElement; + + public: + void Init() { + mAnimVal = mBaseVal = .0f; + mAnimType = mBaseType = + dom::SVGMarkerElement_Binding::SVG_MARKER_ORIENT_ANGLE; + mAnimValUnit = mBaseValUnit = + dom::SVGAngle_Binding::SVG_ANGLETYPE_UNSPECIFIED; + mIsAnimated = false; + } + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement, + bool aDoSetAttr); + void GetBaseValueString(nsAString& aValue) const; + void GetBaseAngleValueString(nsAString& aValue) const; + void GetAnimAngleValueString(nsAString& aValue) const; + + float GetBaseValue() const { + return mBaseVal * GetDegreesPerUnit(mBaseValUnit); + } + float GetAnimValue() const { + return mAnimVal * GetDegreesPerUnit(mAnimValUnit); + } + SVGEnumValue GetAnimType() const { return mAnimType; } + + void SetBaseValue(float aValue, uint8_t aUnit, SVGElement* aSVGElement, + bool aDoSetAttr); + void SetBaseType(SVGEnumValue aValue, SVGElement* aSVGElement, + ErrorResult& aRv); + void SetAnimValue(float aValue, uint8_t aUnit, SVGElement* aSVGElement); + void SetAnimType(SVGEnumValue aValue, SVGElement* aSVGElement); + + uint8_t GetBaseValueUnit() const { return mBaseValUnit; } + uint8_t GetAnimValueUnit() const { return mAnimValUnit; } + float GetBaseValInSpecifiedUnits() const { return mBaseVal; } + float GetAnimValInSpecifiedUnits() const { return mAnimVal; } + + static nsresult ToDOMSVGAngle(nsISupports** aResult); + already_AddRefed<dom::DOMSVGAnimatedAngle> ToDOMAnimatedAngle( + SVGElement* aSVGElement); + already_AddRefed<dom::DOMSVGAnimatedEnumeration> ToDOMAnimatedEnum( + SVGElement* aSVGElement); + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + static bool GetValueFromString(const nsAString& aString, float& aValue, + uint16_t* aUnitType); + static float GetDegreesPerUnit(uint8_t aUnit); + + private: + float mAnimVal; + float mBaseVal; + uint8_t mAnimType; + uint8_t mBaseType; + uint8_t mAnimValUnit; + uint8_t mBaseValUnit; + bool mIsAnimated; + + void SetBaseValueInSpecifiedUnits(float aValue, SVGElement* aSVGElement); + nsresult NewValueSpecifiedUnits(uint16_t aUnitType, + float aValueInSpecifiedUnits, + SVGElement* aSVGElement); + nsresult ConvertToSpecifiedUnits(uint16_t aUnitType, SVGElement* aSVGElement); + already_AddRefed<dom::DOMSVGAngle> ToDOMBaseVal(SVGElement* aSVGElement); + already_AddRefed<dom::DOMSVGAngle> ToDOMAnimVal(SVGElement* aSVGElement); + + public: + // DOM wrapper class for the (DOM)SVGAnimatedEnumeration interface where the + // wrapped class is SVGAnimatedOrient. + struct DOMAnimatedEnum final : public dom::DOMSVGAnimatedEnumeration { + DOMAnimatedEnum(SVGAnimatedOrient* aVal, SVGElement* aSVGElement) + : DOMSVGAnimatedEnumeration(aSVGElement), mVal(aVal) {} + ~DOMAnimatedEnum(); + + SVGAnimatedOrient* mVal; // kept alive because it belongs to content + + using dom::DOMSVGAnimatedEnumeration::SetBaseVal; + uint16_t BaseVal() override { return mVal->mBaseType; } + void SetBaseVal(uint16_t aBaseVal, ErrorResult& aRv) override { + mVal->SetBaseType(aBaseVal, mSVGElement, aRv); + } + uint16_t AnimVal() override { + // Script may have modified animation parameters or timeline -- DOM + // getters need to flush any resample requests to reflect these + // modifications. + mSVGElement->FlushAnimations(); + return mVal->mAnimType; + } + }; + + struct SMILOrient final : public SMILAttr { + public: + SMILOrient(SVGAnimatedOrient* aOrient, SVGElement* aSVGElement) + : mOrient(aOrient), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedOrient* mOrient; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDORIENT_H_ diff --git a/dom/svg/SVGAnimatedPathSegList.cpp b/dom/svg/SVGAnimatedPathSegList.cpp new file mode 100644 index 0000000000..ef7e47bcef --- /dev/null +++ b/dom/svg/SVGAnimatedPathSegList.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedPathSegList.h" + +#include <utility> + +#include "DOMSVGPathSegList.h" +#include "SVGPathSegListSMILType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGElement.h" + +using namespace mozilla::dom; + +// See the comments in this file's header! + +namespace mozilla { + +nsresult SVGAnimatedPathSegList::SetBaseValueString(const nsAString& aValue) { + SVGPathData newBaseValue; + + // The spec says that the path data is parsed and accepted up to the first + // error encountered, so we don't return early if an error occurs. However, + // we do want to throw any error code from setAttribute if there's a problem. + + nsresult rv = newBaseValue.SetValueFromString(aValue); + + // We must send these notifications *before* changing mBaseVal! Our baseVal's + // DOM wrapper list may have to remove DOM items from itself, and any removed + // DOM items need to copy their internal counterpart's values *before* we + // change them. See the comments in + // DOMSVGPathSegList::InternalListWillChangeTo(). + DOMSVGPathSegList* baseValWrapper = nullptr; + DOMSVGPathSegList* animValWrapper = nullptr; + if (StaticPrefs::dom_svg_pathSeg_enabled()) { + baseValWrapper = DOMSVGPathSegList::GetDOMWrapperIfExists(GetBaseValKey()); + if (baseValWrapper) { + baseValWrapper->InternalListWillChangeTo(newBaseValue); + } + + if (!IsAnimating()) { // DOM anim val wraps our base val too! + animValWrapper = + DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey()); + if (animValWrapper) { + animValWrapper->InternalListWillChangeTo(newBaseValue); + } + } + } + + // Only now may we modify mBaseVal! + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + + nsresult rv2 = mBaseVal.CopyFrom(newBaseValue); + if (NS_FAILED(rv2)) { + if (StaticPrefs::dom_svg_pathSeg_enabled()) { + // Attempting to increase mBaseVal's length failed (mBaseVal is left + // unmodified). We MUST keep any DOM wrappers in sync: + if (baseValWrapper) { + baseValWrapper->InternalListWillChangeTo(mBaseVal); + } + if (animValWrapper) { + animValWrapper->InternalListWillChangeTo(mBaseVal); + } + } + return rv2; + } + return rv; +} + +void SVGAnimatedPathSegList::ClearBaseValue() { + if (StaticPrefs::dom_svg_pathSeg_enabled()) { + // We must send these notifications *before* changing mBaseVal! (See above.) + + DOMSVGPathSegList* baseValWrapper = + DOMSVGPathSegList::GetDOMWrapperIfExists(GetBaseValKey()); + if (baseValWrapper) { + baseValWrapper->InternalListWillChangeTo(SVGPathData()); + } + + if (!IsAnimating()) { // DOM anim val wraps our base val too! + DOMSVGPathSegList* animValWrapper = + DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey()); + if (animValWrapper) { + animValWrapper->InternalListWillChangeTo(SVGPathData()); + } + } + } + + mBaseVal.Clear(); + // Caller notifies +} + +nsresult SVGAnimatedPathSegList::SetAnimValue(const SVGPathData& aNewAnimValue, + SVGElement* aElement) { + // Note that a new animation may totally change the number of items in the + // animVal list, either replacing what was essentially a mirror of the + // baseVal list, or else replacing and overriding an existing animation. + // Unfortunately it is not possible for us to reliably distinguish between + // calls to this method that are setting a new sample for an existing + // animation, and calls that are setting the first sample of an animation + // that will override an existing animation. In the case of DOMSVGPathSegList + // the InternalListWillChangeTo method is not virtually free as it is for the + // other DOM list classes, so this is a shame. We'd quite like to be able to + // skip the call if possible. + + if (StaticPrefs::dom_svg_pathSeg_enabled()) { + // We must send these notifications *before* changing mAnimVal! (See above.) + + DOMSVGPathSegList* domWrapper = + DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey()); + if (domWrapper) { + domWrapper->InternalListWillChangeTo(aNewAnimValue); + } + } + if (!mAnimVal) { + mAnimVal = MakeUnique<SVGPathData>(); + } + nsresult rv = mAnimVal->CopyFrom(aNewAnimValue); + if (NS_FAILED(rv)) { + // OOM. We clear the animation and, importantly, ClearAnimValue() ensures + // that mAnimVal's DOM wrapper (if any) is kept in sync! + ClearAnimValue(aElement); + } + aElement->DidAnimatePathSegList(); + return rv; +} + +void SVGAnimatedPathSegList::ClearAnimValue(SVGElement* aElement) { + if (StaticPrefs::dom_svg_pathSeg_enabled()) { + // We must send these notifications *before* changing mAnimVal! (See above.) + + DOMSVGPathSegList* domWrapper = + DOMSVGPathSegList::GetDOMWrapperIfExists(GetAnimValKey()); + if (domWrapper) { + // When all animation ends, animVal simply mirrors baseVal, which may have + // a different number of items to the last active animated value. + // + domWrapper->InternalListWillChangeTo(mBaseVal); + } + } + mAnimVal = nullptr; + aElement->DidAnimatePathSegList(); +} + +bool SVGAnimatedPathSegList::IsRendered() const { + return mAnimVal ? !mAnimVal->IsEmpty() : !mBaseVal.IsEmpty(); +} + +UniquePtr<SMILAttr> SVGAnimatedPathSegList::ToSMILAttr(SVGElement* aElement) { + return MakeUnique<SMILAnimatedPathSegList>(this, aElement); +} + +nsresult SVGAnimatedPathSegList::SMILAnimatedPathSegList::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SMILValue val(SVGPathSegListSMILType::Singleton()); + SVGPathDataAndInfo* list = static_cast<SVGPathDataAndInfo*>(val.mU.mPtr); + nsresult rv = list->SetValueFromString(aStr); + if (NS_SUCCEEDED(rv)) { + list->SetElement(mElement); + aValue = std::move(val); + } + aPreventCachingOfSandwich = false; + return rv; +} + +SMILValue SVGAnimatedPathSegList::SMILAnimatedPathSegList::GetBaseValue() + const { + // To benefit from Return Value Optimization and avoid copy constructor calls + // due to our use of return-by-value, we must return the exact same object + // from ALL return points. This function must only return THIS variable: + SMILValue val; + + SMILValue tmp(SVGPathSegListSMILType::Singleton()); + SVGPathDataAndInfo* list = static_cast<SVGPathDataAndInfo*>(tmp.mU.mPtr); + nsresult rv = list->CopyFrom(mVal->mBaseVal); + if (NS_SUCCEEDED(rv)) { + list->SetElement(mElement); + val = std::move(tmp); + } + return val; +} + +nsresult SVGAnimatedPathSegList::SMILAnimatedPathSegList::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SVGPathSegListSMILType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SVGPathSegListSMILType::Singleton()) { + mVal->SetAnimValue(*static_cast<SVGPathDataAndInfo*>(aValue.mU.mPtr), + mElement); + } + return NS_OK; +} + +void SVGAnimatedPathSegList::SMILAnimatedPathSegList::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->ClearAnimValue(mElement); + } +} + +size_t SVGAnimatedPathSegList::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + size_t total = mBaseVal.SizeOfExcludingThis(aMallocSizeOf); + if (mAnimVal) { + mAnimVal->SizeOfIncludingThis(aMallocSizeOf); + } + return total; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedPathSegList.h b/dom/svg/SVGAnimatedPathSegList.h new file mode 100644 index 0000000000..6c84aa7897 --- /dev/null +++ b/dom/svg/SVGAnimatedPathSegList.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDPATHSEGLIST_H_ +#define DOM_SVG_SVGANIMATEDPATHSEGLIST_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "SVGPathData.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +/** + * Class SVGAnimatedPathSegList + * + * Despite the fact that no SVGAnimatedPathSegList interface or objects exist + * in the SVG specification (unlike e.g. SVGAnimated*Length*List), we + * nevertheless have this internal class. (Note that there is an + * SVGAnimatedPathData interface, but that's quite different to + * SVGAnimatedLengthList since it is inherited by elements, as opposed to + * elements having members of that type.) The reason that we have this class is + * to provide a single locked down point of entry to the SVGPathData objects, + * which helps ensure that the DOM wrappers for SVGPathData objects' are always + * kept in sync. This is vitally important (see the comment in + * DOMSVGPathSegList::InternalListWillChangeTo) and frees consumers from having + * to know or worry about wrappers (or forget about them!) for the most part. + */ +class SVGAnimatedPathSegList final { + // friends so that they can get write access to mBaseVal and mAnimVal + friend class dom::DOMSVGPathSeg; + friend class dom::DOMSVGPathSegList; + + public: + SVGAnimatedPathSegList() = default; + + /** + * Because it's so important that mBaseVal and its DOMSVGPathSegList wrapper + * (if any) be kept in sync (see the comment in + * DOMSVGPathSegList::InternalListWillChangeTo), this method returns a const + * reference. Only our friend classes may get mutable references to mBaseVal. + */ + const SVGPathData& GetBaseValue() const { return mBaseVal; } + + nsresult SetBaseValueString(const nsAString& aValue); + + void ClearBaseValue(); + + /** + * const! See comment for GetBaseValue! + */ + const SVGPathData& GetAnimValue() const { + return mAnimVal ? *mAnimVal : mBaseVal; + } + + nsresult SetAnimValue(const SVGPathData& aNewAnimValue, + dom::SVGElement* aElement); + + void ClearAnimValue(dom::SVGElement* aElement); + + /** + * Empty paths are not rendered. + */ + bool IsRendered() const; + + /** + * Needed for correct DOM wrapper construction since GetAnimValue may + * actually return the baseVal! + */ + void* GetBaseValKey() const { return (void*)&mBaseVal; } + void* GetAnimValKey() const { return (void*)&mAnimVal; } + + bool IsAnimating() const { return !!mAnimVal; } + + UniquePtr<SMILAttr> ToSMILAttr(dom::SVGElement* aElement); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + private: + // mAnimVal is a pointer to allow us to determine if we're being animated or + // not. Making it a non-pointer member and using mAnimVal.IsEmpty() to check + // if we're animating is not an option, since that would break animation *to* + // the empty string (<set to="">). + + SVGPathData mBaseVal; + UniquePtr<SVGPathData> mAnimVal; + + struct SMILAnimatedPathSegList : public SMILAttr { + public: + SMILAnimatedPathSegList(SVGAnimatedPathSegList* aVal, + dom::SVGElement* aElement) + : mVal(aVal), mElement(aElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedPathSegList* mVal; + dom::SVGElement* mElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDPATHSEGLIST_H_ diff --git a/dom/svg/SVGAnimatedPointList.cpp b/dom/svg/SVGAnimatedPointList.cpp new file mode 100644 index 0000000000..471b925c7e --- /dev/null +++ b/dom/svg/SVGAnimatedPointList.cpp @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedPointList.h" + +#include <utility> + +#include "DOMSVGPointList.h" +#include "SVGPointListSMILType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGElement.h" + +using namespace mozilla::dom; + +// See the comments in this file's header! + +namespace mozilla { + +nsresult SVGAnimatedPointList::SetBaseValueString(const nsAString& aValue) { + SVGPointList newBaseValue; + + // The spec says that the point data is parsed and accepted up to the first + // error encountered, so we don't return early if an error occurs. However, + // we do want to throw any error code from setAttribute if there's a problem. + + nsresult rv = newBaseValue.SetValueFromString(aValue); + + // We must send these notifications *before* changing mBaseVal! Our baseVal's + // DOM wrapper list may have to remove DOM items from itself, and any removed + // DOM items need to copy their internal counterpart's values *before* we + // change them. See the comments in + // DOMSVGPointList::InternalListWillChangeTo(). + + DOMSVGPointList* baseValWrapper = + DOMSVGPointList::GetDOMWrapperIfExists(GetBaseValKey()); + if (baseValWrapper) { + baseValWrapper->InternalListWillChangeTo(newBaseValue); + } + + DOMSVGPointList* animValWrapper = nullptr; + if (!IsAnimating()) { // DOM anim val wraps our base val too! + animValWrapper = DOMSVGPointList::GetDOMWrapperIfExists(GetAnimValKey()); + if (animValWrapper) { + animValWrapper->InternalListWillChangeTo(newBaseValue); + } + } + + // Only now may we modify mBaseVal! + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + + nsresult rv2 = mBaseVal.CopyFrom(newBaseValue); + if (NS_FAILED(rv2)) { + // Attempting to increase mBaseVal's length failed (mBaseVal is left + // unmodified). We MUST keep any DOM wrappers in sync: + if (baseValWrapper) { + baseValWrapper->InternalListWillChangeTo(mBaseVal); + } + if (animValWrapper) { + animValWrapper->InternalListWillChangeTo(mBaseVal); + } + return rv2; + } + return rv; +} + +void SVGAnimatedPointList::ClearBaseValue() { + // We must send these notifications *before* changing mBaseVal! (See above.) + + DOMSVGPointList* baseValWrapper = + DOMSVGPointList::GetDOMWrapperIfExists(GetBaseValKey()); + if (baseValWrapper) { + baseValWrapper->InternalListWillChangeTo(SVGPointList()); + } + + if (!IsAnimating()) { // DOM anim val wraps our base val too! + DOMSVGPointList* animValWrapper = + DOMSVGPointList::GetDOMWrapperIfExists(GetAnimValKey()); + if (animValWrapper) { + animValWrapper->InternalListWillChangeTo(SVGPointList()); + } + } + + mBaseVal.Clear(); + // Caller notifies +} + +nsresult SVGAnimatedPointList::SetAnimValue(const SVGPointList& aNewAnimValue, + SVGElement* aElement) { + // Note that a new animation may totally change the number of items in the + // animVal list, either replacing what was essentially a mirror of the + // baseVal list, or else replacing and overriding an existing animation. + // It is not possible for us to reliably distinguish between calls to this + // method that are setting a new sample for an existing animation (in which + // case our list length isn't changing and we wouldn't need to notify our DOM + // wrapper to keep its length in sync), and calls to this method that are + // setting the first sample of a new animation that will override the base + // value/an existing animation (in which case our length may be changing and + // our DOM wrapper may need to be notified). Happily though, it's cheap to + // just blindly notify our animVal's DOM wrapper of our new value each time + // this method is called, so that's what we do. + + // We must send this notification *before* changing mAnimVal! (See above.) + + DOMSVGPointList* domWrapper = + DOMSVGPointList::GetDOMWrapperIfExists(GetAnimValKey()); + if (domWrapper) { + domWrapper->InternalListWillChangeTo(aNewAnimValue); + } + if (!mAnimVal) { + mAnimVal = MakeUnique<SVGPointList>(); + } + nsresult rv = mAnimVal->CopyFrom(aNewAnimValue); + if (NS_FAILED(rv)) { + // OOM. We clear the animation and, importantly, ClearAnimValue() ensures + // that mAnimVal's DOM wrapper (if any) is kept in sync! + ClearAnimValue(aElement); + return rv; + } + aElement->DidAnimatePointList(); + return NS_OK; +} + +void SVGAnimatedPointList::ClearAnimValue(SVGElement* aElement) { + // We must send these notifications *before* changing mAnimVal! (See above.) + + DOMSVGPointList* domWrapper = + DOMSVGPointList::GetDOMWrapperIfExists(GetAnimValKey()); + if (domWrapper) { + // When all animation ends, animVal simply mirrors baseVal, which may have + // a different number of items to the last active animated value. + // + domWrapper->InternalListWillChangeTo(mBaseVal); + } + mAnimVal = nullptr; + aElement->DidAnimatePointList(); +} + +UniquePtr<SMILAttr> SVGAnimatedPointList::ToSMILAttr(SVGElement* aElement) { + return MakeUnique<SMILAnimatedPointList>(this, aElement); +} + +nsresult SVGAnimatedPointList::SMILAnimatedPointList::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SMILValue val(&SVGPointListSMILType::sSingleton); + SVGPointListAndInfo* list = static_cast<SVGPointListAndInfo*>(val.mU.mPtr); + nsresult rv = list->SetValueFromString(aStr); + if (NS_SUCCEEDED(rv)) { + list->SetInfo(mElement); + aValue = std::move(val); + } + aPreventCachingOfSandwich = false; + return rv; +} + +SMILValue SVGAnimatedPointList::SMILAnimatedPointList::GetBaseValue() const { + // To benefit from Return Value Optimization and avoid copy constructor calls + // due to our use of return-by-value, we must return the exact same object + // from ALL return points. This function must only return THIS variable: + SMILValue val; + + SMILValue tmp(&SVGPointListSMILType::sSingleton); + SVGPointListAndInfo* list = static_cast<SVGPointListAndInfo*>(tmp.mU.mPtr); + nsresult rv = list->CopyFrom(mVal->mBaseVal); + if (NS_SUCCEEDED(rv)) { + list->SetInfo(mElement); + std::swap(val, tmp); + } + return val; +} + +nsresult SVGAnimatedPointList::SMILAnimatedPointList::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == &SVGPointListSMILType::sSingleton, + "Unexpected type to assign animated value"); + if (aValue.mType == &SVGPointListSMILType::sSingleton) { + mVal->SetAnimValue(*static_cast<SVGPointListAndInfo*>(aValue.mU.mPtr), + mElement); + } + return NS_OK; +} + +void SVGAnimatedPointList::SMILAnimatedPointList::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->ClearAnimValue(mElement); + } +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedPointList.h b/dom/svg/SVGAnimatedPointList.h new file mode 100644 index 0000000000..a595f775ed --- /dev/null +++ b/dom/svg/SVGAnimatedPointList.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDPOINTLIST_H_ +#define DOM_SVG_SVGANIMATEDPOINTLIST_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "SVGPointList.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class DOMSVGPoint; +class DOMSVGPointList; +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +/** + * Class SVGAnimatedPointList + * + * Despite the fact that no SVGAnimatedPointList interface or objects exist + * in the SVG specification (unlike e.g. SVGAnimated*Length*List), we + * nevertheless have this internal class. (Note that there is an + * SVGAnimatedPoints interface, but that's quite different to + * SVGAnimatedLengthList since it is inherited by elements, as opposed to + * elements having members of that type.) The reason that we have this class is + * to provide a single locked down point of entry to the SVGPointList objects, + * which helps ensure that the DOM wrappers for SVGPointList objects' are + * always kept in sync. This is vitally important (see the comment in + * DOMSVGPointList::InternalListWillChangeTo) and frees consumers from having + * to know or worry about wrappers (or forget about them!) for the most part. + */ +class SVGAnimatedPointList { + // friends so that they can get write access to mBaseVal and mAnimVal + friend class dom::DOMSVGPoint; + friend class dom::DOMSVGPointList; + + public: + SVGAnimatedPointList() = default; + + /** + * Because it's so important that mBaseVal and its DOMSVGPointList wrapper + * (if any) be kept in sync (see the comment in + * DOMSVGPointList::InternalListWillChangeTo), this method returns a const + * reference. Only our friend classes may get mutable references to mBaseVal. + */ + const SVGPointList& GetBaseValue() const { return mBaseVal; } + + nsresult SetBaseValueString(const nsAString& aValue); + + void ClearBaseValue(); + + /** + * const! See comment for GetBaseValue! + */ + const SVGPointList& GetAnimValue() const { + return mAnimVal ? *mAnimVal : mBaseVal; + } + + nsresult SetAnimValue(const SVGPointList& aNewAnimValue, + dom::SVGElement* aElement); + + void ClearAnimValue(dom::SVGElement* aElement); + + /** + * Needed for correct DOM wrapper construction since GetAnimValue may + * actually return the baseVal! + */ + void* GetBaseValKey() const { return (void*)&mBaseVal; } + void* GetAnimValKey() const { return (void*)&mAnimVal; } + + bool IsAnimating() const { return !!mAnimVal; } + + UniquePtr<SMILAttr> ToSMILAttr(dom::SVGElement* aElement); + + private: + // mAnimVal is a pointer to allow us to determine if we're being animated or + // not. Making it a non-pointer member and using mAnimVal.IsEmpty() to check + // if we're animating is not an option, since that would break animation *to* + // the empty string (<set to="">). + + SVGPointList mBaseVal; + UniquePtr<SVGPointList> mAnimVal; + + struct SMILAnimatedPointList : public SMILAttr { + public: + SMILAnimatedPointList(SVGAnimatedPointList* aVal, dom::SVGElement* aElement) + : mVal(aVal), mElement(aElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedPointList* mVal; + dom::SVGElement* mElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDPOINTLIST_H_ diff --git a/dom/svg/SVGAnimatedPreserveAspectRatio.cpp b/dom/svg/SVGAnimatedPreserveAspectRatio.cpp new file mode 100644 index 0000000000..ee8fac3b8e --- /dev/null +++ b/dom/svg/SVGAnimatedPreserveAspectRatio.cpp @@ -0,0 +1,238 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedPreserveAspectRatio.h" + +#include "mozAutoDocUpdate.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/dom/SVGAnimatedPreserveAspectRatioBinding.h" +#include "SMILEnumType.h" +#include "SVGAttrTearoffTable.h" + +using namespace mozilla::dom; + +namespace mozilla { + +//////////////////////////////////////////////////////////////////////// +// SVGAnimatedPreserveAspectRatio class +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED( + DOMSVGAnimatedPreserveAspectRatio, mSVGElement) + +JSObject* DOMSVGAnimatedPreserveAspectRatio::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedPreserveAspectRatio_Binding::Wrap(aCx, this, aGivenProto); +} + +/* Implementation */ + +//---------------------------------------------------------------------- +// Helper class: AutoChangePreserveAspectRatioNotifier +// Stack-based helper class to pair calls to WillChangePreserveAspectRatio and +// DidChangePreserveAspectRatio. +class MOZ_RAII AutoChangePreserveAspectRatioNotifier { + public: + AutoChangePreserveAspectRatioNotifier( + SVGAnimatedPreserveAspectRatio* aPreserveAspectRatio, + SVGElement* aSVGElement, bool aDoSetAttr = true) + : mPreserveAspectRatio(aPreserveAspectRatio), + mSVGElement(aSVGElement), + mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mPreserveAspectRatio, "Expecting non-null preserveAspectRatio"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + if (mDoSetAttr) { + mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true); + mEmptyOrOldValue = + mSVGElement->WillChangePreserveAspectRatio(mUpdateBatch.ref()); + } + } + + ~AutoChangePreserveAspectRatioNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangePreserveAspectRatio(mEmptyOrOldValue, + mUpdateBatch.ref()); + } + if (mPreserveAspectRatio->mIsAnimated) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedPreserveAspectRatio* const mPreserveAspectRatio; + SVGElement* const mSVGElement; + Maybe<mozAutoDocUpdate> mUpdateBatch; + nsAttrValue mEmptyOrOldValue; + bool mDoSetAttr; +}; + +static SVGAttrTearoffTable<SVGAnimatedPreserveAspectRatio, + DOMSVGAnimatedPreserveAspectRatio> + sSVGAnimatedPAspectRatioTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedPreserveAspectRatio, + DOMSVGPreserveAspectRatio> + sBaseSVGPAspectRatioTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedPreserveAspectRatio, + DOMSVGPreserveAspectRatio> + sAnimSVGPAspectRatioTearoffTable; + +already_AddRefed<DOMSVGPreserveAspectRatio> +DOMSVGAnimatedPreserveAspectRatio::BaseVal() { + RefPtr<DOMSVGPreserveAspectRatio> domBaseVal = + sBaseSVGPAspectRatioTearoffTable.GetTearoff(mVal); + if (!domBaseVal) { + domBaseVal = new DOMSVGPreserveAspectRatio(mVal, mSVGElement, true); + sBaseSVGPAspectRatioTearoffTable.AddTearoff(mVal, domBaseVal); + } + + return domBaseVal.forget(); +} + +DOMSVGPreserveAspectRatio::~DOMSVGPreserveAspectRatio() { + if (mIsBaseValue) { + sBaseSVGPAspectRatioTearoffTable.RemoveTearoff(mVal); + } else { + sAnimSVGPAspectRatioTearoffTable.RemoveTearoff(mVal); + } +} + +already_AddRefed<DOMSVGPreserveAspectRatio> +DOMSVGAnimatedPreserveAspectRatio::AnimVal() { + RefPtr<DOMSVGPreserveAspectRatio> domAnimVal = + sAnimSVGPAspectRatioTearoffTable.GetTearoff(mVal); + if (!domAnimVal) { + domAnimVal = new DOMSVGPreserveAspectRatio(mVal, mSVGElement, false); + sAnimSVGPAspectRatioTearoffTable.AddTearoff(mVal, domAnimVal); + } + + return domAnimVal.forget(); +} + +nsresult SVGAnimatedPreserveAspectRatio::SetBaseValueString( + const nsAString& aValueAsString, SVGElement* aSVGElement, bool aDoSetAttr) { + SVGPreserveAspectRatio val; + nsresult res = SVGPreserveAspectRatio::FromString(aValueAsString, &val); + if (NS_FAILED(res)) { + return res; + } + + AutoChangePreserveAspectRatioNotifier notifier(this, aSVGElement, aDoSetAttr); + + mBaseVal = val; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } + + return NS_OK; +} + +void SVGAnimatedPreserveAspectRatio::GetBaseValueString( + nsAString& aValueAsString) const { + mBaseVal.ToString(aValueAsString); +} + +void SVGAnimatedPreserveAspectRatio::SetBaseValue( + const SVGPreserveAspectRatio& aValue, SVGElement* aSVGElement) { + if (mIsBaseSet && mBaseVal == aValue) { + return; + } + + AutoChangePreserveAspectRatioNotifier notifier(this, aSVGElement); + + mBaseVal = aValue; + mIsBaseSet = true; + if (!mIsAnimated) { + mAnimVal = mBaseVal; + } +} + +static uint64_t PackPreserveAspectRatio(const SVGPreserveAspectRatio& par) { + // All preserveAspectRatio values are enum values (do not interpolate), so we + // can safely collate them and treat them as a single enum as for SMIL. + uint64_t packed = 0; + packed |= uint64_t(par.GetAlign()) << 8; + packed |= uint64_t(par.GetMeetOrSlice()); + return packed; +} + +void SVGAnimatedPreserveAspectRatio::SetAnimValue(uint64_t aPackedValue, + SVGElement* aSVGElement) { + if (mIsAnimated && PackPreserveAspectRatio(mAnimVal) == aPackedValue) { + return; + } + mAnimVal.SetAlign(uint16_t((aPackedValue & 0xff00) >> 8)); + mAnimVal.SetMeetOrSlice(uint16_t(aPackedValue & 0xff)); + mIsAnimated = true; + aSVGElement->DidAnimatePreserveAspectRatio(); +} + +already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> +SVGAnimatedPreserveAspectRatio::ToDOMAnimatedPreserveAspectRatio( + SVGElement* aSVGElement) { + RefPtr<DOMSVGAnimatedPreserveAspectRatio> domAnimatedPAspectRatio = + sSVGAnimatedPAspectRatioTearoffTable.GetTearoff(this); + if (!domAnimatedPAspectRatio) { + domAnimatedPAspectRatio = + new DOMSVGAnimatedPreserveAspectRatio(this, aSVGElement); + sSVGAnimatedPAspectRatioTearoffTable.AddTearoff(this, + domAnimatedPAspectRatio); + } + return domAnimatedPAspectRatio.forget(); +} + +DOMSVGAnimatedPreserveAspectRatio::~DOMSVGAnimatedPreserveAspectRatio() { + sSVGAnimatedPAspectRatioTearoffTable.RemoveTearoff(mVal); +} + +UniquePtr<SMILAttr> SVGAnimatedPreserveAspectRatio::ToSMILAttr( + SVGElement* aSVGElement) { + return MakeUnique<SMILPreserveAspectRatio>(this, aSVGElement); +} + +// typedef for inner class, to make function signatures shorter below: +using SMILPreserveAspectRatio = + SVGAnimatedPreserveAspectRatio::SMILPreserveAspectRatio; + +nsresult SMILPreserveAspectRatio::ValueFromString( + const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SVGPreserveAspectRatio par; + nsresult res = SVGPreserveAspectRatio::FromString(aStr, &par); + NS_ENSURE_SUCCESS(res, res); + + SMILValue val(SMILEnumType::Singleton()); + val.mU.mUint = PackPreserveAspectRatio(par); + aValue = val; + aPreventCachingOfSandwich = false; + return NS_OK; +} + +SMILValue SMILPreserveAspectRatio::GetBaseValue() const { + SMILValue val(SMILEnumType::Singleton()); + val.mU.mUint = PackPreserveAspectRatio(mVal->GetBaseValue()); + return val; +} + +void SMILPreserveAspectRatio::ClearAnimValue() { + if (mVal->mIsAnimated) { + mVal->mIsAnimated = false; + mVal->mAnimVal = mVal->mBaseVal; + mSVGElement->DidAnimatePreserveAspectRatio(); + } +} + +nsresult SMILPreserveAspectRatio::SetAnimValue(const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILEnumType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILEnumType::Singleton()) { + mVal->SetAnimValue(aValue.mU.mUint, mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedPreserveAspectRatio.h b/dom/svg/SVGAnimatedPreserveAspectRatio.h new file mode 100644 index 0000000000..cc4721e42f --- /dev/null +++ b/dom/svg/SVGAnimatedPreserveAspectRatio.h @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDPRESERVEASPECTRATIO_H_ +#define DOM_SVG_SVGANIMATEDPRESERVEASPECTRATIO_H_ + +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "mozilla/SVGPreserveAspectRatio.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class DOMSVGAnimatedPreserveAspectRatio; +class SVGAnimationElement; +} // namespace dom + +class SVGAnimatedPreserveAspectRatio final { + friend class AutoChangePreserveAspectRatioNotifier; + + public: + void Init() { + mBaseVal.mAlign = + dom::SVGPreserveAspectRatio_Binding::SVG_PRESERVEASPECTRATIO_XMIDYMID; + mBaseVal.mMeetOrSlice = + dom::SVGPreserveAspectRatio_Binding::SVG_MEETORSLICE_MEET; + mAnimVal = mBaseVal; + mIsAnimated = false; + mIsBaseSet = false; + } + + nsresult SetBaseValueString(const nsAString& aValue, + dom::SVGElement* aSVGElement, bool aDoSetAttr); + void GetBaseValueString(nsAString& aValue) const; + + void SetBaseValue(const SVGPreserveAspectRatio& aValue, + dom::SVGElement* aSVGElement); + nsresult SetBaseAlign(uint16_t aAlign, dom::SVGElement* aSVGElement) { + if (aAlign < SVG_ALIGN_MIN_VALID || aAlign > SVG_ALIGN_MAX_VALID) { + return NS_ERROR_FAILURE; + } + SetBaseValue(SVGPreserveAspectRatio(static_cast<uint8_t>(aAlign), + mBaseVal.GetMeetOrSlice()), + aSVGElement); + return NS_OK; + } + nsresult SetBaseMeetOrSlice(uint16_t aMeetOrSlice, + dom::SVGElement* aSVGElement) { + if (aMeetOrSlice < SVG_MEETORSLICE_MIN_VALID || + aMeetOrSlice > SVG_MEETORSLICE_MAX_VALID) { + return NS_ERROR_FAILURE; + } + SetBaseValue(SVGPreserveAspectRatio(mBaseVal.GetAlign(), + static_cast<uint8_t>(aMeetOrSlice)), + aSVGElement); + return NS_OK; + } + void SetAnimValue(uint64_t aPackedValue, dom::SVGElement* aSVGElement); + + const SVGPreserveAspectRatio& GetBaseValue() const { return mBaseVal; } + const SVGPreserveAspectRatio& GetAnimValue() const { return mAnimVal; } + bool IsAnimated() const { return mIsAnimated; } + bool IsExplicitlySet() const { return mIsAnimated || mIsBaseSet; } + + already_AddRefed<dom::DOMSVGAnimatedPreserveAspectRatio> + ToDOMAnimatedPreserveAspectRatio(dom::SVGElement* aSVGElement); + UniquePtr<SMILAttr> ToSMILAttr(dom::SVGElement* aSVGElement); + + private: + SVGPreserveAspectRatio mAnimVal; + SVGPreserveAspectRatio mBaseVal; + bool mIsAnimated; + bool mIsBaseSet; + + public: + struct SMILPreserveAspectRatio final : public SMILAttr { + public: + SMILPreserveAspectRatio(SVGAnimatedPreserveAspectRatio* aVal, + dom::SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedPreserveAspectRatio* mVal; + dom::SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +namespace dom { +class DOMSVGAnimatedPreserveAspectRatio final : public nsWrapperCache { + ~DOMSVGAnimatedPreserveAspectRatio(); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING( + DOMSVGAnimatedPreserveAspectRatio) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS( + DOMSVGAnimatedPreserveAspectRatio) + + DOMSVGAnimatedPreserveAspectRatio(SVGAnimatedPreserveAspectRatio* aVal, + dom::SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // WebIDL + dom::SVGElement* GetParentObject() const { return mSVGElement; } + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // These aren't weak refs because new objects are returned each time + already_AddRefed<DOMSVGPreserveAspectRatio> BaseVal(); + already_AddRefed<DOMSVGPreserveAspectRatio> AnimVal(); + + protected: + // kept alive because it belongs to content: + SVGAnimatedPreserveAspectRatio* mVal; + RefPtr<dom::SVGElement> mSVGElement; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDPRESERVEASPECTRATIO_H_ diff --git a/dom/svg/SVGAnimatedRect.cpp b/dom/svg/SVGAnimatedRect.cpp new file mode 100644 index 0000000000..68e2728516 --- /dev/null +++ b/dom/svg/SVGAnimatedRect.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedRect.h" +#include "mozilla/dom/SVGAnimatedRectBinding.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGRect.h" +#include "SVGAnimatedViewBox.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(SVGAnimatedRect, mSVGElement) + +SVGAnimatedRect::SVGAnimatedRect(SVGAnimatedViewBox* aVal, + SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + +SVGAnimatedRect::~SVGAnimatedRect() { + SVGAnimatedViewBox::sSVGAnimatedRectTearoffTable.RemoveTearoff(mVal); +} + +already_AddRefed<SVGRect> SVGAnimatedRect::GetBaseVal() { + return mVal->ToDOMBaseVal(mSVGElement); +} + +already_AddRefed<SVGRect> SVGAnimatedRect::GetAnimVal() { + return mVal->ToDOMAnimVal(mSVGElement); +} + +JSObject* SVGAnimatedRect::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGAnimatedRect_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGAnimatedRect.h b/dom/svg/SVGAnimatedRect.h new file mode 100644 index 0000000000..afcf026fc3 --- /dev/null +++ b/dom/svg/SVGAnimatedRect.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDRECT_H_ +#define DOM_SVG_SVGANIMATEDRECT_H_ + +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/Attributes.h" +#include "nsWrapperCache.h" + +namespace mozilla { + +class SVGAnimatedViewBox; + +namespace dom { + +class SVGRect; + +// Despite the name of this class appearing to be generic, +// SVGAnimatedRect is only used for viewBox attributes. + +class SVGAnimatedRect final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SVGAnimatedRect) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(SVGAnimatedRect) + + SVGAnimatedRect(SVGAnimatedViewBox* aVal, SVGElement* aSVGElement); + + SVGElement* GetParentObject() const { return mSVGElement; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + already_AddRefed<SVGRect> GetBaseVal(); + + already_AddRefed<SVGRect> GetAnimVal(); + + private: + virtual ~SVGAnimatedRect(); + + SVGAnimatedViewBox* mVal; // kept alive because it belongs to content + RefPtr<SVGElement> mSVGElement; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDRECT_H_ diff --git a/dom/svg/SVGAnimatedString.cpp b/dom/svg/SVGAnimatedString.cpp new file mode 100644 index 0000000000..21a7954959 --- /dev/null +++ b/dom/svg/SVGAnimatedString.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedString.h" + +#include <utility> + +#include "SMILStringType.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/SMILValue.h" + +using namespace mozilla::dom; + +namespace mozilla { + +/* Implementation */ + +void SVGAnimatedString::SetBaseValue(const nsAString& aValue, + SVGElement* aSVGElement, bool aDoSetAttr) { + NS_ASSERTION(aSVGElement, "Null element passed to SetBaseValue"); + + mIsBaseSet = true; + if (aDoSetAttr) { + aSVGElement->SetStringBaseValue(mAttrEnum, aValue); + } + if (mAnimVal) { + aSVGElement->AnimationNeedsResample(); + } + + aSVGElement->DidChangeString(mAttrEnum); +} + +void SVGAnimatedString::GetAnimValue(nsAString& aResult, + const SVGElement* aSVGElement) const { + if (mAnimVal) { + aResult = *mAnimVal; + return; + } + + aSVGElement->GetStringBaseValue(mAttrEnum, aResult); +} + +void SVGAnimatedString::SetAnimValue(const nsAString& aValue, + SVGElement* aSVGElement) { + if (aSVGElement->IsStringAnimatable(mAttrEnum)) { + if (mAnimVal && mAnimVal->Equals(aValue)) { + return; + } + if (!mAnimVal) { + mAnimVal = MakeUnique<nsString>(); + } + *mAnimVal = aValue; + aSVGElement->DidAnimateString(mAttrEnum); + } +} + +UniquePtr<SMILAttr> SVGAnimatedString::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILString>(this, aSVGElement); +} + +nsresult SVGAnimatedString::SMILString::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SMILValue val(SMILStringType::Singleton()); + + *static_cast<nsAString*>(val.mU.mPtr) = aStr; + aValue = std::move(val); + aPreventCachingOfSandwich = false; + return NS_OK; +} + +SMILValue SVGAnimatedString::SMILString::GetBaseValue() const { + SMILValue val(SMILStringType::Singleton()); + mSVGElement->GetStringBaseValue(mVal->mAttrEnum, + *static_cast<nsAString*>(val.mU.mPtr)); + return val; +} + +void SVGAnimatedString::SMILString::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->mAnimVal = nullptr; + mSVGElement->DidAnimateString(mVal->mAttrEnum); + } +} + +nsresult SVGAnimatedString::SMILString::SetAnimValue(const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == SMILStringType::Singleton(), + "Unexpected type to assign animated value"); + if (aValue.mType == SMILStringType::Singleton()) { + mVal->SetAnimValue(*static_cast<nsAString*>(aValue.mU.mPtr), mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedString.h b/dom/svg/SVGAnimatedString.h new file mode 100644 index 0000000000..a86e4dec19 --- /dev/null +++ b/dom/svg/SVGAnimatedString.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDSTRING_H_ +#define DOM_SVG_SVGANIMATEDSTRING_H_ + +#include "nsError.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/SVGAnimatedClassOrString.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGElement; +} + +class SVGAnimatedString final : public SVGAnimatedClassOrString { + public: + using SVGElement = dom::SVGElement; + + void Init(uint8_t aAttrEnum) { + mAnimVal = nullptr; + mAttrEnum = aAttrEnum; + mIsBaseSet = false; + } + + void SetBaseValue(const nsAString& aValue, SVGElement* aSVGElement, + bool aDoSetAttr) override; + void GetBaseValue(nsAString& aValue, + const SVGElement* aSVGElement) const override { + aSVGElement->GetStringBaseValue(mAttrEnum, aValue); + } + + void SetAnimValue(const nsAString& aValue, SVGElement* aSVGElement); + void GetAnimValue(nsAString& aResult, + const SVGElement* aSVGElement) const override; + + // Returns true if the animated value of this string has been explicitly + // set (either by animation, or by taking on the base value which has been + // explicitly set by markup or a DOM call), false otherwise. + // If this returns false, the animated value is still valid, that is, + // usable, and represents the default base value of the attribute. + bool IsExplicitlySet() const { return !!mAnimVal || mIsBaseSet; } + + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + SVGAnimatedString() = default; + + SVGAnimatedString& operator=(const SVGAnimatedString& aOther) { + mAttrEnum = aOther.mAttrEnum; + mIsBaseSet = aOther.mIsBaseSet; + if (aOther.mAnimVal) { + mAnimVal = MakeUnique<nsString>(*aOther.mAnimVal); + } + return *this; + } + + SVGAnimatedString(const SVGAnimatedString& aOther) : SVGAnimatedString() { + *this = aOther; + } + + private: + // FIXME: Should probably use void string rather than UniquePtr<nsString>. + UniquePtr<nsString> mAnimVal; + uint8_t mAttrEnum = 0; // element specified tracking for attribute + bool mIsBaseSet = false; + + public: + struct SMILString : public SMILAttr { + public: + SMILString(SVGAnimatedString* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedString* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDSTRING_H_ diff --git a/dom/svg/SVGAnimatedTransformList.cpp b/dom/svg/SVGAnimatedTransformList.cpp new file mode 100644 index 0000000000..3f1f029a98 --- /dev/null +++ b/dom/svg/SVGAnimatedTransformList.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedTransformList.h" + +#include <utility> + +#include "DOMSVGAnimatedTransformList.h" +#include "SVGTransform.h" +#include "SVGTransformListSMILType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "mozilla/dom/SVGAnimationElement.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGTransform_Binding; + +namespace mozilla { + +nsresult SVGAnimatedTransformList::SetBaseValueString(const nsAString& aValue, + SVGElement* aSVGElement) { + SVGTransformList newBaseValue; + nsresult rv = newBaseValue.SetValueFromString(aValue); + if (NS_FAILED(rv)) { + return rv; + } + + return SetBaseValue(newBaseValue, aSVGElement); +} + +nsresult SVGAnimatedTransformList::SetBaseValue(const SVGTransformList& aValue, + SVGElement* aSVGElement) { + DOMSVGAnimatedTransformList* domWrapper = + DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // We must send this notification *before* changing mBaseVal! If the length + // of our baseVal is being reduced, our baseVal's DOM wrapper list may have + // to remove DOM items from itself, and any removed DOM items need to copy + // their internal counterpart values *before* we change them. + // + domWrapper->InternalBaseValListWillChangeLengthTo(aValue.Length()); + } + + // (This bool will be copied to our member-var, if attr-change succeeds.) + bool hadTransform = HasTransform(); + + // We don't need to call DidChange* here - we're only called by + // SVGElement::ParseAttribute under Element::SetAttr, + // which takes care of notifying. + + nsresult rv = mBaseVal.CopyFrom(aValue); + if (NS_FAILED(rv) && domWrapper) { + // Attempting to increase mBaseVal's length failed - reduce domWrapper + // back to the same length: + domWrapper->InternalBaseValListWillChangeLengthTo(mBaseVal.Length()); + } else { + mIsAttrSet = true; + // We only need to treat this as a creation or removal of a transform if the + // frame already exists and it didn't have an existing one. + mCreatedOrRemovedOnLastChange = + aSVGElement->GetPrimaryFrame() && !hadTransform; + } + return rv; +} + +void SVGAnimatedTransformList::ClearBaseValue() { + mCreatedOrRemovedOnLastChange = !HasTransform(); + + DOMSVGAnimatedTransformList* domWrapper = + DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // We must send this notification *before* changing mBaseVal! (See above.) + domWrapper->InternalBaseValListWillChangeLengthTo(0); + } + mBaseVal.Clear(); + mIsAttrSet = false; + // Caller notifies +} + +nsresult SVGAnimatedTransformList::SetAnimValue(const SVGTransformList& aValue, + SVGElement* aElement) { + bool prevSet = HasTransform() || aElement->GetAnimateMotionTransform(); + DOMSVGAnimatedTransformList* domWrapper = + DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // A new animation may totally change the number of items in the animVal + // list, replacing what was essentially a mirror of the baseVal list, or + // else replacing and overriding an existing animation. When this happens + // we must try and keep our animVal's DOM wrapper in sync (see the comment + // in DOMSVGAnimatedTransformList::InternalBaseValListWillChangeLengthTo). + // + // It's not possible for us to reliably distinguish between calls to this + // method that are setting a new sample for an existing animation, and + // calls that are setting the first sample of an animation that will + // override an existing animation. Happily it's cheap to just blindly + // notify our animVal's DOM wrapper of its internal counterpart's new value + // each time this method is called, so that's what we do. + // + // Note that we must send this notification *before* setting or changing + // mAnimVal! (See the comment in SetBaseValueString above.) + // + domWrapper->InternalAnimValListWillChangeLengthTo(aValue.Length()); + } + if (!mAnimVal) { + mAnimVal = MakeUnique<SVGTransformList>(); + } + nsresult rv = mAnimVal->CopyFrom(aValue); + if (NS_FAILED(rv)) { + // OOM. We clear the animation, and, importantly, ClearAnimValue() ensures + // that mAnimVal and its DOM wrapper (if any) will have the same length! + ClearAnimValue(aElement); + return rv; + } + int32_t modType; + if (prevSet) { + modType = MutationEvent_Binding::MODIFICATION; + } else { + modType = MutationEvent_Binding::ADDITION; + } + mCreatedOrRemovedOnLastChange = modType == MutationEvent_Binding::ADDITION; + aElement->DidAnimateTransformList(modType); + return NS_OK; +} + +void SVGAnimatedTransformList::ClearAnimValue(SVGElement* aElement) { + DOMSVGAnimatedTransformList* domWrapper = + DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); + if (domWrapper) { + // When all animation ends, animVal simply mirrors baseVal, which may have + // a different number of items to the last active animated value. We must + // keep the length of our animVal's DOM wrapper list in sync, and again we + // must do that before touching mAnimVal. See comments above. + // + domWrapper->InternalAnimValListWillChangeLengthTo(mBaseVal.Length()); + } + mAnimVal = nullptr; + int32_t modType; + if (HasTransform() || aElement->GetAnimateMotionTransform()) { + modType = MutationEvent_Binding::MODIFICATION; + } else { + modType = MutationEvent_Binding::REMOVAL; + } + mCreatedOrRemovedOnLastChange = modType == MutationEvent_Binding::REMOVAL; + aElement->DidAnimateTransformList(modType); +} + +bool SVGAnimatedTransformList::IsExplicitlySet() const { + // Like other methods of this name, we need to know when a transform value has + // been explicitly set. + // + // There are three ways an animated list can become set: + // 1) Markup -- we set mIsAttrSet to true on any successful call to + // SetBaseValueString and clear it on ClearBaseValue (as called by + // SVGElement::UnsetAttr or a failed SVGElement::ParseAttribute) + // 2) DOM call -- simply fetching the baseVal doesn't mean the transform value + // has been set. It is set if that baseVal has one or more transforms in + // the list. + // 3) Animation -- which will cause the mAnimVal member to be allocated + return mIsAttrSet || !mBaseVal.IsEmpty() || mAnimVal; +} + +UniquePtr<SMILAttr> SVGAnimatedTransformList::ToSMILAttr( + SVGElement* aSVGElement) { + return MakeUnique<SMILAnimatedTransformList>(this, aSVGElement); +} + +nsresult SVGAnimatedTransformList::SMILAnimatedTransformList::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + NS_ENSURE_TRUE(aSrcElement, NS_ERROR_FAILURE); + MOZ_ASSERT(aValue.IsNull(), + "aValue should have been cleared before calling ValueFromString"); + + const nsAttrValue* typeAttr = aSrcElement->GetParsedAttr(nsGkAtoms::type); + const nsAtom* transformType = nsGkAtoms::translate; // default val + if (typeAttr) { + if (typeAttr->Type() != nsAttrValue::eAtom) { + // Recognized values of |type| are parsed as an atom -- so if we have + // something other than an atom, then we know already our |type| is + // invalid. + return NS_ERROR_FAILURE; + } + transformType = typeAttr->GetAtomValue(); + } + + ParseValue(aStr, transformType, aValue); + aPreventCachingOfSandwich = false; + return aValue.IsNull() ? NS_ERROR_FAILURE : NS_OK; +} + +void SVGAnimatedTransformList::SMILAnimatedTransformList::ParseValue( + const nsAString& aSpec, const nsAtom* aTransformType, SMILValue& aResult) { + MOZ_ASSERT(aResult.IsNull(), "Unexpected type for SMIL value"); + + static_assert(SVGTransformSMILData::NUM_SIMPLE_PARAMS == 3, + "SVGSMILTransform constructor should be expecting array " + "with 3 params"); + + float params[3] = {0.f}; + int32_t numParsed = ParseParameterList(aSpec, params, 3); + uint16_t transformType; + + if (aTransformType == nsGkAtoms::translate) { + // tx [ty=0] + if (numParsed != 1 && numParsed != 2) return; + transformType = SVG_TRANSFORM_TRANSLATE; + } else if (aTransformType == nsGkAtoms::scale) { + // sx [sy=sx] + if (numParsed != 1 && numParsed != 2) return; + if (numParsed == 1) { + params[1] = params[0]; + } + transformType = SVG_TRANSFORM_SCALE; + } else if (aTransformType == nsGkAtoms::rotate) { + // r [cx=0 cy=0] + if (numParsed != 1 && numParsed != 3) return; + transformType = SVG_TRANSFORM_ROTATE; + } else if (aTransformType == nsGkAtoms::skewX) { + // x-angle + if (numParsed != 1) return; + transformType = SVG_TRANSFORM_SKEWX; + } else if (aTransformType == nsGkAtoms::skewY) { + // y-angle + if (numParsed != 1) return; + transformType = SVG_TRANSFORM_SKEWY; + } else { + return; + } + + SMILValue val(SVGTransformListSMILType::Singleton()); + SVGTransformSMILData transform(transformType, params); + if (NS_FAILED(SVGTransformListSMILType::AppendTransform(transform, val))) { + return; // OOM + } + + // Success! Populate our outparam with parsed value. + aResult = std::move(val); +} + +int32_t SVGAnimatedTransformList::SMILAnimatedTransformList::ParseParameterList( + const nsAString& aSpec, float* aVars, int32_t aNVars) { + int numArgsFound = 0; + + for (const auto& token : + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional>( + aSpec, ',') + .ToRange()) { + float f; + if (!SVGContentUtils::ParseNumber(token, f)) { + return -1; + } + if (numArgsFound < aNVars) { + aVars[numArgsFound] = f; + } + numArgsFound++; + } + return numArgsFound; +} + +SMILValue SVGAnimatedTransformList::SMILAnimatedTransformList::GetBaseValue() + const { + // To benefit from Return Value Optimization and avoid copy constructor calls + // due to our use of return-by-value, we must return the exact same object + // from ALL return points. This function must only return THIS variable: + SMILValue val(SVGTransformListSMILType::Singleton()); + if (!SVGTransformListSMILType::AppendTransforms(mVal->mBaseVal, val)) { + val = SMILValue(); + } + + return val; +} + +nsresult SVGAnimatedTransformList::SMILAnimatedTransformList::SetAnimValue( + const SMILValue& aNewAnimValue) { + MOZ_ASSERT(aNewAnimValue.mType == SVGTransformListSMILType::Singleton(), + "Unexpected type to assign animated value"); + SVGTransformList animVal; + if (!SVGTransformListSMILType::GetTransforms(aNewAnimValue, animVal.mItems)) { + return NS_ERROR_FAILURE; + } + + return mVal->SetAnimValue(animVal, mElement); +} + +void SVGAnimatedTransformList::SMILAnimatedTransformList::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->ClearAnimValue(mElement); + } +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedTransformList.h b/dom/svg/SVGAnimatedTransformList.h new file mode 100644 index 0000000000..9261ac7665 --- /dev/null +++ b/dom/svg/SVGAnimatedTransformList.h @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDTRANSFORMLIST_H_ +#define DOM_SVG_SVGANIMATEDTRANSFORMLIST_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SVGTransformList.h" + +class nsAtom; + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +class DOMSVGTransform; +} // namespace dom + +/** + * Class SVGAnimatedTransformList + * + * This class is very different to the SVG DOM interface of the same name found + * in the SVG specification. This is a lightweight internal class - see + * DOMSVGAnimatedTransformList for the heavier DOM class that wraps instances of + * this class and implements the SVG specification's SVGAnimatedTransformList + * DOM interface. + * + * Except where noted otherwise, this class' methods take care of keeping the + * appropriate DOM wrappers in sync (see the comment in + * DOMSVGAnimatedTransformList::InternalBaseValListWillChangeTo) so that their + * consumers don't need to concern themselves with that. + */ +class SVGAnimatedTransformList { + // friends so that they can get write access to mBaseVal + friend class dom::DOMSVGTransform; + friend class dom::DOMSVGTransformList; + + public: + SVGAnimatedTransformList() + : mIsAttrSet(false), mCreatedOrRemovedOnLastChange(true) {} + + /** + * Because it's so important that mBaseVal and its DOMSVGTransformList wrapper + * (if any) be kept in sync (see the comment in + * DOMSVGAnimatedTransformList::InternalBaseValListWillChangeTo), this method + * returns a const reference. Only our friend classes may get mutable + * references to mBaseVal. + */ + const SVGTransformList& GetBaseValue() const { return mBaseVal; } + + nsresult SetBaseValue(const SVGTransformList& aValue, + dom::SVGElement* aSVGElement); + + nsresult SetBaseValueString(const nsAString& aValue, + dom::SVGElement* aSVGElement); + + void ClearBaseValue(); + + const SVGTransformList& GetAnimValue() const { + return mAnimVal ? *mAnimVal : mBaseVal; + } + + nsresult SetAnimValue(const SVGTransformList& aValue, + dom::SVGElement* aElement); + + void ClearAnimValue(dom::SVGElement* aElement); + + /** + * Returns true if the corresponding transform attribute is set (or animated) + * to a valid value. Unlike HasTransform it will return true for an empty + * transform. + */ + bool IsExplicitlySet() const; + + /** + * Returns true if the corresponding transform attribute is set (or animated) + * to a valid value, such that we have at least one transform in our list. + * Returns false otherwise (e.g. if the transform attribute is missing or + * empty or invalid). + */ + bool HasTransform() const { + return (mAnimVal && !mAnimVal->IsEmpty()) || !mBaseVal.IsEmpty(); + } + + bool IsAnimating() const { return !!mAnimVal; } + + /** + * Returns true if the last change of this transform went from having to not + * having a transform or vice versa. + * + * (This is used as part of an optimization in + * SVGTransformableElement::GetAttributeChangeHint. That function reports an + * inexpensive nsChangeHint when a transform has just modified -- but this + * accessor lets it detect cases where the "modification" is actually creating + * a transform where we previously had none. These cases require a more + * thorough nsChangeHint.) + */ + bool CreatedOrRemovedOnLastChange() const { + return mCreatedOrRemovedOnLastChange; + } + + UniquePtr<SMILAttr> ToSMILAttr(dom::SVGElement* aSVGElement); + + private: + // mAnimVal is a pointer to allow us to determine if we're being animated or + // not. Making it a non-pointer member and using mAnimVal.IsEmpty() to check + // if we're animating is not an option, since that would break animation *to* + // the empty string (<set to="">). + + SVGTransformList mBaseVal; + UniquePtr<SVGTransformList> mAnimVal; + bool mIsAttrSet; + // See documentation for accessor. + bool mCreatedOrRemovedOnLastChange; + + struct SMILAnimatedTransformList : public SMILAttr { + public: + SMILAnimatedTransformList(SVGAnimatedTransformList* aVal, + dom::SVGElement* aSVGElement) + : mVal(aVal), mElement(aSVGElement) {} + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aNewAnimValue) override; + + protected: + static void ParseValue(const nsAString& aSpec, const nsAtom* aTransformType, + SMILValue& aResult); + static int32_t ParseParameterList(const nsAString& aSpec, float* aVars, + int32_t aNVars); + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedTransformList* mVal; + dom::SVGElement* mElement; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDTRANSFORMLIST_H_ diff --git a/dom/svg/SVGAnimatedViewBox.cpp b/dom/svg/SVGAnimatedViewBox.cpp new file mode 100644 index 0000000000..7f3094228a --- /dev/null +++ b/dom/svg/SVGAnimatedViewBox.cpp @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAnimatedViewBox.h" + +#include "mozAutoDocUpdate.h" +#include "mozilla/Maybe.h" +#include <utility> + +#include "SVGViewBoxSMILType.h" +#include "mozilla/SMILValue.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/dom/SVGRect.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsTextFormatter.h" + +using namespace mozilla::dom; + +namespace mozilla { + +#define NUM_VIEWBOX_COMPONENTS 4 + +/* Implementation of SVGViewBox methods */ + +bool SVGViewBox::operator==(const SVGViewBox& aOther) const { + if (&aOther == this) return true; + + return (none && aOther.none) || + (!none && !aOther.none && x == aOther.x && y == aOther.y && + width == aOther.width && height == aOther.height); +} + +/* static */ +nsresult SVGViewBox::FromString(const nsAString& aStr, SVGViewBox* aViewBox) { + if (aStr.EqualsLiteral("none")) { + aViewBox->none = true; + return NS_OK; + } + + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional> + tokenizer(aStr, ','); + float vals[NUM_VIEWBOX_COMPONENTS]; + uint32_t i; + for (i = 0; i < NUM_VIEWBOX_COMPONENTS && tokenizer.hasMoreTokens(); ++i) { + if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), vals[i])) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + } + + if (i != NUM_VIEWBOX_COMPONENTS || // Too few values. + tokenizer.hasMoreTokens() || // Too many values. + tokenizer.separatorAfterCurrentToken()) { // Trailing comma. + return NS_ERROR_DOM_SYNTAX_ERR; + } + + aViewBox->x = vals[0]; + aViewBox->y = vals[1]; + aViewBox->width = vals[2]; + aViewBox->height = vals[3]; + aViewBox->none = false; + + return NS_OK; +} + +static SVGAttrTearoffTable<SVGAnimatedViewBox, SVGRect> + sBaseSVGViewBoxTearoffTable; +static SVGAttrTearoffTable<SVGAnimatedViewBox, SVGRect> + sAnimSVGViewBoxTearoffTable; +SVGAttrTearoffTable<SVGAnimatedViewBox, SVGAnimatedRect> + SVGAnimatedViewBox::sSVGAnimatedRectTearoffTable; + +//---------------------------------------------------------------------- +// Helper class: AutoChangeViewBoxNotifier +// Stack-based helper class to pair calls to WillChangeViewBox and +// DidChangeViewBox. +class MOZ_RAII AutoChangeViewBoxNotifier { + public: + AutoChangeViewBoxNotifier(SVGAnimatedViewBox* aViewBox, + SVGElement* aSVGElement, bool aDoSetAttr = true) + : mViewBox(aViewBox), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { + MOZ_ASSERT(mViewBox, "Expecting non-null viewBox"); + MOZ_ASSERT(mSVGElement, "Expecting non-null element"); + + if (mDoSetAttr) { + mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true); + mEmptyOrOldValue = mSVGElement->WillChangeViewBox(mUpdateBatch.ref()); + } + } + + ~AutoChangeViewBoxNotifier() { + if (mDoSetAttr) { + mSVGElement->DidChangeViewBox(mEmptyOrOldValue, mUpdateBatch.ref()); + } + if (mViewBox->mAnimVal) { + mSVGElement->AnimationNeedsResample(); + } + } + + private: + SVGAnimatedViewBox* const mViewBox; + SVGElement* const mSVGElement; + Maybe<mozAutoDocUpdate> mUpdateBatch; + nsAttrValue mEmptyOrOldValue; + bool mDoSetAttr; +}; + +/* Implementation of SVGAnimatedViewBox methods */ + +void SVGAnimatedViewBox::Init() { + mHasBaseVal = false; + // We shouldn't use mBaseVal for rendering (its usages should be guarded with + // "mHasBaseVal" checks), but just in case we do by accident, this will + // ensure that we treat it as "none" and ignore its numeric values: + mBaseVal.none = true; + + mAnimVal = nullptr; +} + +bool SVGAnimatedViewBox::HasRect() const { + // Check mAnimVal if we have one; otherwise, check mBaseVal if we have one; + // otherwise, just return false (we clearly do not have a rect). + const SVGViewBox* rect = mAnimVal.get(); + if (!rect) { + if (!mHasBaseVal) { + // no anim val, no base val --> no viewbox rect + return false; + } + rect = &mBaseVal; + } + + return !rect->none && rect->width >= 0 && rect->height >= 0; +} + +void SVGAnimatedViewBox::SetAnimValue(const SVGViewBox& aRect, + SVGElement* aSVGElement) { + if (!mAnimVal) { + // it's okay if allocation fails - and no point in reporting that + mAnimVal = MakeUnique<SVGViewBox>(aRect); + } else { + if (aRect == *mAnimVal) { + return; + } + *mAnimVal = aRect; + } + aSVGElement->DidAnimateViewBox(); +} + +void SVGAnimatedViewBox::SetBaseValue(const SVGViewBox& aRect, + SVGElement* aSVGElement) { + if (!mHasBaseVal || mBaseVal == aRect) { + // This method is used to set a single x, y, width + // or height value. It can't create a base value + // as the other components may be undefined. We record + // the new value though, so as not to lose data. + mBaseVal = aRect; + return; + } + + AutoChangeViewBoxNotifier notifier(this, aSVGElement); + + mBaseVal = aRect; + mHasBaseVal = true; +} + +nsresult SVGAnimatedViewBox::SetBaseValueString(const nsAString& aValue, + SVGElement* aSVGElement, + bool aDoSetAttr) { + SVGViewBox viewBox; + + nsresult rv = SVGViewBox::FromString(aValue, &viewBox); + if (NS_FAILED(rv)) { + return rv; + } + // Comparison against mBaseVal is only valid if we currently have a base val. + if (mHasBaseVal && viewBox == mBaseVal) { + return NS_OK; + } + + AutoChangeViewBoxNotifier notifier(this, aSVGElement, aDoSetAttr); + mHasBaseVal = true; + mBaseVal = viewBox; + + return NS_OK; +} + +void SVGAnimatedViewBox::GetBaseValueString(nsAString& aValue) const { + if (mBaseVal.none) { + aValue.AssignLiteral("none"); + return; + } + nsTextFormatter::ssprintf(aValue, u"%g %g %g %g", (double)mBaseVal.x, + (double)mBaseVal.y, (double)mBaseVal.width, + (double)mBaseVal.height); +} + +already_AddRefed<SVGAnimatedRect> SVGAnimatedViewBox::ToSVGAnimatedRect( + SVGElement* aSVGElement) { + RefPtr<SVGAnimatedRect> domAnimatedRect = + sSVGAnimatedRectTearoffTable.GetTearoff(this); + if (!domAnimatedRect) { + domAnimatedRect = new SVGAnimatedRect(this, aSVGElement); + sSVGAnimatedRectTearoffTable.AddTearoff(this, domAnimatedRect); + } + + return domAnimatedRect.forget(); +} + +already_AddRefed<SVGRect> SVGAnimatedViewBox::ToDOMBaseVal( + SVGElement* aSVGElement) { + if (!mHasBaseVal || mBaseVal.none) { + return nullptr; + } + + RefPtr<SVGRect> domBaseVal = sBaseSVGViewBoxTearoffTable.GetTearoff(this); + if (!domBaseVal) { + domBaseVal = new SVGRect(this, aSVGElement, SVGRect::RectType::BaseValue); + sBaseSVGViewBoxTearoffTable.AddTearoff(this, domBaseVal); + } + + return domBaseVal.forget(); +} + +SVGRect::~SVGRect() { + switch (mType) { + case RectType::BaseValue: + sBaseSVGViewBoxTearoffTable.RemoveTearoff(mVal); + break; + case RectType::AnimValue: + sAnimSVGViewBoxTearoffTable.RemoveTearoff(mVal); + break; + default: + break; + } +} + +already_AddRefed<SVGRect> SVGAnimatedViewBox::ToDOMAnimVal( + SVGElement* aSVGElement) { + if ((mAnimVal && mAnimVal->none) || + (!mAnimVal && (!mHasBaseVal || mBaseVal.none))) { + return nullptr; + } + + RefPtr<SVGRect> domAnimVal = sAnimSVGViewBoxTearoffTable.GetTearoff(this); + if (!domAnimVal) { + domAnimVal = new SVGRect(this, aSVGElement, SVGRect::RectType::AnimValue); + sAnimSVGViewBoxTearoffTable.AddTearoff(this, domAnimVal); + } + + return domAnimVal.forget(); +} + +UniquePtr<SMILAttr> SVGAnimatedViewBox::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique<SMILViewBox>(this, aSVGElement); +} + +nsresult SVGAnimatedViewBox::SMILViewBox ::ValueFromString( + const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SVGViewBox viewBox; + nsresult res = SVGViewBox::FromString(aStr, &viewBox); + if (NS_FAILED(res)) { + return res; + } + SMILValue val(&SVGViewBoxSMILType::sSingleton); + *static_cast<SVGViewBox*>(val.mU.mPtr) = viewBox; + aValue = std::move(val); + aPreventCachingOfSandwich = false; + + return NS_OK; +} + +SMILValue SVGAnimatedViewBox::SMILViewBox::GetBaseValue() const { + SMILValue val(&SVGViewBoxSMILType::sSingleton); + *static_cast<SVGViewBox*>(val.mU.mPtr) = mVal->mBaseVal; + return val; +} + +void SVGAnimatedViewBox::SMILViewBox::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->mAnimVal = nullptr; + mSVGElement->DidAnimateViewBox(); + } +} + +nsresult SVGAnimatedViewBox::SMILViewBox::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == &SVGViewBoxSMILType::sSingleton, + "Unexpected type to assign animated value"); + if (aValue.mType == &SVGViewBoxSMILType::sSingleton) { + SVGViewBox& vb = *static_cast<SVGViewBox*>(aValue.mU.mPtr); + mVal->SetAnimValue(vb, mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedViewBox.h b/dom/svg/SVGAnimatedViewBox.h new file mode 100644 index 0000000000..edc67e1d24 --- /dev/null +++ b/dom/svg/SVGAnimatedViewBox.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDVIEWBOX_H_ +#define DOM_SVG_SVGANIMATEDVIEWBOX_H_ + +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SVGAnimatedRect.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGRect; +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +struct SVGViewBox { + float x, y; + float width, height; + bool none; + + SVGViewBox() : x(0.0), y(0.0), width(0.0), height(0.0), none(true) {} + SVGViewBox(float aX, float aY, float aWidth, float aHeight) + : x(aX), y(aY), width(aWidth), height(aHeight), none(false) {} + bool operator==(const SVGViewBox& aOther) const; + + static nsresult FromString(const nsAString& aStr, SVGViewBox* aViewBox); +}; + +class SVGAnimatedViewBox { + public: + friend class AutoChangeViewBoxNotifier; + using SVGElement = dom::SVGElement; + + void Init(); + + /** + * Returns true if the corresponding "viewBox" attribute defined a rectangle + * with finite values and nonnegative width/height. + * Returns false if the viewBox was set to an invalid + * string, or if any of the four rect values were too big to store in a + * float, or the width/height are negative. + */ + bool HasRect() const; + + /** + * Returns true if the corresponding "viewBox" attribute either defined a + * rectangle with finite values or the special "none" value. + */ + bool IsExplicitlySet() const { + if (mAnimVal || mHasBaseVal) { + const SVGViewBox& rect = GetAnimValue(); + return rect.none || (rect.width >= 0 && rect.height >= 0); + } + return false; + } + + const SVGViewBox& GetBaseValue() const { return mBaseVal; } + void SetBaseValue(const SVGViewBox& aRect, SVGElement* aSVGElement); + const SVGViewBox& GetAnimValue() const { + return mAnimVal ? *mAnimVal : mBaseVal; + } + void SetAnimValue(const SVGViewBox& aRect, SVGElement* aSVGElement); + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement, + bool aDoSetAttr); + void GetBaseValueString(nsAString& aValue) const; + + already_AddRefed<dom::SVGAnimatedRect> ToSVGAnimatedRect( + SVGElement* aSVGElement); + + already_AddRefed<dom::SVGRect> ToDOMBaseVal(SVGElement* aSVGElement); + + already_AddRefed<dom::SVGRect> ToDOMAnimVal(SVGElement* aSVGElement); + + UniquePtr<SMILAttr> ToSMILAttr(SVGElement* aSVGElement); + + private: + SVGViewBox mBaseVal; + UniquePtr<SVGViewBox> mAnimVal; + bool mHasBaseVal; + + public: + struct SMILViewBox : public SMILAttr { + public: + SMILViewBox(SVGAnimatedViewBox* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedViewBox* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; + + static SVGAttrTearoffTable<SVGAnimatedViewBox, dom::SVGAnimatedRect> + sSVGAnimatedRectTearoffTable; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDVIEWBOX_H_ diff --git a/dom/svg/SVGAnimationElement.cpp b/dom/svg/SVGAnimationElement.cpp new file mode 100644 index 0000000000..f0dffe8db2 --- /dev/null +++ b/dom/svg/SVGAnimationElement.cpp @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/ElementInlines.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/SMILAnimationFunction.h" +#include "mozilla/SMILTimeContainer.h" +#include "mozilla/SVGObserverUtils.h" +#include "nsContentUtils.h" +#include "nsIContentInlines.h" +#include "nsIReferrerInfo.h" +#include "nsIURI.h" +#include "prtime.h" + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGAnimationElement, SVGAnimationElementBase) +NS_IMPL_RELEASE_INHERITED(SVGAnimationElement, SVGAnimationElementBase) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAnimationElement) + NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGAnimationElement) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests) +NS_INTERFACE_MAP_END_INHERITING(SVGAnimationElementBase) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAnimationElement, SVGAnimationElementBase, + mHrefTarget, mTimedElement) + +//---------------------------------------------------------------------- +// Implementation + +SVGAnimationElement::SVGAnimationElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGAnimationElementBase(std::move(aNodeInfo)), mHrefTarget(this) {} + +nsresult SVGAnimationElement::Init() { + nsresult rv = SVGAnimationElementBase::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mTimedElement.SetAnimationElement(this); + AnimationFunction().SetAnimationElement(this); + mTimedElement.SetTimeClient(&AnimationFunction()); + + return NS_OK; +} + +//---------------------------------------------------------------------- + +Element* SVGAnimationElement::GetTargetElementContent() { + if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) || + HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { + return mHrefTarget.get(); + } + MOZ_ASSERT(!mHrefTarget.get(), + "We shouldn't have a href target " + "if we don't have an xlink:href or href attribute"); + + // No "href" or "xlink:href" attribute --> I should target my parent. + // + // Note that we want to use GetParentElement instead of the flattened tree to + // allow <use><animate>, for example. + return GetParentElement(); +} + +bool SVGAnimationElement::GetTargetAttributeName(int32_t* aNamespaceID, + nsAtom** aLocalName) const { + const nsAttrValue* nameAttr = mAttrs.GetAttr(nsGkAtoms::attributeName); + + if (!nameAttr) return false; + + NS_ASSERTION(nameAttr->Type() == nsAttrValue::eAtom, + "attributeName should have been parsed as an atom"); + + return NS_SUCCEEDED(nsContentUtils::SplitQName( + this, nsDependentAtomString(nameAttr->GetAtomValue()), aNamespaceID, + aLocalName)); +} + +SMILTimedElement& SVGAnimationElement::TimedElement() { return mTimedElement; } + +SVGElement* SVGAnimationElement::GetTargetElement() { + FlushAnimations(); + + // We'll just call the other GetTargetElement method, and QI to the right type + nsIContent* target = GetTargetElementContent(); + + return (target && target->IsSVGElement()) ? static_cast<SVGElement*>(target) + : nullptr; +} + +float SVGAnimationElement::GetStartTime(ErrorResult& rv) { + FlushAnimations(); + + SMILTimeValue startTime = mTimedElement.GetStartTime(); + if (!startTime.IsDefinite()) { + rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return 0.f; + } + + return float(double(startTime.GetMillis()) / PR_MSEC_PER_SEC); +} + +float SVGAnimationElement::GetCurrentTimeAsFloat() { + // Not necessary to call FlushAnimations() for this + + SMILTimeContainer* root = GetTimeContainer(); + if (root) { + return float(double(root->GetCurrentTimeAsSMILTime()) / PR_MSEC_PER_SEC); + } + + return 0.0f; +} + +float SVGAnimationElement::GetSimpleDuration(ErrorResult& rv) { + // Not necessary to call FlushAnimations() for this + + SMILTimeValue simpleDur = mTimedElement.GetSimpleDuration(); + if (!simpleDur.IsDefinite()) { + rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return 0.f; + } + + return float(double(simpleDur.GetMillis()) / PR_MSEC_PER_SEC); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGAnimationElement::BindToTree(BindContext& aContext, + nsINode& aParent) { + MOZ_ASSERT(!mHrefTarget.get(), + "Shouldn't have href-target yet (or it should've been cleared)"); + nsresult rv = SVGAnimationElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + // Add myself to the animation controller's master set of animation elements. + if (Document* doc = aContext.GetComposedDoc()) { + if (SMILAnimationController* controller = doc->GetAnimationController()) { + controller->RegisterAnimationElement(this); + } + const nsAttrValue* href = + HasAttr(kNameSpaceID_None, nsGkAtoms::href) + ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None) + : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (href) { + nsAutoString hrefStr; + href->ToString(hrefStr); + + UpdateHrefTarget(hrefStr); + } + + mTimedElement.BindToTree(*this); + } + + AnimationNeedsResample(); + + return NS_OK; +} + +void SVGAnimationElement::UnbindFromTree(bool aNullParent) { + SMILAnimationController* controller = OwnerDoc()->GetAnimationController(); + if (controller) { + controller->UnregisterAnimationElement(this); + } + + mHrefTarget.Unlink(); + mTimedElement.DissolveReferences(); + + AnimationNeedsResample(); + + SVGAnimationElementBase::UnbindFromTree(aNullParent); +} + +bool SVGAnimationElement::ParseAttribute(int32_t aNamespaceID, + nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None) { + // Deal with target-related attributes here + if (aAttribute == nsGkAtoms::attributeName) { + aResult.ParseAtom(aValue); + AnimationNeedsResample(); + return true; + } + + nsresult rv = NS_ERROR_FAILURE; + + // First let the animation function try to parse it... + bool foundMatch = + AnimationFunction().SetAttr(aAttribute, aValue, aResult, &rv); + + // ... and if that didn't recognize the attribute, let the timed element + // try to parse it. + if (!foundMatch) { + foundMatch = + mTimedElement.SetAttr(aAttribute, aValue, aResult, *this, &rv); + } + + if (foundMatch) { + AnimationNeedsResample(); + if (NS_FAILED(rv)) { + ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue); + return false; + } + return true; + } + } + + return SVGAnimationElementBase::ParseAttribute( + aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); +} + +nsresult SVGAnimationElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (!aValue && aNamespaceID == kNameSpaceID_None) { + // Attribute is being removed. + if (AnimationFunction().UnsetAttr(aName) || + mTimedElement.UnsetAttr(aName)) { + AnimationNeedsResample(); + } + } + + nsresult rv = SVGAnimationElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); + + if (SVGTests::IsConditionalProcessingAttribute(aName)) { + bool isDisabled = !SVGTests::PassesConditionalProcessingTests(); + if (mTimedElement.SetIsDisabled(isDisabled)) { + AnimationNeedsResample(); + } + } + + if (!IsInComposedDoc()) { + return rv; + } + + if (!((aNamespaceID == kNameSpaceID_None || + aNamespaceID == kNameSpaceID_XLink) && + aName == nsGkAtoms::href)) { + return rv; + } + + if (!aValue) { + if (aNamespaceID == kNameSpaceID_None) { + mHrefTarget.Unlink(); + AnimationTargetChanged(); + + // After unsetting href, we may still have xlink:href, so we + // should try to add it back. + const nsAttrValue* xlinkHref = + mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (xlinkHref) { + UpdateHrefTarget(xlinkHref->GetStringValue()); + } + } else if (!HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { + mHrefTarget.Unlink(); + AnimationTargetChanged(); + } // else: we unset xlink:href, but we still have href attribute, so keep + // mHrefTarget linking to href. + } else if (!(aNamespaceID == kNameSpaceID_XLink && + HasAttr(kNameSpaceID_None, nsGkAtoms::href))) { + // Note: "href" takes priority over xlink:href. So if "xlink:href" is being + // set here, we only let that update our target if "href" is *unset*. + MOZ_ASSERT(aValue->Type() == nsAttrValue::eString, + "Expected href attribute to be string type"); + UpdateHrefTarget(aValue->GetStringValue()); + } // else: we're not yet in a document -- we'll update the target on + // next BindToTree call. + + return rv; +} + +//---------------------------------------------------------------------- +// SVG utility methods + +void SVGAnimationElement::ActivateByHyperlink() { + FlushAnimations(); + + // The behavior for when the target is an animation element is defined in + // SMIL Animation: + // http://www.w3.org/TR/smil-animation/#HyperlinkSemantics + SMILTimeValue seekTime = mTimedElement.GetHyperlinkTime(); + if (seekTime.IsDefinite()) { + SMILTimeContainer* timeContainer = GetTimeContainer(); + if (timeContainer) { + timeContainer->SetCurrentTime(seekTime.GetMillis()); + AnimationNeedsResample(); + // As with SVGSVGElement::SetCurrentTime, we need to trigger + // a synchronous sample now. + FlushAnimations(); + } + // else, silently fail. We mustn't be part of an SVG document fragment that + // is attached to the document tree so there's nothing we can do here + } else { + BeginElement(IgnoreErrors()); + } +} + +//---------------------------------------------------------------------- +// Implementation helpers + +SMILTimeContainer* SVGAnimationElement::GetTimeContainer() { + SVGSVGElement* element = SVGContentUtils::GetOuterSVGElement(this); + + if (element) { + return element->GetTimedDocumentRoot(); + } + + return nullptr; +} + +void SVGAnimationElement::BeginElementAt(float offset, ErrorResult& rv) { + // Make sure the timegraph is up-to-date + FlushAnimations(); + + // This will fail if we're not attached to a time container (SVG document + // fragment). + rv = mTimedElement.BeginElementAt(offset); + if (rv.Failed()) return; + + AnimationNeedsResample(); + // Force synchronous sample so that events resulting from this call arrive in + // the expected order and we get an up-to-date paint. + FlushAnimations(); +} + +void SVGAnimationElement::EndElementAt(float offset, ErrorResult& rv) { + // Make sure the timegraph is up-to-date + FlushAnimations(); + + rv = mTimedElement.EndElementAt(offset); + if (rv.Failed()) return; + + AnimationNeedsResample(); + // Force synchronous sample + FlushAnimations(); +} + +bool SVGAnimationElement::IsEventAttributeNameInternal(nsAtom* aName) { + return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL); +} + +void SVGAnimationElement::UpdateHrefTarget(const nsAString& aHrefStr) { + nsCOMPtr<nsIURI> baseURI = GetBaseURI(); + if (nsContentUtils::IsLocalRefURL(aHrefStr)) { + baseURI = SVGObserverUtils::GetBaseURLForLocalRef(this, baseURI); + } + nsCOMPtr<nsIURI> targetURI; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), aHrefStr, + OwnerDoc(), baseURI); + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForSVGResources(OwnerDoc()); + + mHrefTarget.ResetToURIFragmentID(this, targetURI, referrerInfo); + AnimationTargetChanged(); +} + +void SVGAnimationElement::AnimationTargetChanged() { + mTimedElement.HandleTargetElementChange(GetTargetElementContent()); + AnimationNeedsResample(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGAnimationElement.h b/dom/svg/SVGAnimationElement.h new file mode 100644 index 0000000000..28df2097ed --- /dev/null +++ b/dom/svg/SVGAnimationElement.h @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATIONELEMENT_H_ +#define DOM_SVG_SVGANIMATIONELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILTimedElement.h" +#include "mozilla/dom/IDTracker.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGTests.h" + +// {f80ef85f-ef48-401a-8aed-1652312326b0} +#define MOZILLA_SVGANIMATIONELEMENT_IID \ + { \ + 0xf80ef85f, 0xef48, 0x401a, { \ + 0x8a, 0xed, 0x16, 0x52, 0x31, 0x23, 0x26, 0xb0 \ + } \ + } + +namespace mozilla::dom { + +using SVGAnimationElementBase = SVGElement; + +class SVGAnimationElement : public SVGAnimationElementBase, public SVGTests { + protected: + explicit SVGAnimationElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + nsresult Init(); + virtual ~SVGAnimationElement() = default; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_SVGANIMATIONELEMENT_IID) + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGAnimationElement, + SVGAnimationElementBase) + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override = 0; + + // nsIContent specializations + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + + // Element specializations + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + + Element* GetTargetElementContent(); + virtual bool GetTargetAttributeName(int32_t* aNamespaceID, + nsAtom** aLocalName) const; + mozilla::SMILTimedElement& TimedElement(); + mozilla::SMILTimeContainer* GetTimeContainer(); + virtual SMILAnimationFunction& AnimationFunction() = 0; + + bool IsEventAttributeNameInternal(nsAtom* aName) override; + + // Utility methods for within SVG + void ActivateByHyperlink(); + + // WebIDL + SVGElement* GetTargetElement(); + float GetStartTime(ErrorResult& rv); + float GetCurrentTimeAsFloat(); + float GetSimpleDuration(ErrorResult& rv); + void BeginElement(ErrorResult& rv) { BeginElementAt(0.f, rv); } + void BeginElementAt(float offset, ErrorResult& rv); + void EndElement(ErrorResult& rv) { EndElementAt(0.f, rv); } + void EndElementAt(float offset, ErrorResult& rv); + + // SVGTests + SVGElement* AsSVGElement() final { return this; } + + protected: + // SVGElement overrides + + void UpdateHrefTarget(const nsAString& aHrefStr); + void AnimationTargetChanged(); + + /** + * Helper that provides a reference to the element with the ID that is + * referenced by the animation element's 'href' attribute, if any, and that + * will notify the animation element if the element that that ID identifies + * changes to a different element (or none). (If the 'href' attribute is not + * specified, then the animation target is the parent element and this helper + * is not used.) + */ + class HrefTargetTracker final : public IDTracker { + public: + explicit HrefTargetTracker(SVGAnimationElement* aAnimationElement) + : mAnimationElement(aAnimationElement) {} + + protected: + // We need to be notified when target changes, in order to request a + // sample (which will clear animation effects from old target and apply + // them to the new target) and update any event registrations. + void ElementChanged(Element* aFrom, Element* aTo) override { + IDTracker::ElementChanged(aFrom, aTo); + mAnimationElement->AnimationTargetChanged(); + } + + // We need to override IsPersistent to get persistent tracking (beyond the + // first time the target changes) + bool IsPersistent() override { return true; } + + private: + SVGAnimationElement* const mAnimationElement; + }; + + HrefTargetTracker mHrefTarget; + mozilla::SMILTimedElement mTimedElement; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGAnimationElement, + MOZILLA_SVGANIMATIONELEMENT_IID) + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGANIMATIONELEMENT_H_ diff --git a/dom/svg/SVGAttrTearoffTable.h b/dom/svg/SVGAttrTearoffTable.h new file mode 100644 index 0000000000..888ea5c97f --- /dev/null +++ b/dom/svg/SVGAttrTearoffTable.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGATTRTEAROFFTABLE_H_ +#define DOM_SVG_SVGATTRTEAROFFTABLE_H_ + +#include "mozilla/DebugOnly.h" +#include "nsTHashMap.h" +#include "nsDebug.h" +#include "nsHashKeys.h" + +namespace mozilla { + +/** + * Global hashmap to associate internal SVG data types (e.g. SVGAnimatedLength) + * with DOM tear-off objects (e.g. DOMSVGLength). This allows us to always + * return the same object for subsequent requests for DOM objects. + * + * We don't keep an owning reference to the tear-off objects so they are + * responsible for removing themselves from this table when they die. + */ +template <class SimpleType, class TearoffType> +class SVGAttrTearoffTable { + public: +#ifdef DEBUG + ~SVGAttrTearoffTable() { + MOZ_ASSERT(!mTable, "Tear-off objects remain in hashtable at shutdown."); + } +#endif + + TearoffType* GetTearoff(SimpleType* aSimple); + + void AddTearoff(SimpleType* aSimple, TearoffType* aTearoff); + + void RemoveTearoff(SimpleType* aSimple); + + private: + using SimpleTypePtrKey = nsPtrHashKey<SimpleType>; + using TearoffTable = nsTHashMap<SimpleTypePtrKey, TearoffType*>; + + TearoffTable* mTable; +}; + +template <class SimpleType, class TearoffType> +TearoffType* SVGAttrTearoffTable<SimpleType, TearoffType>::GetTearoff( + SimpleType* aSimple) { + if (!mTable) return nullptr; + + TearoffType* tearoff = nullptr; + + DebugOnly<bool> found = mTable->Get(aSimple, &tearoff); + MOZ_ASSERT(!found || tearoff, + "null pointer stored in attribute tear-off map"); + + return tearoff; +} + +template <class SimpleType, class TearoffType> +void SVGAttrTearoffTable<SimpleType, TearoffType>::AddTearoff( + SimpleType* aSimple, TearoffType* aTearoff) { + if (!mTable) { + mTable = new TearoffTable; + } + + // We shouldn't be adding a tear-off if there already is one. If that happens, + // something is wrong. + if (mTable->Get(aSimple, nullptr)) { + MOZ_ASSERT(false, "There is already a tear-off for this object."); + return; + } + + mTable->InsertOrUpdate(aSimple, aTearoff); +} + +template <class SimpleType, class TearoffType> +void SVGAttrTearoffTable<SimpleType, TearoffType>::RemoveTearoff( + SimpleType* aSimple) { + if (!mTable) { + // Perhaps something happened in between creating the SimpleType object and + // registering it + return; + } + + mTable->Remove(aSimple); + if (mTable->Count() == 0) { + delete mTable; + mTable = nullptr; + } +} + +} // namespace mozilla + +#endif // DOM_SVG_SVGATTRTEAROFFTABLE_H_ diff --git a/dom/svg/SVGAttrValueWrapper.cpp b/dom/svg/SVGAttrValueWrapper.cpp new file mode 100644 index 0000000000..4bd097931f --- /dev/null +++ b/dom/svg/SVGAttrValueWrapper.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAttrValueWrapper.h" + +#include "SVGAnimatedIntegerPair.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedOrient.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedViewBox.h" +#include "SVGLengthList.h" +#include "SVGNumberList.h" +#include "SVGPathData.h" +#include "SVGPointList.h" +#include "SVGStringList.h" +#include "SVGTransformList.h" + +namespace mozilla { + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedOrient* aOrient, + nsAString& aResult) { + aOrient->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedIntegerPair* aIntegerPair, + nsAString& aResult) { + aIntegerPair->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedLength* aLength, + nsAString& aResult) { + aLength->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGLengthList* aLengthList, + nsAString& aResult) { + aLengthList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGNumberList* aNumberList, + nsAString& aResult) { + aNumberList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedNumberPair* aNumberPair, + nsAString& aResult) { + aNumberPair->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGPathData* aPathData, + nsAString& aResult) { + aPathData->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGPointList* aPointList, + nsAString& aResult) { + aPointList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString( + const SVGAnimatedPreserveAspectRatio* aPreserveAspectRatio, + nsAString& aResult) { + aPreserveAspectRatio->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGStringList* aStringList, + nsAString& aResult) { + aStringList->GetValue(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGTransformList* aTransformList, + nsAString& aResult) { + aTransformList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedViewBox* aViewBox, + nsAString& aResult) { + aViewBox->GetBaseValueString(aResult); +} + +} // namespace mozilla diff --git a/dom/svg/SVGAttrValueWrapper.h b/dom/svg/SVGAttrValueWrapper.h new file mode 100644 index 0000000000..c3fb96ba38 --- /dev/null +++ b/dom/svg/SVGAttrValueWrapper.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGATTRVALUEWRAPPER_H_ +#define DOM_SVG_SVGATTRVALUEWRAPPER_H_ + +/** + * Utility wrapper for handling SVG types used inside nsAttrValue so that these + * types don't need to be exported outside the SVG module. + */ + +#include "nsString.h" + +namespace mozilla { +class SVGAnimatedIntegerPair; +class SVGAnimatedLength; +class SVGAnimatedNumberPair; +class SVGAnimatedOrient; +class SVGAnimatedPreserveAspectRatio; +class SVGAnimatedViewBox; +class SVGLengthList; +class SVGNumberList; +class SVGPathData; +class SVGPointList; +class SVGStringList; +class SVGTransformList; + +class SVGAttrValueWrapper { + public: + static void ToString(const SVGAnimatedIntegerPair* aIntegerPair, + nsAString& aResult); + static void ToString(const SVGAnimatedLength* aLength, nsAString& aResult); + static void ToString(const SVGAnimatedNumberPair* aNumberPair, + nsAString& aResult); + static void ToString(const SVGAnimatedOrient* aOrient, nsAString& aResult); + static void ToString( + const SVGAnimatedPreserveAspectRatio* aPreserveAspectRatio, + nsAString& aResult); + static void ToString(const SVGAnimatedViewBox* aViewBox, nsAString& aResult); + static void ToString(const SVGLengthList* aLengthList, nsAString& aResult); + static void ToString(const SVGNumberList* aNumberList, nsAString& aResult); + static void ToString(const SVGPathData* aPathData, nsAString& aResult); + static void ToString(const SVGPointList* aPointList, nsAString& aResult); + static void ToString(const SVGStringList* aStringList, nsAString& aResult); + static void ToString(const SVGTransformList* aTransformList, + nsAString& aResult); +}; + +} /* namespace mozilla */ + +#endif // DOM_SVG_SVGATTRVALUEWRAPPER_H_ diff --git a/dom/svg/SVGCircleElement.cpp b/dom/svg/SVGCircleElement.cpp new file mode 100644 index 0000000000..c3f4beb40d --- /dev/null +++ b/dom/svg/SVGCircleElement.cpp @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ComputedStyle.h" +#include "mozilla/dom/SVGCircleElement.h" +#include "mozilla/gfx/2D.h" +#include "nsGkAtoms.h" +#include "mozilla/dom/SVGCircleElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Circle) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGCircleElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGCircleElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGCircleElement::sLengthInfo[3] = { + {nsGkAtoms::cx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::cy, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::r, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::XY}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGCircleElement::SVGCircleElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGCircleElementBase(std::move(aNodeInfo)) {} + +bool SVGCircleElement::IsAttributeMapped(const nsAtom* aAttribute) const { + return IsInLengthInfo(aAttribute, sLengthInfo) || + SVGCircleElementBase::IsAttributeMapped(aAttribute); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGCircleElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGCircleElement::Cx() { + return mLengthAttributes[ATTR_CX].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGCircleElement::Cy() { + return mLengthAttributes[ATTR_CY].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGCircleElement::R() { + return mLengthAttributes[ATTR_R].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGCircleElement::HasValidDimensions() const { + float r; + + if (SVGGeometryProperty::ResolveAll<SVGT::R>(this, &r)) { + return r > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + return mLengthAttributes[ATTR_R].IsExplicitlySet() && + mLengthAttributes[ATTR_R].GetAnimValInSpecifiedUnits() > 0; +} + +SVGElement::LengthAttributesInfo SVGCircleElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGCircleElement::GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) { + float x, y, r; + + DebugOnly<bool> ok = + SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::R>(this, &x, &y, + &r); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (r <= 0.f) { + // Rendering of the element is disabled + *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x, y)), Size()); + return true; + } + + if (aToBoundsSpace.IsRectilinear()) { + // Optimize the case where we can treat the circle as a rectangle and + // still get tight bounds. + if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + Rect userBounds(x - r, y - r, 2 * r, 2 * r); + SVGContentUtils::RectilinearGetStrokeBounds( + userBounds, aToBoundsSpace, *aToNonScalingStrokeSpace, + aStrokeOptions.mLineWidth, aBounds); + return true; + } + return false; + } + r += aStrokeOptions.mLineWidth / 2.f; + } + Rect rect(x - r, y - r, 2 * r, 2 * r); + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; + } + + return false; +} + +already_AddRefed<Path> SVGCircleElement::BuildPath(PathBuilder* aBuilder) { + float x, y, r; + if (!SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::R>(this, &x, + &y, &r)) { + // This function might be called for element in display:none subtree + // (e.g. getTotalLength), we fall back to use SVG attributes. + GetAnimatedLengthValues(&x, &y, &r, nullptr); + } + + if (r <= 0.0f) { + return nullptr; + } + + aBuilder->Arc(Point(x, y), r, 0, Float(2 * M_PI)); + + return aBuilder->Finish(); +} + +bool SVGCircleElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + const auto& newSVGReset = *aNewStyle.StyleSVGReset(); + const auto& oldSVGReset = *aOldStyle.StyleSVGReset(); + + return newSVGReset.mCx != oldSVGReset.mCx || + newSVGReset.mCy != oldSVGReset.mCy || newSVGReset.mR != oldSVGReset.mR; +} + +nsCSSPropertyID SVGCircleElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_CX: + return eCSSProperty_cx; + case ATTR_CY: + return eCSSProperty_cy; + case ATTR_R: + return eCSSProperty_r; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGCircleElement.h b/dom/svg/SVGCircleElement.h new file mode 100644 index 0000000000..85c5d0bd9d --- /dev/null +++ b/dom/svg/SVGCircleElement.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCIRCLEELEMENT_H_ +#define DOM_SVG_SVGCIRCLEELEMENT_H_ + +#include "nsCSSPropertyID.h" +#include "SVGGeometryElement.h" +#include "SVGAnimatedLength.h" + +nsresult NS_NewSVGCircleElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class ComputedStyle; + +namespace dom { + +using SVGCircleElementBase = SVGGeometryElement; + +class SVGCircleElement final : public SVGCircleElementBase { + protected: + explicit SVGCircleElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGCircleElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + public: + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> Cx(); + already_AddRefed<DOMSVGAnimatedLength> Cy(); + already_AddRefed<DOMSVGAnimatedLength> R(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_CX, ATTR_CY, ATTR_R }; + SVGAnimatedLength mLengthAttributes[3]; + static LengthInfo sLengthInfo[3]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGCIRCLEELEMENT_H_ diff --git a/dom/svg/SVGClipPathElement.cpp b/dom/svg/SVGClipPathElement.cpp new file mode 100644 index 0000000000..a10624079d --- /dev/null +++ b/dom/svg/SVGClipPathElement.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGClipPathElement.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGClipPathElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "nsGkAtoms.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(ClipPath) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGClipPathElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGClipPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::EnumInfo SVGClipPathElement::sEnumInfo[1] = { + {nsGkAtoms::clipPathUnits, sSVGUnitTypesMap, SVG_UNIT_TYPE_USERSPACEONUSE}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGClipPathElement::SVGClipPathElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGClipPathElementBase(std::move(aNodeInfo)) {} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGClipPathElement::ClipPathUnits() { + return mEnumAttributes[CLIPPATHUNITS].ToDOMAnimatedEnum(this); +} + +SVGElement::EnumAttributesInfo SVGClipPathElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +bool SVGClipPathElement::IsUnitsObjectBoundingBox() const { + return mEnumAttributes[CLIPPATHUNITS].GetAnimValue() == + SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGClipPathElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGClipPathElement.h b/dom/svg/SVGClipPathElement.h new file mode 100644 index 0000000000..7f00753a46 --- /dev/null +++ b/dom/svg/SVGClipPathElement.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCLIPPATHELEMENT_H_ +#define DOM_SVG_SVGCLIPPATHELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGTransformableElement.h" + +nsresult NS_NewSVGClipPathElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGClipPathFrame; + +namespace dom { + +using SVGClipPathElementBase = SVGTransformableElement; + +class SVGClipPathElement final : public SVGClipPathElementBase { + friend class mozilla::SVGClipPathFrame; + + protected: + friend nsresult(::NS_NewSVGClipPathElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGClipPathElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedEnumeration> ClipPathUnits(); + + // This is an internal method that does not flush style, and thus + // the answer may be out of date if there's a pending style flush. + bool IsUnitsObjectBoundingBox() const; + + protected: + enum { CLIPPATHUNITS }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static EnumInfo sEnumInfo[1]; + + EnumAttributesInfo GetEnumInfo() override; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGCLIPPATHELEMENT_H_ diff --git a/dom/svg/SVGComponentTransferFunctionElement.h b/dom/svg/SVGComponentTransferFunctionElement.h new file mode 100644 index 0000000000..01e838d392 --- /dev/null +++ b/dom/svg/SVGComponentTransferFunctionElement.h @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCOMPONENTTRANSFERFUNCTIONELEMENT_H_ +#define DOM_SVG_SVGCOMPONENTTRANSFERFUNCTIONELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberList.h" +#include "mozilla/dom/SVGFilters.h" + +#define NS_SVG_FE_COMPONENT_TRANSFER_FUNCTION_ELEMENT_CID \ + { \ + 0xafab106d, 0xbc18, 0x4f7f, { \ + 0x9e, 0x29, 0xfe, 0xb4, 0xb0, 0x16, 0x5f, 0xf4 \ + } \ + } + +namespace mozilla::dom { + +class DOMSVGAnimatedNumberList; + +using SVGComponentTransferFunctionElementBase = SVGFEUnstyledElement; + +class SVGComponentTransferFunctionElement + : public SVGComponentTransferFunctionElementBase { + protected: + explicit SVGComponentTransferFunctionElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGComponentTransferFunctionElementBase(std::move(aNodeInfo)) {} + + virtual ~SVGComponentTransferFunctionElement() = default; + + public: + using ComponentTransferAttributes = gfx::ComponentTransferAttributes; + + // interfaces: + NS_DECLARE_STATIC_IID_ACCESSOR( + NS_SVG_FE_COMPONENT_TRANSFER_FUNCTION_ELEMENT_CID) + + NS_DECL_ISUPPORTS_INHERITED + + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + virtual int32_t GetChannel() = 0; + + void ComputeAttributes(int32_t aChannel, + ComponentTransferAttributes& aAttributes); + + // WebIDL + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override = 0; + already_AddRefed<DOMSVGAnimatedEnumeration> Type(); + already_AddRefed<DOMSVGAnimatedNumberList> TableValues(); + already_AddRefed<DOMSVGAnimatedNumber> Slope(); + already_AddRefed<DOMSVGAnimatedNumber> Intercept(); + already_AddRefed<DOMSVGAnimatedNumber> Amplitude(); + already_AddRefed<DOMSVGAnimatedNumber> Exponent(); + already_AddRefed<DOMSVGAnimatedNumber> Offset(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + EnumAttributesInfo GetEnumInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { TABLEVALUES }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; + + enum { SLOPE, INTERCEPT, AMPLITUDE, EXPONENT, OFFSET }; + SVGAnimatedNumber mNumberAttributes[5]; + static NumberInfo sNumberInfo[5]; + + enum { TYPE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sTypeMap[]; + static EnumInfo sEnumInfo[1]; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGComponentTransferFunctionElement, + NS_SVG_FE_COMPONENT_TRANSFER_FUNCTION_ELEMENT_CID) + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncRElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncRElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncRElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEFuncRElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 0; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; +}; + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncGElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncGElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncGElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEFuncGElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 1; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; +}; + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncBElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncBElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncBElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEFuncBElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 2; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; +}; + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncAElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncAElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncAElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEFuncAElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 3; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGCOMPONENTTRANSFERFUNCTIONELEMENT_H_ diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp new file mode 100644 index 0000000000..2b9d0120c9 --- /dev/null +++ b/dom/svg/SVGContentUtils.cpp @@ -0,0 +1,865 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Main header first: +// This is also necessary to ensure our definition of M_SQRT1_2 is picked up +#include "SVGContentUtils.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxMatrix.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/PresShell.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/TextUtils.h" +#include "nsComputedDOMStyle.h" +#include "nsContainerFrame.h" +#include "nsFontMetrics.h" +#include "nsIFrame.h" +#include "nsIScriptError.h" +#include "nsLayoutUtils.h" +#include "nsMathUtils.h" +#include "nsWhitespaceTokenizer.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGGeometryProperty.h" +#include "nsContentUtils.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/ComputedStyle.h" +#include "SVGPathDataParser.h" +#include "SVGPathData.h" +#include "SVGPathElement.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; +using namespace mozilla::gfx; + +static bool ParseNumber(RangedPtr<const char16_t>& aIter, + const RangedPtr<const char16_t>& aEnd, double& aValue) { + int32_t sign; + if (!SVGContentUtils::ParseOptionalSign(aIter, aEnd, sign)) { + return false; + } + + // Absolute value of the integer part of the mantissa. + double intPart = 0.0; + + bool gotDot = *aIter == '.'; + + if (!gotDot) { + if (!mozilla::IsAsciiDigit(*aIter)) { + return false; + } + do { + intPart = 10.0 * intPart + mozilla::AsciiAlphanumericToNumber(*aIter); + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + + if (aIter != aEnd) { + gotDot = *aIter == '.'; + } + } + + // Fractional part of the mantissa. + double fracPart = 0.0; + + if (gotDot) { + ++aIter; + if (aIter == aEnd || !mozilla::IsAsciiDigit(*aIter)) { + return false; + } + + // Power of ten by which we need to divide the fraction + double divisor = 1.0; + + do { + fracPart = 10.0 * fracPart + mozilla::AsciiAlphanumericToNumber(*aIter); + divisor *= 10.0; + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + + fracPart /= divisor; + } + + bool gotE = false; + int32_t exponent = 0; + int32_t expSign; + + if (aIter != aEnd && (*aIter == 'e' || *aIter == 'E')) { + RangedPtr<const char16_t> expIter(aIter); + + ++expIter; + if (expIter != aEnd) { + expSign = *expIter == '-' ? -1 : 1; + if (*expIter == '-' || *expIter == '+') { + ++expIter; + } + if (expIter != aEnd && mozilla::IsAsciiDigit(*expIter)) { + // At this point we're sure this is an exponent + // and not the start of a unit such as em or ex. + gotE = true; + } + } + + if (gotE) { + aIter = expIter; + do { + exponent = 10.0 * exponent + mozilla::AsciiAlphanumericToNumber(*aIter); + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + } + } + + // Assemble the number + aValue = sign * (intPart + fracPart); + if (gotE) { + aValue *= pow(10.0, expSign * exponent); + } + return true; +} + +namespace mozilla { + +SVGSVGElement* SVGContentUtils::GetOuterSVGElement(SVGElement* aSVGElement) { + Element* element = nullptr; + Element* ancestor = aSVGElement->GetParentElementCrossingShadowRoot(); + + while (ancestor && ancestor->IsSVGElement() && + !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + element = ancestor; + ancestor = element->GetParentElementCrossingShadowRoot(); + } + + if (element && element->IsSVGElement(nsGkAtoms::svg)) { + return static_cast<SVGSVGElement*>(element); + } + return nullptr; +} + +enum DashState { + eDashedStroke, + eContinuousStroke, //< all dashes, no gaps + eNoStroke //< all gaps, no dashes +}; + +static DashState GetStrokeDashData( + SVGContentUtils::AutoStrokeOptions* aStrokeOptions, SVGElement* aElement, + const nsStyleSVG* aStyleSVG, SVGContextPaint* aContextPaint) { + size_t dashArrayLength; + Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0; + Float pathScale = 1.0; + + if (aStyleSVG->mStrokeDasharray.IsContextValue()) { + if (!aContextPaint) { + return eContinuousStroke; + } + const FallibleTArray<Float>& dashSrc = aContextPaint->GetStrokeDashArray(); + dashArrayLength = dashSrc.Length(); + if (dashArrayLength <= 0) { + return eContinuousStroke; + } + Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); + if (!dashPattern) { + return eContinuousStroke; + } + for (size_t i = 0; i < dashArrayLength; i++) { + if (dashSrc[i] < 0.0) { + return eContinuousStroke; // invalid + } + dashPattern[i] = Float(dashSrc[i]); + (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i]; + } + } else { + const auto dasharray = aStyleSVG->mStrokeDasharray.AsValues().AsSpan(); + dashArrayLength = dasharray.Length(); + if (dashArrayLength <= 0) { + return eContinuousStroke; + } + if (aElement->IsNodeOfType(nsINode::eSHAPE)) { + pathScale = + static_cast<SVGGeometryElement*>(aElement)->GetPathLengthScale( + SVGGeometryElement::eForStroking); + if (pathScale <= 0) { + return eContinuousStroke; + } + } + Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); + if (!dashPattern) { + return eContinuousStroke; + } + for (uint32_t i = 0; i < dashArrayLength; i++) { + Float dashLength = + SVGContentUtils::CoordToFloat(aElement, dasharray[i]) * pathScale; + if (dashLength < 0.0) { + return eContinuousStroke; // invalid + } + dashPattern[i] = dashLength; + (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashLength; + } + } + + // Now that aStrokeOptions.mDashPattern is fully initialized (we didn't + // return early above) we can safely set mDashLength: + aStrokeOptions->mDashLength = dashArrayLength; + + if ((dashArrayLength % 2) == 1) { + // If we have a dash pattern with an odd number of lengths the pattern + // repeats a second time, per the SVG spec., and as implemented by Moz2D. + // When deciding whether to return eNoStroke or eContinuousStroke below we + // need to take into account that in the repeat pattern the dashes become + // gaps, and the gaps become dashes. + Float origTotalLengthOfDashes = totalLengthOfDashes; + totalLengthOfDashes += totalLengthOfGaps; + totalLengthOfGaps += origTotalLengthOfDashes; + } + + // Stroking using dashes is much slower than stroking a continuous line + // (see bug 609361 comment 40), and much, much slower than not stroking the + // line at all. Here we check for cases when the dash pattern causes the + // stroke to essentially be continuous or to be nonexistent in which case + // we can avoid expensive stroking operations (the underlying platform + // graphics libraries don't seem to optimize for this). + if (totalLengthOfGaps <= 0) { + return eContinuousStroke; + } + // We can only return eNoStroke if the value of stroke-linecap isn't + // adding caps to zero length dashes. + if (totalLengthOfDashes <= 0 && + aStyleSVG->mStrokeLinecap == StyleStrokeLinecap::Butt) { + return eNoStroke; + } + + if (aStyleSVG->mStrokeDashoffset.IsContextValue()) { + aStrokeOptions->mDashOffset = + Float(aContextPaint ? aContextPaint->GetStrokeDashOffset() : 0); + } else { + aStrokeOptions->mDashOffset = + SVGContentUtils::CoordToFloat( + aElement, aStyleSVG->mStrokeDashoffset.AsLengthPercentage()) * + pathScale; + } + + return eDashedStroke; +} + +void SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions, + SVGElement* aElement, + const ComputedStyle* aComputedStyle, + SVGContextPaint* aContextPaint, + StrokeOptionFlags aFlags) { + auto doCompute = [&](const ComputedStyle* computedStyle) { + const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); + + bool checkedDashAndStrokeIsDashed = false; + if (aFlags != eIgnoreStrokeDashing) { + DashState dashState = + GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint); + + if (dashState == eNoStroke) { + // Hopefully this will shortcircuit any stroke operations: + aStrokeOptions->mLineWidth = 0; + return; + } + if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) { + // Prevent our caller from wasting time looking at a pattern without + // gaps: + aStrokeOptions->DiscardDashPattern(); + } + checkedDashAndStrokeIsDashed = (dashState == eDashedStroke); + } + + aStrokeOptions->mLineWidth = + GetStrokeWidth(aElement, computedStyle, aContextPaint); + + aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit); + + switch (styleSVG->mStrokeLinejoin) { + case StyleStrokeLinejoin::Miter: + aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL; + break; + case StyleStrokeLinejoin::Round: + aStrokeOptions->mLineJoin = JoinStyle::ROUND; + break; + case StyleStrokeLinejoin::Bevel: + aStrokeOptions->mLineJoin = JoinStyle::BEVEL; + break; + } + + if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) { + // Note: if aFlags == eIgnoreStrokeDashing then we may be returning the + // wrong linecap value here, since the actual linecap used on render in + // this case depends on whether the stroke is dashed or not. + aStrokeOptions->mLineCap = CapStyle::BUTT; + } else { + switch (styleSVG->mStrokeLinecap) { + case StyleStrokeLinecap::Butt: + aStrokeOptions->mLineCap = CapStyle::BUTT; + break; + case StyleStrokeLinecap::Round: + aStrokeOptions->mLineCap = CapStyle::ROUND; + break; + case StyleStrokeLinecap::Square: + aStrokeOptions->mLineCap = CapStyle::SQUARE; + break; + } + } + }; + + if (aComputedStyle) { + doCompute(aComputedStyle); + } else { + SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); + } +} + +Float SVGContentUtils::GetStrokeWidth(SVGElement* aElement, + const ComputedStyle* aComputedStyle, + SVGContextPaint* aContextPaint) { + Float res = 0.0; + + auto doCompute = [&](const ComputedStyle* computedStyle) { + const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); + + if (styleSVG->mStrokeWidth.IsContextValue()) { + res = aContextPaint ? aContextPaint->GetStrokeWidth() : 1.0; + } else { + auto& lp = styleSVG->mStrokeWidth.AsLengthPercentage(); + if (lp.HasPercent() && aElement) { + auto counter = + aElement->IsSVGElement(nsGkAtoms::text) + ? UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVGText + : UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVG; + aElement->OwnerDoc()->SetUseCounter(counter); + } + res = SVGContentUtils::CoordToFloat(aElement, lp); + } + }; + + if (aComputedStyle) { + doCompute(aComputedStyle); + } else { + SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); + } + + return res; +} + +float SVGContentUtils::GetFontSize(Element* aElement) { + if (!aElement) { + return 1.0f; + } + + nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); + if (!pc) { + return 1.0f; + } + + if (auto* f = aElement->GetPrimaryFrame()) { + return GetFontSize(f->Style(), pc); + } + + if (RefPtr<const ComputedStyle> style = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + return GetFontSize(style, pc); + } + + // ReportToConsole + NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); + return 1.0f; +} + +float SVGContentUtils::GetFontSize(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "NULL frame in GetFontSize"); + return GetFontSize(aFrame->Style(), aFrame->PresContext()); +} + +float SVGContentUtils::GetFontSize(const ComputedStyle* aComputedStyle, + nsPresContext* aPresContext) { + MOZ_ASSERT(aComputedStyle); + MOZ_ASSERT(aPresContext); + + return aComputedStyle->StyleFont()->mSize.ToCSSPixels() / + aPresContext->EffectiveTextZoom(); +} + +float SVGContentUtils::GetFontXHeight(Element* aElement) { + if (!aElement) { + return 1.0f; + } + + nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); + if (!pc) { + return 1.0f; + } + + if (auto* f = aElement->GetPrimaryFrame()) { + return GetFontXHeight(f->Style(), pc); + } + + if (RefPtr<const ComputedStyle> style = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + return GetFontXHeight(style, pc); + } + + // ReportToConsole + NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); + return 1.0f; +} + +float SVGContentUtils::GetFontXHeight(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "NULL frame in GetFontXHeight"); + return GetFontXHeight(aFrame->Style(), aFrame->PresContext()); +} + +float SVGContentUtils::GetFontXHeight(const ComputedStyle* aComputedStyle, + nsPresContext* aPresContext) { + MOZ_ASSERT(aComputedStyle && aPresContext); + + RefPtr<nsFontMetrics> fontMetrics = + nsLayoutUtils::GetFontMetricsForComputedStyle(aComputedStyle, + aPresContext); + + if (!fontMetrics) { + // ReportToConsole + NS_WARNING("no FontMetrics in GetFontXHeight()"); + return 1.0f; + } + + nscoord xHeight = fontMetrics->XHeight(); + return nsPresContext::AppUnitsToFloatCSSPixels(xHeight) / + aPresContext->EffectiveTextZoom(); +} +nsresult SVGContentUtils::ReportToConsole(Document* doc, const char* aWarning, + const nsTArray<nsString>& aParams) { + return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "SVG"_ns, + doc, nsContentUtils::eSVG_PROPERTIES, + aWarning, aParams); +} + +bool SVGContentUtils::EstablishesViewport(nsIContent* aContent) { + // Although SVG 1.1 states that <image> is an element that establishes a + // viewport, this is really only for the document it references, not + // for any child content, which is what this function is used for. + return aContent && + aContent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::foreignObject, + nsGkAtoms::symbol); +} + +SVGViewportElement* SVGContentUtils::GetNearestViewportElement( + const nsIContent* aContent) { + nsIContent* element = aContent->GetFlattenedTreeParent(); + + while (element && element->IsSVGElement()) { + if (EstablishesViewport(element)) { + if (element->IsSVGElement(nsGkAtoms::foreignObject)) { + return nullptr; + } + MOZ_ASSERT(element->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol), + "upcoming static_cast is only valid for " + "SVGViewportElement subclasses"); + return static_cast<SVGViewportElement*>(element); + } + element = element->GetFlattenedTreeParent(); + } + return nullptr; +} + +static gfx::Matrix GetCTMInternal(SVGElement* aElement, bool aScreenCTM, + bool aHaveRecursed) { + auto getLocalTransformHelper = + [](SVGElement const* e, bool shouldIncludeChildToUserSpace) -> gfxMatrix { + gfxMatrix ret; + + if (auto* f = e->GetPrimaryFrame()) { + ret = SVGUtils::GetTransformMatrixInUserSpace(f); + } else { + // FIXME: Ideally we should also return the correct matrix + // for display:none, but currently transform related code relies + // heavily on the present of a frame. + // For now we just fall back to |PrependLocalTransformsTo| which + // doesn't account for CSS transform. + ret = e->PrependLocalTransformsTo({}, eUserSpaceToParent); + } + + if (shouldIncludeChildToUserSpace) { + ret = e->PrependLocalTransformsTo({}, eChildToUserSpace) * ret; + } + + return ret; + }; + + gfxMatrix matrix = getLocalTransformHelper(aElement, aHaveRecursed); + + SVGElement* element = aElement; + nsIContent* ancestor = aElement->GetFlattenedTreeParent(); + + while (ancestor && ancestor->IsSVGElement() && + !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + element = static_cast<SVGElement*>(ancestor); + matrix *= getLocalTransformHelper(element, true); + if (!aScreenCTM && SVGContentUtils::EstablishesViewport(element)) { + if (!element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG) && + !element->NodeInfo()->Equals(nsGkAtoms::symbol, kNameSpaceID_SVG)) { + NS_ERROR("New (SVG > 1.1) SVG viewport establishing element?"); + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + // XXX spec seems to say x,y translation should be undone for IsInnerSVG + return gfx::ToMatrix(matrix); + } + ancestor = ancestor->GetFlattenedTreeParent(); + } + if (!aScreenCTM) { + // didn't find a nearestViewportElement + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + if (!element->IsSVGElement(nsGkAtoms::svg)) { + // Not a valid SVG fragment + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + if (element == aElement && !aHaveRecursed) { + // We get here when getScreenCTM() is called on an outer-<svg>. + // Consistency with other elements would have us include only the + // eFromUserSpace transforms, but we include the eAllTransforms + // transforms in this case since that's what we've been doing for + // a while, and it keeps us consistent with WebKit and Opera (if not + // really with the ambiguous spec). + matrix = getLocalTransformHelper(aElement, true); + } + + if (auto* f = element->GetPrimaryFrame()) { + if (f->IsSVGOuterSVGFrame()) { + nsMargin bp = f->GetUsedBorderAndPadding(); + matrix.PostTranslate( + NSAppUnitsToFloatPixels(bp.left, AppUnitsPerCSSPixel()), + NSAppUnitsToFloatPixels(bp.top, AppUnitsPerCSSPixel())); + } + } + + if (!ancestor || !ancestor->IsElement()) { + return gfx::ToMatrix(matrix); + } + if (ancestor->IsSVGElement()) { + return gfx::ToMatrix(matrix) * + GetCTMInternal(static_cast<SVGElement*>(ancestor), true, true); + } + + // XXX this does not take into account CSS transform, or that the non-SVG + // content that we've hit may itself be inside an SVG foreignObject higher up + Document* currentDoc = aElement->GetComposedDoc(); + float x = 0.0f, y = 0.0f; + if (currentDoc && + element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG)) { + PresShell* presShell = currentDoc->GetPresShell(); + if (presShell) { + nsIFrame* frame = element->GetPrimaryFrame(); + nsIFrame* ancestorFrame = presShell->GetRootFrame(); + if (frame && ancestorFrame) { + nsPoint point = frame->GetOffsetTo(ancestorFrame); + x = nsPresContext::AppUnitsToFloatCSSPixels(point.x); + y = nsPresContext::AppUnitsToFloatCSSPixels(point.y); + } + } + } + return ToMatrix(matrix).PostTranslate(x, y); +} + +gfx::Matrix SVGContentUtils::GetCTM(SVGElement* aElement, bool aScreenCTM) { + return GetCTMInternal(aElement, aScreenCTM, false); +} + +void SVGContentUtils::RectilinearGetStrokeBounds( + const Rect& aRect, const Matrix& aToBoundsSpace, + const Matrix& aToNonScalingStrokeSpace, float aStrokeWidth, Rect* aBounds) { + MOZ_ASSERT(aToBoundsSpace.IsRectilinear(), + "aToBoundsSpace must be rectilinear"); + MOZ_ASSERT(aToNonScalingStrokeSpace.IsRectilinear(), + "aToNonScalingStrokeSpace must be rectilinear"); + + Matrix nonScalingToSource = aToNonScalingStrokeSpace.Inverse(); + Matrix nonScalingToBounds = nonScalingToSource * aToBoundsSpace; + + *aBounds = aToBoundsSpace.TransformBounds(aRect); + + // Compute the amounts dx and dy that nonScalingToBounds scales a half-width + // stroke in the x and y directions, and then inflate aBounds by those amounts + // so that when aBounds is transformed back to non-scaling-stroke space + // it will map onto the correct stroked bounds. + + Float dx = 0.0f; + Float dy = 0.0f; + // nonScalingToBounds is rectilinear, so either _12 and _21 are zero or _11 + // and _22 are zero, and in each case the non-zero entries (from among _11, + // _12, _21, _22) simply scale the stroke width in the x and y directions. + if (FuzzyEqual(nonScalingToBounds._12, 0) && + FuzzyEqual(nonScalingToBounds._21, 0)) { + dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._11); + dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._22); + } else { + dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._21); + dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._12); + } + + aBounds->Inflate(dx, dy); +} + +double SVGContentUtils::ComputeNormalizedHypotenuse(double aWidth, + double aHeight) { + return NS_hypot(aWidth, aHeight) / M_SQRT2; +} + +float SVGContentUtils::AngleBisect(float a1, float a2) { + float delta = std::fmod(a2 - a1, static_cast<float>(2 * M_PI)); + if (delta < 0) { + delta += static_cast<float>(2 * M_PI); + } + /* delta is now the angle from a1 around to a2, in the range [0, 2*M_PI) */ + float r = a1 + delta / 2; + if (delta >= M_PI) { + /* the arc from a2 to a1 is smaller, so use the ray on that side */ + r += static_cast<float>(M_PI); + } + return r; +} + +gfx::Matrix SVGContentUtils::GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGAnimatedPreserveAspectRatio& aPreserveAspectRatio) { + return GetViewBoxTransform(aViewportWidth, aViewportHeight, aViewboxX, + aViewboxY, aViewboxWidth, aViewboxHeight, + aPreserveAspectRatio.GetAnimValue()); +} + +gfx::Matrix SVGContentUtils::GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGPreserveAspectRatio& aPreserveAspectRatio) { + NS_ASSERTION(aViewportWidth >= 0, "viewport width must be nonnegative!"); + NS_ASSERTION(aViewportHeight >= 0, "viewport height must be nonnegative!"); + NS_ASSERTION(aViewboxWidth > 0, "viewBox width must be greater than zero!"); + NS_ASSERTION(aViewboxHeight > 0, "viewBox height must be greater than zero!"); + + uint16_t align = aPreserveAspectRatio.GetAlign(); + uint16_t meetOrSlice = aPreserveAspectRatio.GetMeetOrSlice(); + + // default to the defaults + if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) + align = SVG_PRESERVEASPECTRATIO_XMIDYMID; + if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) + meetOrSlice = SVG_MEETORSLICE_MEET; + + float a, d, e, f; + a = aViewportWidth / aViewboxWidth; + d = aViewportHeight / aViewboxHeight; + e = 0.0f; + f = 0.0f; + + if (align != SVG_PRESERVEASPECTRATIO_NONE && a != d) { + if ((meetOrSlice == SVG_MEETORSLICE_MEET && a < d) || + (meetOrSlice == SVG_MEETORSLICE_SLICE && d < a)) { + d = a; + switch (align) { + case SVG_PRESERVEASPECTRATIO_XMINYMIN: + case SVG_PRESERVEASPECTRATIO_XMIDYMIN: + case SVG_PRESERVEASPECTRATIO_XMAXYMIN: + break; + case SVG_PRESERVEASPECTRATIO_XMINYMID: + case SVG_PRESERVEASPECTRATIO_XMIDYMID: + case SVG_PRESERVEASPECTRATIO_XMAXYMID: + f = (aViewportHeight - a * aViewboxHeight) / 2.0f; + break; + case SVG_PRESERVEASPECTRATIO_XMINYMAX: + case SVG_PRESERVEASPECTRATIO_XMIDYMAX: + case SVG_PRESERVEASPECTRATIO_XMAXYMAX: + f = aViewportHeight - a * aViewboxHeight; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown value for align"); + } + } else if ((meetOrSlice == SVG_MEETORSLICE_MEET && d < a) || + (meetOrSlice == SVG_MEETORSLICE_SLICE && a < d)) { + a = d; + switch (align) { + case SVG_PRESERVEASPECTRATIO_XMINYMIN: + case SVG_PRESERVEASPECTRATIO_XMINYMID: + case SVG_PRESERVEASPECTRATIO_XMINYMAX: + break; + case SVG_PRESERVEASPECTRATIO_XMIDYMIN: + case SVG_PRESERVEASPECTRATIO_XMIDYMID: + case SVG_PRESERVEASPECTRATIO_XMIDYMAX: + e = (aViewportWidth - a * aViewboxWidth) / 2.0f; + break; + case SVG_PRESERVEASPECTRATIO_XMAXYMIN: + case SVG_PRESERVEASPECTRATIO_XMAXYMID: + case SVG_PRESERVEASPECTRATIO_XMAXYMAX: + e = aViewportWidth - a * aViewboxWidth; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown value for align"); + } + } else + MOZ_ASSERT_UNREACHABLE("Unknown value for meetOrSlice"); + } + + if (aViewboxX) e += -a * aViewboxX; + if (aViewboxY) f += -d * aViewboxY; + + return gfx::Matrix(a, 0.0f, 0.0f, d, e, f); +} + +template <class floatType> +bool SVGContentUtils::ParseNumber(RangedPtr<const char16_t>& aIter, + const RangedPtr<const char16_t>& aEnd, + floatType& aValue) { + RangedPtr<const char16_t> iter(aIter); + + double value; + if (!::ParseNumber(iter, aEnd, value)) { + return false; + } + floatType floatValue = floatType(value); + if (!IsFinite(floatValue)) { + return false; + } + aValue = floatValue; + aIter = iter; + return true; +} + +template bool SVGContentUtils::ParseNumber<float>( + RangedPtr<const char16_t>& aIter, const RangedPtr<const char16_t>& aEnd, + float& aValue); + +template bool SVGContentUtils::ParseNumber<double>( + RangedPtr<const char16_t>& aIter, const RangedPtr<const char16_t>& aEnd, + double& aValue); + +RangedPtr<const char16_t> SVGContentUtils::GetStartRangedPtr( + const nsAString& aString) { + return RangedPtr<const char16_t>(aString.Data(), aString.Length()); +} + +RangedPtr<const char16_t> SVGContentUtils::GetEndRangedPtr( + const nsAString& aString) { + return RangedPtr<const char16_t>(aString.Data() + aString.Length(), + aString.Data(), aString.Length()); +} + +template <class floatType> +bool SVGContentUtils::ParseNumber(const nsAString& aString, floatType& aValue) { + RangedPtr<const char16_t> iter = GetStartRangedPtr(aString); + const RangedPtr<const char16_t> end = GetEndRangedPtr(aString); + + return ParseNumber(iter, end, aValue) && iter == end; +} + +template bool SVGContentUtils::ParseNumber<float>(const nsAString& aString, + float& aValue); +template bool SVGContentUtils::ParseNumber<double>(const nsAString& aString, + double& aValue); + +/* static */ +bool SVGContentUtils::ParseInteger(RangedPtr<const char16_t>& aIter, + const RangedPtr<const char16_t>& aEnd, + int32_t& aValue) { + RangedPtr<const char16_t> iter(aIter); + + int32_t sign; + if (!ParseOptionalSign(iter, aEnd, sign)) { + return false; + } + + if (!mozilla::IsAsciiDigit(*iter)) { + return false; + } + + int64_t value = 0; + + do { + if (value <= std::numeric_limits<int32_t>::max()) { + value = 10 * value + mozilla::AsciiAlphanumericToNumber(*iter); + } + ++iter; + } while (iter != aEnd && mozilla::IsAsciiDigit(*iter)); + + aIter = iter; + aValue = int32_t(clamped(sign * value, + int64_t(std::numeric_limits<int32_t>::min()), + int64_t(std::numeric_limits<int32_t>::max()))); + return true; +} + +/* static */ +bool SVGContentUtils::ParseInteger(const nsAString& aString, int32_t& aValue) { + RangedPtr<const char16_t> iter = GetStartRangedPtr(aString); + const RangedPtr<const char16_t> end = GetEndRangedPtr(aString); + + return ParseInteger(iter, end, aValue) && iter == end; +} + +float SVGContentUtils::CoordToFloat(SVGElement* aContent, + const LengthPercentage& aLength, + uint8_t aCtxType) { + float result = aLength.ResolveToCSSPixelsWith([&] { + SVGViewportElement* ctx = aContent->GetCtx(); + return CSSCoord(ctx ? ctx->GetLength(aCtxType) : 0.0f); + }); + if (aLength.IsCalc()) { + const auto& calc = aLength.AsCalc(); + if (calc.clamping_mode == StyleAllowedNumericType::NonNegative) { + result = std::max(result, 0.0f); + } else { + MOZ_ASSERT(calc.clamping_mode == StyleAllowedNumericType::All); + } + } + return result; +} + +already_AddRefed<gfx::Path> SVGContentUtils::GetPath( + const nsAString& aPathString) { + SVGPathData pathData; + SVGPathDataParser parser(aPathString, &pathData); + if (!parser.Parse()) { + return nullptr; + } + + RefPtr<DrawTarget> drawTarget = + gfxPlatform::ThreadLocalScreenReferenceDrawTarget(); + RefPtr<PathBuilder> builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + + return pathData.BuildPath(builder, StyleStrokeLinecap::Butt, 1); +} + +bool SVGContentUtils::ShapeTypeHasNoCorners(const nsIContent* aContent) { + return aContent && + aContent->IsAnyOfSVGElements(nsGkAtoms::circle, nsGkAtoms::ellipse); +} + +nsDependentSubstring SVGContentUtils::GetAndEnsureOneToken( + const nsAString& aString, bool& aSuccess) { + nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tokenizer( + aString); + + aSuccess = false; + if (!tokenizer.hasMoreTokens()) { + return {}; + } + auto token = tokenizer.nextToken(); + if (tokenizer.hasMoreTokens()) { + return {}; + } + + aSuccess = true; + return token; +} + +} // namespace mozilla diff --git a/dom/svg/SVGContentUtils.h b/dom/svg/SVGContentUtils.h new file mode 100644 index 0000000000..e67f72554f --- /dev/null +++ b/dom/svg/SVGContentUtils.h @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCONTENTUTILS_H_ +#define DOM_SVG_SVGCONTENTUTILS_H_ + +// include math.h to pick up definition of M_ maths defines e.g. M_PI +#include <math.h> + +#include "mozilla/gfx/2D.h" // for StrokeOptions +#include "mozilla/gfx/Matrix.h" +#include "mozilla/RangedPtr.h" +#include "nsError.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "gfx2DGlue.h" +#include "nsDependentSubstring.h" + +class nsIContent; + +class nsIFrame; +class nsPresContext; + +namespace mozilla { +class ComputedStyle; +class SVGAnimatedTransformList; +class SVGAnimatedPreserveAspectRatio; +class SVGContextPaint; +class SVGPreserveAspectRatio; +union StyleLengthPercentageUnion; +namespace dom { +class Document; +class Element; +class SVGElement; +class SVGSVGElement; +class SVGViewportElement; +} // namespace dom + +#define SVG_ZERO_LENGTH_PATH_FIX_FACTOR 512 + +/** + * SVGTransformTypes controls the transforms that PrependLocalTransformsTo + * applies. + * + * If aWhich is eAllTransforms, then all the transforms from the coordinate + * space established by this element for its children to the coordinate + * space established by this element's parent element for this element, are + * included. + * + * If aWhich is eUserSpaceToParent, then only the transforms from this + * element's userspace to the coordinate space established by its parent is + * included. This includes any transforms introduced by the 'transform' + * attribute, transform animations and animateMotion, but not any offsets + * due to e.g. 'x'/'y' attributes, or any transform due to a 'viewBox' + * attribute. (SVG userspace is defined to be the coordinate space in which + * coordinates on an element apply.) + * + * If aWhich is eChildToUserSpace, then only the transforms from the + * coordinate space established by this element for its childre to this + * elements userspace are included. This includes any offsets due to e.g. + * 'x'/'y' attributes, and any transform due to a 'viewBox' attribute, but + * does not include any transforms due to the 'transform' attribute. + */ +enum SVGTransformTypes { + eAllTransforms, + eUserSpaceToParent, + eChildToUserSpace +}; + +/** + * Functions generally used by SVG Content classes. Functions here + * should not generally depend on layout methods/classes e.g. SVGUtils + */ +class SVGContentUtils { + public: + using Float = gfx::Float; + using Matrix = gfx::Matrix; + using Rect = gfx::Rect; + using StrokeOptions = gfx::StrokeOptions; + + /* + * Get the outer SVG element of an nsIContent + */ + static dom::SVGSVGElement* GetOuterSVGElement(dom::SVGElement* aSVGElement); + + /** + * Moz2D's StrokeOptions requires someone else to own its mDashPattern + * buffer, which is a pain when you want to initialize a StrokeOptions object + * in a helper function and pass it out. This sub-class owns the mDashPattern + * buffer so that consumers of such a helper function don't need to worry + * about creating it, passing it in, or deleting it. (An added benefit is + * that in the typical case when stroke-dasharray is short it will avoid + * allocating.) + */ + struct AutoStrokeOptions : public StrokeOptions { + AutoStrokeOptions() { + MOZ_ASSERT(mDashLength == 0, "InitDashPattern() depends on this"); + } + ~AutoStrokeOptions() { + if (mDashPattern && mDashPattern != mSmallArray) { + delete[] mDashPattern; + } + } + /** + * Creates the buffer to store the stroke-dasharray, assuming out-of-memory + * does not occur. The buffer's address is assigned to mDashPattern and + * returned to the caller as a non-const pointer (so that the caller can + * initialize the values in the buffer, since mDashPattern is const). + */ + Float* InitDashPattern(size_t aDashCount) { + if (aDashCount <= MOZ_ARRAY_LENGTH(mSmallArray)) { + mDashPattern = mSmallArray; + return mSmallArray; + } + Float* nonConstArray = new (mozilla::fallible) Float[aDashCount]; + mDashPattern = nonConstArray; + return nonConstArray; + } + void DiscardDashPattern() { + if (mDashPattern && mDashPattern != mSmallArray) { + delete[] mDashPattern; + } + mDashLength = 0; + mDashPattern = nullptr; + } + + private: + // Most dasharrays will fit in this and save us allocating + Float mSmallArray[16]; + }; + + enum StrokeOptionFlags { eAllStrokeOptions, eIgnoreStrokeDashing }; + /** + * Note: the linecap style returned in aStrokeOptions is not valid when + * ShapeTypeHasNoCorners(aElement) == true && aFlags == eIgnoreStrokeDashing, + * since when aElement has no corners the rendered linecap style depends on + * whether or not the stroke is dashed. + */ + static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions, + dom::SVGElement* aElement, + const ComputedStyle* aComputedStyle, + mozilla::SVGContextPaint* aContextPaint, + StrokeOptionFlags aFlags = eAllStrokeOptions); + + /** + * Returns the current computed value of the CSS property 'stroke-width' for + * the given element. aComputedStyle may be provided as an optimization. + * aContextPaint is also optional. + * + * Note that this function does NOT take account of the value of the 'stroke' + * and 'stroke-opacity' properties to, say, return zero if they are "none" or + * "0", respectively. + */ + static Float GetStrokeWidth(dom::SVGElement* aElement, + const ComputedStyle* aComputedStyle, + mozilla::SVGContextPaint* aContextPaint); + + /* + * Get the number of CSS px (user units) per em (i.e. the em-height in user + * units) for an nsIContent + * + * XXX document the conditions under which these may fail, and what they + * return in those cases. + */ + static float GetFontSize(mozilla::dom::Element* aElement); + static float GetFontSize(nsIFrame* aFrame); + static float GetFontSize(const ComputedStyle*, nsPresContext*); + /* + * Get the number of CSS px (user units) per ex (i.e. the x-height in user + * units) for an nsIContent + * + * XXX document the conditions under which these may fail, and what they + * return in those cases. + */ + static float GetFontXHeight(mozilla::dom::Element* aElement); + static float GetFontXHeight(nsIFrame* aFrame); + static float GetFontXHeight(const ComputedStyle*, nsPresContext*); + + /* + * Report a localized error message to the error console. + */ + static nsresult ReportToConsole(dom::Document* doc, const char* aWarning, + const nsTArray<nsString>& aParams); + + static Matrix GetCTM(dom::SVGElement* aElement, bool aScreenCTM); + + /** + * Gets the tight bounds-space stroke bounds of the non-scaling-stroked rect + * aRect. + * @param aToBoundsSpace transforms from source space to the space aBounds + * should be computed in. Must be rectilinear. + * @param aToNonScalingStrokeSpace transforms from source + * space to the space in which non-scaling stroke should be applied. + * Must be rectilinear. + */ + static void RectilinearGetStrokeBounds(const Rect& aRect, + const Matrix& aToBoundsSpace, + const Matrix& aToNonScalingStrokeSpace, + float aStrokeWidth, Rect* aBounds); + + /** + * Check if this is one of the SVG elements that SVG 1.1 Full says + * establishes a viewport: svg, symbol, image or foreignObject. + */ + static bool EstablishesViewport(nsIContent* aContent); + + static mozilla::dom::SVGViewportElement* GetNearestViewportElement( + const nsIContent* aContent); + + /* enum for specifying coordinate direction for ObjectSpace/UserSpace */ + enum ctxDirection { X, Y, XY }; + + /** + * Computes sqrt((aWidth^2 + aHeight^2)/2); + */ + static double ComputeNormalizedHypotenuse(double aWidth, double aHeight); + + /* Returns the angle halfway between the two specified angles */ + static float AngleBisect(float a1, float a2); + + /* Generate a viewbox to viewport transformation matrix */ + + static Matrix GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGAnimatedPreserveAspectRatio& aPreserveAspectRatio); + + static Matrix GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGPreserveAspectRatio& aPreserveAspectRatio); + + static mozilla::RangedPtr<const char16_t> GetStartRangedPtr( + const nsAString& aString); + + static mozilla::RangedPtr<const char16_t> GetEndRangedPtr( + const nsAString& aString); + + /** + * Parses the sign (+ or -) of a number and moves aIter to the next + * character if a sign is found. + * @param aSignMultiplier [outparam] -1 if the sign is negative otherwise 1 + * @return false if we hit the end of the string (i.e. if aIter is initially + * at aEnd, or if we reach aEnd right after the sign character). + */ + static inline bool ParseOptionalSign( + mozilla::RangedPtr<const char16_t>& aIter, + const mozilla::RangedPtr<const char16_t>& aEnd, + int32_t& aSignMultiplier) { + if (aIter == aEnd) { + return false; + } + aSignMultiplier = *aIter == '-' ? -1 : 1; + + mozilla::RangedPtr<const char16_t> iter(aIter); + + if (*iter == '-' || *iter == '+') { + ++iter; + if (iter == aEnd) { + return false; + } + } + aIter = iter; + return true; + } + + /** + * Parse a number of the form: + * number ::= integer ([Ee] integer)? | [+-]? [0-9]* "." [0-9]+ ([Ee] + * integer)? Parsing fails if the number cannot be represented by a floatType. + * If parsing succeeds, aIter is updated so that it points to the character + * after the end of the number, otherwise it is left unchanged + */ + template <class floatType> + static bool ParseNumber(mozilla::RangedPtr<const char16_t>& aIter, + const mozilla::RangedPtr<const char16_t>& aEnd, + floatType& aValue); + + /** + * Parse a number of the form: + * number ::= integer ([Ee] integer)? | [+-]? [0-9]* "." [0-9]+ ([Ee] + * integer)? Parsing fails if there is anything left over after the number, or + * the number cannot be represented by a floatType. + */ + template <class floatType> + static bool ParseNumber(const nsAString& aString, floatType& aValue); + + /** + * Parse an integer of the form: + * integer ::= [+-]? [0-9]+ + * The returned number is clamped to an int32_t if outside that range. + * If parsing succeeds, aIter is updated so that it points to the character + * after the end of the number, otherwise it is left unchanged + */ + static bool ParseInteger(mozilla::RangedPtr<const char16_t>& aIter, + const mozilla::RangedPtr<const char16_t>& aEnd, + int32_t& aValue); + + /** + * Parse an integer of the form: + * integer ::= [+-]? [0-9]+ + * The returned number is clamped to an int32_t if outside that range. + * Parsing fails if there is anything left over after the number. + */ + static bool ParseInteger(const nsAString& aString, int32_t& aValue); + + // XXX This should rather use LengthPercentage instead of + // StyleLengthPercentageUnion, but that's a type alias defined in + // ServoStyleConsts.h, and we don't want to avoid including that large header + // with all its dependencies. If a forwarding header were generated by + // cbindgen, we could include that. + // https://github.com/eqrion/cbindgen/issues/617 addresses this. + /** + * Converts a LengthPercentage into a userspace value, resolving percentage + * values relative to aContent's SVG viewport. + */ + static float CoordToFloat(dom::SVGElement* aContent, + const StyleLengthPercentageUnion&, + uint8_t aCtxType = SVGContentUtils::XY); + /** + * Parse the SVG path string + * Returns a path + * string formatted as an SVG path + */ + static already_AddRefed<mozilla::gfx::Path> GetPath( + const nsAString& aPathString); + + /** + * Returns true if aContent is one of the elements whose stroke is guaranteed + * to have no corners: circle or ellipse + */ + static bool ShapeTypeHasNoCorners(const nsIContent* aContent); + + /** + * Return one token in aString, aString may have leading and trailing + * whitespace; aSuccess will be set to false if there is no token or more than + * one token, otherwise it's set to true. + */ + static nsDependentSubstring GetAndEnsureOneToken(const nsAString& aString, + bool& aSuccess); +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGCONTENTUTILS_H_ diff --git a/dom/svg/SVGDataParser.cpp b/dom/svg/SVGDataParser.cpp new file mode 100644 index 0000000000..43112a2134 --- /dev/null +++ b/dom/svg/SVGDataParser.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGDataParser.h" +#include "nsContentUtils.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +SVGDataParser::SVGDataParser(const nsAString& aValue) + : mIter(SVGContentUtils::GetStartRangedPtr(aValue)), + mEnd(SVGContentUtils::GetEndRangedPtr(aValue)) {} + +bool SVGDataParser::SkipCommaWsp() { + if (!SkipWsp()) { + // end of string + return false; + } + if (*mIter != ',') { + return true; + } + ++mIter; + return SkipWsp(); +} + +bool SVGDataParser::SkipWsp() { + while (mIter != mEnd) { + if (!nsContentUtils::IsHTMLWhitespace(*mIter)) { + return true; + } + ++mIter; + } + return false; +} + +} // namespace mozilla diff --git a/dom/svg/SVGDataParser.h b/dom/svg/SVGDataParser.h new file mode 100644 index 0000000000..9e0996361a --- /dev/null +++ b/dom/svg/SVGDataParser.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDATAPARSER_H_ +#define DOM_SVG_SVGDATAPARSER_H_ + +#include <cctype> +#include "mozilla/RangedPtr.h" +#include "nsStringFwd.h" + +namespace mozilla { + +//////////////////////////////////////////////////////////////////////// +// SVGDataParser: a simple base class for parsing values +// for path and transform values. +// +class SVGDataParser { + public: + explicit SVGDataParser(const nsAString& aValue); + + protected: + static bool IsAlpha(char16_t aCh) { + // Exclude non-ascii characters before calling isalpha + return (aCh & 0x7f) == aCh && isalpha(aCh); + } + + // Returns true if there are more characters to read, false otherwise. + bool SkipCommaWsp(); + + // Returns true if there are more characters to read, false otherwise. + bool SkipWsp(); + + mozilla::RangedPtr<const char16_t> mIter; + const mozilla::RangedPtr<const char16_t> mEnd; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGDATAPARSER_H_ diff --git a/dom/svg/SVGDefsElement.cpp b/dom/svg/SVGDefsElement.cpp new file mode 100644 index 0000000000..0e518c9b66 --- /dev/null +++ b/dom/svg/SVGDefsElement.cpp @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGDefsElement.h" +#include "mozilla/dom/SVGDefsElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Defs) + +namespace mozilla::dom { + +JSObject* SVGDefsElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGDefsElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGDefsElement::SVGDefsElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGraphicsElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGDefsElement) + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGDefsElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return FindAttributeDependence(name, map) || + SVGGraphicsElement::IsAttributeMapped(name); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGDefsElement.h b/dom/svg/SVGDefsElement.h new file mode 100644 index 0000000000..e1e43e46da --- /dev/null +++ b/dom/svg/SVGDefsElement.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDEFSELEMENT_H_ +#define DOM_SVG_SVGDEFSELEMENT_H_ + +#include "SVGGraphicsElement.h" + +nsresult NS_NewSVGDefsElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGDefsElement final : public SVGGraphicsElement { + protected: + friend nsresult(::NS_NewSVGDefsElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGDefsElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // defs elements are not focusable. + bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override { + return nsIContent::IsFocusableInternal(aTabIndex, aWithMouse); + } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGDEFSELEMENT_H_ diff --git a/dom/svg/SVGDescElement.cpp b/dom/svg/SVGDescElement.cpp new file mode 100644 index 0000000000..95e8f0f168 --- /dev/null +++ b/dom/svg/SVGDescElement.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGDescElement.h" +#include "mozilla/dom/SVGDescElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Desc) + +namespace mozilla::dom { + +JSObject* SVGDescElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGDescElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGDescElement::SVGDescElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGDescElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGDescElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGDescElement.h b/dom/svg/SVGDescElement.h new file mode 100644 index 0000000000..0769db9519 --- /dev/null +++ b/dom/svg/SVGDescElement.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDESCELEMENT_H_ +#define DOM_SVG_SVGDESCELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" + +nsresult NS_NewSVGDescElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGDescElementBase = SVGElement; + +class SVGDescElement final : public SVGDescElementBase { + protected: + friend nsresult(::NS_NewSVGDescElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGDescElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGDESCELEMENT_H_ diff --git a/dom/svg/SVGDocument.cpp b/dom/svg/SVGDocument.cpp new file mode 100644 index 0000000000..6a69aaaa32 --- /dev/null +++ b/dom/svg/SVGDocument.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGDocument.h" + +#include "mozilla/css/Loader.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsLiteralString.h" +#include "mozilla/dom/Element.h" +#include "SVGElement.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" + +using namespace mozilla::css; +using namespace mozilla::dom; + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// Implementation + +nsresult SVGDocument::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const { + NS_ASSERTION(aNodeInfo->NodeInfoManager() == mNodeInfoManager, + "Can't import this document into another document!"); + + RefPtr<SVGDocument> clone = new SVGDocument(); + nsresult rv = CloneDocHelper(clone.get()); + NS_ENSURE_SUCCESS(rv, rv); + + clone.forget(aResult); + return NS_OK; +} + +} // namespace mozilla::dom + +//////////////////////////////////////////////////////////////////////// +// Exported creation functions + +nsresult NS_NewSVGDocument(Document** aInstancePtrResult) { + RefPtr<SVGDocument> doc = new SVGDocument(); + + nsresult rv = doc->Init(); + if (NS_FAILED(rv)) { + return rv; + } + + doc.forget(aInstancePtrResult); + return rv; +} diff --git a/dom/svg/SVGDocument.h b/dom/svg/SVGDocument.h new file mode 100644 index 0000000000..e5c9ab9359 --- /dev/null +++ b/dom/svg/SVGDocument.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDOCUMENT_H_ +#define DOM_SVG_SVGDOCUMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/XMLDocument.h" + +namespace mozilla { + +class SVGContextPaint; + +namespace dom { + +class SVGElement; +class SVGForeignObjectElement; + +class SVGDocument final : public XMLDocument { + public: + SVGDocument() : XMLDocument("image/svg+xml") { mType = eSVG; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + void SetCurrentContextPaint(const SVGContextPaint* aContextPaint) { + mCurrentContextPaint = aContextPaint; + } + + const SVGContextPaint* GetCurrentContextPaint() const { + return mCurrentContextPaint; + } + + private: + // This is maintained by AutoSetRestoreSVGContextPaint. + const SVGContextPaint* mCurrentContextPaint = nullptr; +}; + +inline SVGDocument* Document::AsSVGDocument() { + MOZ_ASSERT(IsSVGDocument()); + return static_cast<SVGDocument*>(this); +} + +inline const SVGDocument* Document::AsSVGDocument() const { + MOZ_ASSERT(IsSVGDocument()); + return static_cast<const SVGDocument*>(this); +} + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGDOCUMENT_H_ diff --git a/dom/svg/SVGElement.cpp b/dom/svg/SVGElement.cpp new file mode 100644 index 0000000000..ec59d00926 --- /dev/null +++ b/dom/svg/SVGElement.cpp @@ -0,0 +1,2452 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGElement.h" + +#include "mozilla/dom/MutationEventBinding.h" +#include "mozilla/dom/MutationObservers.h" +#include "mozilla/dom/CSSRuleBinding.h" +#include "mozilla/dom/SVGElementBinding.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGTests.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "mozilla/dom/Element.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/DeclarationBlock.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/Unused.h" + +#include "mozAutoDocUpdate.h" +#include "nsAttrValueOrString.h" +#include "nsCSSProps.h" +#include "nsCSSValue.h" +#include "nsContentUtils.h" +#include "nsDOMCSSAttrDeclaration.h" +#include "nsICSSDeclaration.h" +#include "nsIContentInlines.h" +#include "mozilla/dom/Document.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include "nsQueryObject.h" +#include "nsLayoutUtils.h" +#include "SVGAnimatedNumberList.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAnimatedPointList.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGAnimatedTransformList.h" +#include "SVGAnimatedBoolean.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedInteger.h" +#include "SVGAnimatedIntegerPair.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedOrient.h" +#include "SVGAnimatedString.h" +#include "SVGAnimatedViewBox.h" +#include "SVGGeometryProperty.h" +#include "SVGMotionSMILAttr.h" +#include <stdarg.h> + +// This is needed to ensure correct handling of calls to the +// vararg-list methods in this file: +// SVGElement::GetAnimated{Length,Number,Integer}Values +// See bug 547964 for details: +static_assert(sizeof(void*) == sizeof(nullptr), + "nullptr should be the correct size"); + +nsresult NS_NewSVGElement( + mozilla::dom::Element** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) { + RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); + auto* nim = nodeInfo->NodeInfoManager(); + RefPtr<mozilla::dom::SVGElement> it = + new (nim) mozilla::dom::SVGElement(nodeInfo.forget()); + nsresult rv = it->Init(); + + if (NS_FAILED(rv)) { + return rv; + } + + it.forget(aResult); + return rv; +} + +namespace mozilla::dom { +using namespace SVGUnitTypes_Binding; + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGElement) + +// Use the CC variant of this, even though this class does not define +// a new CC participant, to make QIing to the CC interfaces faster. +NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(SVGElement, SVGElementBase, + SVGElement) + +SVGEnumMapping SVGElement::sSVGUnitTypesMap[] = { + {nsGkAtoms::userSpaceOnUse, SVG_UNIT_TYPE_USERSPACEONUSE}, + {nsGkAtoms::objectBoundingBox, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nullptr, 0}}; + +SVGElement::SVGElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGElementBase(std::move(aNodeInfo)) {} + +SVGElement::~SVGElement() { + OwnerDoc()->UnscheduleSVGForPresAttrEvaluation(this); +} + +JSObject* SVGElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +template <typename Value, typename Info> +void SVGElement::AttributesInfo<Value, Info>::ResetAll() { + for (uint32_t i = 0; i < mCount; ++i) { + Reset(i); + } +} + +template <typename Value, typename Info> +void SVGElement::AttributesInfo<Value, Info>::CopyAllFrom( + const AttributesInfo& aOther) { + MOZ_DIAGNOSTIC_ASSERT(mCount == aOther.mCount, + "Should only be called on clones"); + for (uint32_t i = 0; i < mCount; ++i) { + mValues[i] = aOther.mValues[i]; + } +} + +template <> +void SVGElement::LengthAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(mInfos[aAttrEnum].mCtxType, aAttrEnum, + mInfos[aAttrEnum].mDefaultValue, + mInfos[aAttrEnum].mDefaultUnitType); +} + +template <> +void SVGElement::LengthListAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].ClearBaseValue(aAttrEnum); + // caller notifies +} + +template <> +void SVGElement::NumberListAttributesInfo::Reset(uint8_t aAttrEnum) { + MOZ_ASSERT(aAttrEnum < mCount, "Bad attr enum"); + mValues[aAttrEnum].ClearBaseValue(aAttrEnum); + // caller notifies +} + +template <> +void SVGElement::NumberAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::NumberPairAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue1, + mInfos[aAttrEnum].mDefaultValue2); +} + +template <> +void SVGElement::IntegerAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::IntegerPairAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue1, + mInfos[aAttrEnum].mDefaultValue2); +} + +template <> +void SVGElement::BooleanAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::StringListAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Clear(); + // caller notifies +} + +template <> +void SVGElement::EnumAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::StringAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum); +} + +nsresult SVGElement::CopyInnerTo(mozilla::dom::Element* aDest) { + nsresult rv = Element::CopyInnerTo(aDest); + NS_ENSURE_SUCCESS(rv, rv); + + auto* dest = static_cast<SVGElement*>(aDest); + + // cloning a node must retain its internal nonce slot + if (auto* nonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce))) { + dest->SetNonce(*nonce); + } + + // If our destination is a print document, copy all the relevant length values + // etc so that they match the state of the original node. + if (aDest->OwnerDoc()->IsStaticDocument()) { + dest->GetLengthInfo().CopyAllFrom(GetLengthInfo()); + dest->GetNumberInfo().CopyAllFrom(GetNumberInfo()); + dest->GetNumberPairInfo().CopyAllFrom(GetNumberPairInfo()); + dest->GetIntegerInfo().CopyAllFrom(GetIntegerInfo()); + dest->GetIntegerPairInfo().CopyAllFrom(GetIntegerPairInfo()); + dest->GetEnumInfo().CopyAllFrom(GetEnumInfo()); + dest->GetStringInfo().CopyAllFrom(GetStringInfo()); + dest->GetLengthListInfo().CopyAllFrom(GetLengthListInfo()); + dest->GetNumberListInfo().CopyAllFrom(GetNumberListInfo()); + } + + return NS_OK; +} + +//---------------------------------------------------------------------- +// SVGElement methods + +void SVGElement::DidAnimateClass() { + // For Servo, snapshot the element before we change it. + PresShell* presShell = OwnerDoc()->GetPresShell(); + if (presShell) { + if (nsPresContext* presContext = presShell->GetPresContext()) { + presContext->RestyleManager()->ClassAttributeWillBeChangedBySMIL(this); + } + } + + nsAutoString src; + mClassAttribute.GetAnimValue(src, this); + if (!mClassAnimAttr) { + mClassAnimAttr = MakeUnique<nsAttrValue>(); + } + mClassAnimAttr->ParseAtomArray(src); + + // FIXME(emilio): This re-selector-matches, but we do the snapshot stuff right + // above... Is this needed anymore? + if (presShell) { + presShell->RestyleForAnimation(this, RestyleHint::RESTYLE_SELF); + } +} + +nsresult SVGElement::Init() { + // Set up length attributes - can't do this in the constructor + // because we can't do a virtual call at that point + + GetLengthInfo().ResetAll(); + GetNumberInfo().ResetAll(); + GetNumberPairInfo().ResetAll(); + GetIntegerInfo().ResetAll(); + GetIntegerPairInfo().ResetAll(); + GetBooleanInfo().ResetAll(); + GetEnumInfo().ResetAll(); + + if (SVGAnimatedOrient* orient = GetAnimatedOrient()) { + orient->Init(); + } + + if (SVGAnimatedViewBox* viewBox = GetAnimatedViewBox()) { + viewBox->Init(); + } + + if (SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio()) { + preserveAspectRatio->Init(); + } + + GetLengthListInfo().ResetAll(); + GetNumberListInfo().ResetAll(); + + // No need to reset SVGPointList since the default value is always the same + // (an empty list). + + // No need to reset SVGPathData since the default value is always the same + // (an empty list). + + GetStringInfo().ResetAll(); + return NS_OK; +} + +//---------------------------------------------------------------------- +// Implementation + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + // Hide any nonce from the DOM, but keep the internal value of the + // nonce by copying and resetting the internal nonce value. + if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() && + OwnerDoc()->GetBrowsingContext()) { + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "SVGElement::ResetNonce::Runnable", + [self = RefPtr<SVGElement>(this)]() { + nsAutoString nonce; + self->GetNonce(nonce); + self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true); + self->SetNonce(nonce); + })); + } + + return NS_OK; +} + +nsresult SVGElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + // We don't currently use nsMappedAttributes within SVG. If this changes, we + // need to be very careful because some nsAttrValues used by SVG point to + // member data of SVG elements and if an nsAttrValue outlives the SVG element + // whose data it points to (by virtue of being stored in + // mAttrs->mMappedAttributes, meaning it's shared between + // elements), the pointer will dangle. See bug 724680. + MOZ_ASSERT(!mAttrs.HasMappedAttrs(), + "Unexpected use of nsMappedAttributes within SVG"); + + // If this is an svg presentation attribute we need to map it into + // the content declaration block. + // XXX For some reason incremental mapping doesn't work, so for now + // just delete the style rule and lazily reconstruct it as needed). + if (aNamespaceID == kNameSpaceID_None && IsAttributeMapped(aName)) { + mContentDeclarationBlock = nullptr; + OwnerDoc()->ScheduleSVGForPresAttrEvaluation(this); + } + + if (IsEventAttributeName(aName) && aValue) { + MOZ_ASSERT(aValue->Type() == nsAttrValue::eString, + "Expected string value for script body"); + SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue()); + } + + // The nonce will be copied over to an internal slot and cleared from the + // Element within BindToTree to avoid CSS Selector nonce exfiltration if + // the CSP list contains a header-delivered CSP. + if (nsGkAtoms::nonce == aName && kNameSpaceID_None == aNamespaceID) { + if (aValue) { + SetNonce(aValue->GetStringValue()); + if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) { + SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP); + } + } else { + RemoveNonce(); + } + } + + return SVGElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, + aSubjectPrincipal, aNotify); +} + +bool SVGElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + nsresult rv = NS_OK; + bool foundMatch = false; + bool didSetResult = false; + + if (aNamespaceID == kNameSpaceID_None) { + // Check for SVGAnimatedLength attribute + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + uint32_t i; + for (i = 0; i < lengthInfo.mCount; i++) { + if (aAttribute == lengthInfo.mInfos[i].mName) { + rv = lengthInfo.mValues[i].SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + lengthInfo.Reset(i); + } else { + aResult.SetTo(lengthInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + + if (!foundMatch) { + // Check for SVGAnimatedLengthList attribute + LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); + for (i = 0; i < lengthListInfo.mCount; i++) { + if (aAttribute == lengthListInfo.mInfos[i].mName) { + rv = lengthListInfo.mValues[i].SetBaseValueString(aValue); + if (NS_FAILED(rv)) { + lengthListInfo.Reset(i); + } else { + aResult.SetTo(lengthListInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedNumberList attribute + NumberListAttributesInfo numberListInfo = GetNumberListInfo(); + for (i = 0; i < numberListInfo.mCount; i++) { + if (aAttribute == numberListInfo.mInfos[i].mName) { + rv = numberListInfo.mValues[i].SetBaseValueString(aValue); + if (NS_FAILED(rv)) { + numberListInfo.Reset(i); + } else { + aResult.SetTo(numberListInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedPointList attribute + if (GetPointListAttrName() == aAttribute) { + if (SVGAnimatedPointList* pointList = GetAnimatedPointList()) { + pointList->SetBaseValueString(aValue); + // The spec says we parse everything up to the failure, so we DON'T + // need to check the result of SetBaseValueString or call + // pointList->ClearBaseValue() if it fails + aResult.SetTo(pointList->GetBaseValue(), &aValue); + didSetResult = true; + foundMatch = true; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedPathSegList attribute + if (GetPathDataAttrName() == aAttribute) { + if (SVGAnimatedPathSegList* segList = GetAnimPathSegList()) { + segList->SetBaseValueString(aValue); + // The spec says we parse everything up to the failure, so we DON'T + // need to check the result of SetBaseValueString or call + // segList->ClearBaseValue() if it fails + aResult.SetTo(segList->GetBaseValue(), &aValue); + didSetResult = true; + foundMatch = true; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedNumber attribute + NumberAttributesInfo numberInfo = GetNumberInfo(); + for (i = 0; i < numberInfo.mCount; i++) { + if (aAttribute == numberInfo.mInfos[i].mName) { + rv = numberInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + numberInfo.Reset(i); + } else { + aResult.SetTo(numberInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedNumberPair attribute + NumberPairAttributesInfo numberPairInfo = GetNumberPairInfo(); + for (i = 0; i < numberPairInfo.mCount; i++) { + if (aAttribute == numberPairInfo.mInfos[i].mName) { + rv = numberPairInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + numberPairInfo.Reset(i); + } else { + aResult.SetTo(numberPairInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedInteger attribute + IntegerAttributesInfo integerInfo = GetIntegerInfo(); + for (i = 0; i < integerInfo.mCount; i++) { + if (aAttribute == integerInfo.mInfos[i].mName) { + rv = integerInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + integerInfo.Reset(i); + } else { + aResult.SetTo(integerInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedIntegerPair attribute + IntegerPairAttributesInfo integerPairInfo = GetIntegerPairInfo(); + for (i = 0; i < integerPairInfo.mCount; i++) { + if (aAttribute == integerPairInfo.mInfos[i].mName) { + rv = integerPairInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + integerPairInfo.Reset(i); + } else { + aResult.SetTo(integerPairInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedBoolean attribute + BooleanAttributesInfo booleanInfo = GetBooleanInfo(); + for (i = 0; i < booleanInfo.mCount; i++) { + if (aAttribute == booleanInfo.mInfos[i].mName) { + nsAtom* valAtom = NS_GetStaticAtom(aValue); + rv = valAtom ? booleanInfo.mValues[i].SetBaseValueAtom(valAtom, this) + : NS_ERROR_DOM_SYNTAX_ERR; + if (NS_FAILED(rv)) { + booleanInfo.Reset(i); + } else { + aResult.SetTo(valAtom); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedEnumeration attribute + EnumAttributesInfo enumInfo = GetEnumInfo(); + for (i = 0; i < enumInfo.mCount; i++) { + if (aAttribute == enumInfo.mInfos[i].mName) { + RefPtr<nsAtom> valAtom = NS_Atomize(aValue); + if (!enumInfo.mValues[i].SetBaseValueAtom(valAtom, this)) { + // Exact error value does not matter; we just need to mark the + // parse as failed. + rv = NS_ERROR_FAILURE; + enumInfo.Reset(i); + } else { + aResult.SetTo(valAtom); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for conditional processing attributes + nsCOMPtr<SVGTests> tests = do_QueryObject(this); + if (tests && tests->ParseConditionalProcessingAttribute( + aAttribute, aValue, aResult)) { + foundMatch = true; + } + } + + if (!foundMatch) { + // Check for StringList attribute + StringListAttributesInfo stringListInfo = GetStringListInfo(); + for (i = 0; i < stringListInfo.mCount; i++) { + if (aAttribute == stringListInfo.mInfos[i].mName) { + rv = stringListInfo.mValues[i].SetValue(aValue); + if (NS_FAILED(rv)) { + stringListInfo.Reset(i); + } else { + aResult.SetTo(stringListInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for orient attribute + if (aAttribute == nsGkAtoms::orient) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + if (orient) { + rv = orient->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + orient->Init(); + } else { + aResult.SetTo(*orient, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for viewBox attribute + } else if (aAttribute == nsGkAtoms::viewBox) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + if (viewBox) { + rv = viewBox->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + viewBox->Init(); + } else { + aResult.SetTo(*viewBox, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for preserveAspectRatio attribute + } else if (aAttribute == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + if (preserveAspectRatio) { + rv = preserveAspectRatio->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + preserveAspectRatio->Init(); + } else { + aResult.SetTo(*preserveAspectRatio, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for SVGAnimatedTransformList attribute + } else if (GetTransformListAttrName() == aAttribute) { + // The transform attribute is being set, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + SVGAnimatedTransformList* transformList = + GetAnimatedTransformList(DO_ALLOCATE); + rv = transformList->SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + transformList->ClearBaseValue(); + } else { + aResult.SetTo(transformList->GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + } else if (aAttribute == nsGkAtoms::tabindex) { + didSetResult = aResult.ParseIntValue(aValue); + foundMatch = true; + } + } + + if (aAttribute == nsGkAtoms::_class) { + mClassAttribute.SetBaseValue(aValue, this, false); + aResult.ParseAtomArray(aValue); + return true; + } + + if (aAttribute == nsGkAtoms::rel) { + aResult.ParseAtomArray(aValue); + return true; + } + } + + if (!foundMatch) { + // Check for SVGAnimatedString attribute + StringAttributesInfo stringInfo = GetStringInfo(); + for (uint32_t i = 0; i < stringInfo.mCount; i++) { + if (aNamespaceID == stringInfo.mInfos[i].mNamespaceID && + aAttribute == stringInfo.mInfos[i].mName) { + stringInfo.mValues[i].SetBaseValue(aValue, this, false); + foundMatch = true; + break; + } + } + } + + if (foundMatch) { + if (NS_FAILED(rv)) { + ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue); + return false; + } + if (!didSetResult) { + aResult.SetTo(aValue); + } + return true; + } + + return SVGElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +void SVGElement::UnsetAttrInternal(int32_t aNamespaceID, nsAtom* aName, + bool aNotify) { + // XXXbz there's a bunch of redundancy here with AfterSetAttr. + // Maybe consolidate? + + if (aNamespaceID == kNameSpaceID_None) { + // If this is an svg presentation attribute, remove declaration block to + // force an update + if (IsAttributeMapped(aName)) { + mContentDeclarationBlock = nullptr; + } + + if (IsEventAttributeName(aName)) { + EventListenerManager* manager = GetExistingListenerManager(); + if (manager) { + nsAtom* eventName = GetEventNameForAttr(aName); + manager->RemoveEventHandler(eventName); + } + return; + } + + // Check if this is a length attribute going away + LengthAttributesInfo lenInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lenInfo.mCount; i++) { + if (aName == lenInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + lenInfo.Reset(i); + return; + } + } + + // Check if this is a length list attribute going away + LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); + + for (uint32_t i = 0; i < lengthListInfo.mCount; i++) { + if (aName == lengthListInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + lengthListInfo.Reset(i); + return; + } + } + + // Check if this is a number list attribute going away + NumberListAttributesInfo numberListInfo = GetNumberListInfo(); + + for (uint32_t i = 0; i < numberListInfo.mCount; i++) { + if (aName == numberListInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + numberListInfo.Reset(i); + return; + } + } + + // Check if this is a point list attribute going away + if (GetPointListAttrName() == aName) { + SVGAnimatedPointList* pointList = GetAnimatedPointList(); + if (pointList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + pointList->ClearBaseValue(); + return; + } + } + + // Check if this is a path segment list attribute going away + if (GetPathDataAttrName() == aName) { + SVGAnimatedPathSegList* segList = GetAnimPathSegList(); + if (segList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + segList->ClearBaseValue(); + return; + } + } + + // Check if this is a number attribute going away + NumberAttributesInfo numInfo = GetNumberInfo(); + + for (uint32_t i = 0; i < numInfo.mCount; i++) { + if (aName == numInfo.mInfos[i].mName) { + numInfo.Reset(i); + return; + } + } + + // Check if this is a number pair attribute going away + NumberPairAttributesInfo numPairInfo = GetNumberPairInfo(); + + for (uint32_t i = 0; i < numPairInfo.mCount; i++) { + if (aName == numPairInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + numPairInfo.Reset(i); + return; + } + } + + // Check if this is an integer attribute going away + IntegerAttributesInfo intInfo = GetIntegerInfo(); + + for (uint32_t i = 0; i < intInfo.mCount; i++) { + if (aName == intInfo.mInfos[i].mName) { + intInfo.Reset(i); + return; + } + } + + // Check if this is an integer pair attribute going away + IntegerPairAttributesInfo intPairInfo = GetIntegerPairInfo(); + + for (uint32_t i = 0; i < intPairInfo.mCount; i++) { + if (aName == intPairInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + intPairInfo.Reset(i); + return; + } + } + + // Check if this is a boolean attribute going away + BooleanAttributesInfo boolInfo = GetBooleanInfo(); + + for (uint32_t i = 0; i < boolInfo.mCount; i++) { + if (aName == boolInfo.mInfos[i].mName) { + boolInfo.Reset(i); + return; + } + } + + // Check if this is an enum attribute going away + EnumAttributesInfo enumInfo = GetEnumInfo(); + + for (uint32_t i = 0; i < enumInfo.mCount; i++) { + if (aName == enumInfo.mInfos[i].mName) { + enumInfo.Reset(i); + return; + } + } + + // Check if this is an orient attribute going away + if (aName == nsGkAtoms::orient) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + if (orient) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + orient->Init(); + return; + } + } + + // Check if this is a viewBox attribute going away + if (aName == nsGkAtoms::viewBox) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + if (viewBox) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + viewBox->Init(); + return; + } + } + + // Check if this is a preserveAspectRatio attribute going away + if (aName == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + if (preserveAspectRatio) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + preserveAspectRatio->Init(); + return; + } + } + + // Check if this is a transform list attribute going away + if (GetTransformListAttrName() == aName) { + SVGAnimatedTransformList* transformList = GetAnimatedTransformList(); + if (transformList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + transformList->ClearBaseValue(); + return; + } + } + + // Check for conditional processing attributes + nsCOMPtr<SVGTests> tests = do_QueryObject(this); + if (tests && tests->IsConditionalProcessingAttribute(aName)) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + tests->UnsetAttr(aName); + return; + } + + // Check if this is a string list attribute going away + StringListAttributesInfo stringListInfo = GetStringListInfo(); + + for (uint32_t i = 0; i < stringListInfo.mCount; i++) { + if (aName == stringListInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + stringListInfo.Reset(i); + return; + } + } + + if (aName == nsGkAtoms::_class) { + mClassAttribute.Init(); + return; + } + } + + // Check if this is a string attribute going away + StringAttributesInfo stringInfo = GetStringInfo(); + + for (uint32_t i = 0; i < stringInfo.mCount; i++) { + if (aNamespaceID == stringInfo.mInfos[i].mNamespaceID && + aName == stringInfo.mInfos[i].mName) { + stringInfo.Reset(i); + return; + } + } +} + +nsresult SVGElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValueOrString* aValue, + bool aNotify) { + if (!aValue) { + UnsetAttrInternal(aNamespaceID, aName, aNotify); + } + return SVGElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); +} + +nsChangeHint SVGElement::GetAttributeChangeHint(const nsAtom* aAttribute, + int32_t aModType) const { + nsChangeHint retval = + SVGElementBase::GetAttributeChangeHint(aAttribute, aModType); + + nsCOMPtr<SVGTests> tests = do_QueryObject(const_cast<SVGElement*>(this)); + if (tests && tests->IsConditionalProcessingAttribute(aAttribute)) { + // It would be nice to only reconstruct the frame if the value returned by + // SVGTests::PassesConditionalProcessingTests has changed, but we don't + // know that + retval |= nsChangeHint_ReconstructFrame; + } + return retval; +} + +bool SVGElement::IsNodeOfType(uint32_t aFlags) const { return false; } + +void SVGElement::NodeInfoChanged(Document* aOldDoc) { + SVGElementBase::NodeInfoChanged(aOldDoc); + aOldDoc->UnscheduleSVGForPresAttrEvaluation(this); + mContentDeclarationBlock = nullptr; + OwnerDoc()->ScheduleSVGForPresAttrEvaluation(this); +} + +NS_IMETHODIMP_(bool) +SVGElement::IsAttributeMapped(const nsAtom* name) const { + if (name == nsGkAtoms::lang) { + return true; + } + return SVGElementBase::IsAttributeMapped(name); +} + +// PresentationAttributes-FillStroke +/* static */ +const Element::MappedAttributeEntry SVGElement::sFillStrokeMap[] = { + {nsGkAtoms::fill}, + {nsGkAtoms::fill_opacity}, + {nsGkAtoms::fill_rule}, + {nsGkAtoms::paint_order}, + {nsGkAtoms::stroke}, + {nsGkAtoms::stroke_dasharray}, + {nsGkAtoms::stroke_dashoffset}, + {nsGkAtoms::stroke_linecap}, + {nsGkAtoms::stroke_linejoin}, + {nsGkAtoms::stroke_miterlimit}, + {nsGkAtoms::stroke_opacity}, + {nsGkAtoms::stroke_width}, + {nsGkAtoms::vector_effect}, + {nullptr}}; + +// PresentationAttributes-Graphics +/* static */ +const Element::MappedAttributeEntry SVGElement::sGraphicsMap[] = { + {nsGkAtoms::clip_path}, + {nsGkAtoms::clip_rule}, + {nsGkAtoms::colorInterpolation}, + {nsGkAtoms::cursor}, + {nsGkAtoms::display}, + {nsGkAtoms::filter}, + {nsGkAtoms::image_rendering}, + {nsGkAtoms::mask}, + {nsGkAtoms::opacity}, + {nsGkAtoms::pointer_events}, + {nsGkAtoms::shape_rendering}, + {nsGkAtoms::text_rendering}, + {nsGkAtoms::transform_origin}, + {nsGkAtoms::visibility}, + {nullptr}}; + +// PresentationAttributes-TextContentElements +/* static */ +const Element::MappedAttributeEntry SVGElement::sTextContentElementsMap[] = { + // Properties that we don't support are commented out. + // { nsGkAtoms::alignment_baseline }, + // { nsGkAtoms::baseline_shift }, + {nsGkAtoms::direction}, {nsGkAtoms::dominant_baseline}, + {nsGkAtoms::letter_spacing}, {nsGkAtoms::text_anchor}, + {nsGkAtoms::text_decoration}, {nsGkAtoms::unicode_bidi}, + {nsGkAtoms::white_space}, {nsGkAtoms::word_spacing}, + {nsGkAtoms::writing_mode}, {nullptr}}; + +// PresentationAttributes-FontSpecification +/* static */ +const Element::MappedAttributeEntry SVGElement::sFontSpecificationMap[] = { + {nsGkAtoms::font_family}, {nsGkAtoms::font_size}, + {nsGkAtoms::font_size_adjust}, {nsGkAtoms::font_stretch}, + {nsGkAtoms::font_style}, {nsGkAtoms::font_variant}, + {nsGkAtoms::fontWeight}, {nullptr}}; + +// PresentationAttributes-GradientStop +/* static */ +const Element::MappedAttributeEntry SVGElement::sGradientStopMap[] = { + {nsGkAtoms::stop_color}, {nsGkAtoms::stop_opacity}, {nullptr}}; + +// PresentationAttributes-Viewports +/* static */ +const Element::MappedAttributeEntry SVGElement::sViewportsMap[] = { + {nsGkAtoms::overflow}, {nsGkAtoms::clip}, {nullptr}}; + +// PresentationAttributes-Makers +/* static */ +const Element::MappedAttributeEntry SVGElement::sMarkersMap[] = { + {nsGkAtoms::marker_end}, + {nsGkAtoms::marker_mid}, + {nsGkAtoms::marker_start}, + {nullptr}}; + +// PresentationAttributes-Color +/* static */ +const Element::MappedAttributeEntry SVGElement::sColorMap[] = { + {nsGkAtoms::color}, {nullptr}}; + +// PresentationAttributes-Filters +/* static */ +const Element::MappedAttributeEntry SVGElement::sFiltersMap[] = { + {nsGkAtoms::colorInterpolationFilters}, {nullptr}}; + +// PresentationAttributes-feFlood +/* static */ +const Element::MappedAttributeEntry SVGElement::sFEFloodMap[] = { + {nsGkAtoms::flood_color}, {nsGkAtoms::flood_opacity}, {nullptr}}; + +// PresentationAttributes-LightingEffects +/* static */ +const Element::MappedAttributeEntry SVGElement::sLightingEffectsMap[] = { + {nsGkAtoms::lighting_color}, {nullptr}}; + +// PresentationAttributes-mask +/* static */ +const Element::MappedAttributeEntry SVGElement::sMaskMap[] = { + {nsGkAtoms::mask_type}, {nullptr}}; + +//---------------------------------------------------------------------- +// Element methods + +// forwarded to Element implementations + +//---------------------------------------------------------------------- + +SVGSVGElement* SVGElement::GetOwnerSVGElement() { + nsIContent* ancestor = GetFlattenedTreeParent(); + + while (ancestor && ancestor->IsSVGElement()) { + if (ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + return nullptr; + } + if (ancestor->IsSVGElement(nsGkAtoms::svg)) { + return static_cast<SVGSVGElement*>(ancestor); + } + ancestor = ancestor->GetFlattenedTreeParent(); + } + + // we don't have an ancestor <svg> element... + return nullptr; +} + +SVGElement* SVGElement::GetViewportElement() { + return SVGContentUtils::GetNearestViewportElement(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGElement::ClassName() { + return mClassAttribute.ToDOMAnimatedString(this); +} + +/* static */ +bool SVGElement::UpdateDeclarationBlockFromLength( + DeclarationBlock& aBlock, nsCSSPropertyID aPropId, + const SVGAnimatedLength& aLength, ValToUse aValToUse) { + aBlock.AssertMutable(); + + float value; + if (aValToUse == ValToUse::Anim) { + value = aLength.GetAnimValInSpecifiedUnits(); + } else { + MOZ_ASSERT(aValToUse == ValToUse::Base); + value = aLength.GetBaseValInSpecifiedUnits(); + } + + // SVG parser doesn't check non-negativity of some parsed value, + // we should not pass those to CSS side. + if (value < 0 && + SVGGeometryProperty::IsNonNegativeGeometryProperty(aPropId)) { + return false; + } + + nsCSSUnit cssUnit = SVGGeometryProperty::SpecifiedUnitTypeToCSSUnit( + aLength.GetSpecifiedUnitType()); + + if (cssUnit == eCSSUnit_Percent) { + Servo_DeclarationBlock_SetPercentValue(aBlock.Raw(), aPropId, + value / 100.f); + } else { + Servo_DeclarationBlock_SetLengthValue(aBlock.Raw(), aPropId, value, + cssUnit); + } + + return true; +} + +/* static */ +bool SVGElement::UpdateDeclarationBlockFromPath( + DeclarationBlock& aBlock, const SVGAnimatedPathSegList& aPath, + ValToUse aValToUse) { + aBlock.AssertMutable(); + + const SVGPathData& pathData = + aValToUse == ValToUse::Anim ? aPath.GetAnimValue() : aPath.GetBaseValue(); + + // SVGPathData::mData is fallible but rust binding accepts nsTArray only, so + // we need to point to one or the other. Fortunately, fallible and infallible + // array types can be implicitly converted provided they are const. + // + // FIXME: here we just convert the data structure from cpp verion into rust + // version. We don't do any normalization for the path data from d attribute. + // Based on the current discussion of https://github.com/w3c/svgwg/issues/321, + // we may have to convert the relative commands into absolute commands. + // The normalization should be fixed in Bug 1489392. Besides, Bug 1714238 + // will use the same data structure, so we may simplify this more. + const nsTArray<float>& asInFallibleArray = pathData.RawData(); + Servo_DeclarationBlock_SetPathValue(aBlock.Raw(), eCSSProperty_d, + &asInFallibleArray); + return true; +} + +//------------------------------------------------------------------------ +// Helper class: MappedAttrParser, for parsing values of mapped attributes + +namespace { + +class MOZ_STACK_CLASS MappedAttrParser { + public: + explicit MappedAttrParser(SVGElement& aElement) : mElement(aElement) {} + ~MappedAttrParser() { + MOZ_ASSERT(!mDecl, + "If mDecl was initialized, it should have been returned via " + "TakeDeclarationBlock (and have its pointer cleared)"); + }; + + // Parses a mapped attribute value. + void ParseMappedAttrValue(nsAtom* aMappedAttrName, + const nsAString& aMappedAttrValue); + + void TellStyleAlreadyParsedResult(nsAtom const* aAtom, + SVGAnimatedLength const& aLength); + void TellStyleAlreadyParsedResult(const SVGAnimatedPathSegList& aPath); + + // If we've parsed any values for mapped attributes, this method returns the + // already_AddRefed css::Declaration that incorporates the parsed + // values. Otherwise, this method returns null. + already_AddRefed<DeclarationBlock> TakeDeclarationBlock() { + return mDecl.forget(); + } + + DeclarationBlock& EnsureDeclarationBlock() { + if (!mDecl) { + mDecl = new DeclarationBlock(); + } + return *mDecl; + } + + URLExtraData& EnsureExtraData() { + if (!mExtraData) { + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForSVGResources(mElement.OwnerDoc()); + mExtraData = MakeRefPtr<URLExtraData>(mElement.GetBaseURI(), referrerInfo, + mElement.NodePrincipal()); + } + return *mExtraData; + } + + private: + // Declaration for storing parsed values (lazily initialized). + RefPtr<DeclarationBlock> mDecl; + + // URL data for parsing stuff. Also lazy. + RefPtr<URLExtraData> mExtraData; + + // For reporting use counters + SVGElement& mElement; +}; + +void MappedAttrParser::ParseMappedAttrValue(nsAtom* aMappedAttrName, + const nsAString& aMappedAttrValue) { + // Get the nsCSSPropertyID ID for our mapped attribute. + nsCSSPropertyID propertyID = + nsCSSProps::LookupProperty(nsAtomCString(aMappedAttrName)); + if (propertyID != eCSSProperty_UNKNOWN) { + bool changed = false; // outparam for ParseProperty. + NS_ConvertUTF16toUTF8 value(aMappedAttrValue); + + auto* doc = mElement.OwnerDoc(); + changed = Servo_DeclarationBlock_SetPropertyById( + EnsureDeclarationBlock().Raw(), propertyID, &value, false, + &EnsureExtraData(), ParsingMode::AllowUnitlessLength, + doc->GetCompatibilityMode(), doc->CSSLoader(), StyleCssRuleType::Style, + {}); + + // TODO(emilio): If we want to record these from CSSOM more generally, we + // can pass the document use counters down the FFI call. For now manually + // count them. + if (changed && StaticPrefs::layout_css_use_counters_enabled()) { + UseCounter useCounter = nsCSSProps::UseCounterFor(propertyID); + MOZ_ASSERT(useCounter != eUseCounter_UNKNOWN); + doc->SetUseCounter(useCounter); + } + return; + } + MOZ_ASSERT(aMappedAttrName == nsGkAtoms::lang, + "Only 'lang' should be unrecognized!"); + // CSS parser doesn't know about 'lang', so we need to handle it specially. + if (aMappedAttrName == nsGkAtoms::lang) { + propertyID = eCSSProperty__x_lang; + RefPtr<nsAtom> atom = NS_Atomize(aMappedAttrValue); + Servo_DeclarationBlock_SetIdentStringValue(EnsureDeclarationBlock().Raw(), + propertyID, atom); + } +} + +void MappedAttrParser::TellStyleAlreadyParsedResult( + nsAtom const* aAtom, SVGAnimatedLength const& aLength) { + nsCSSPropertyID propertyID = nsCSSProps::LookupProperty(nsAtomCString(aAtom)); + SVGElement::UpdateDeclarationBlockFromLength(EnsureDeclarationBlock(), + propertyID, aLength, + SVGElement::ValToUse::Base); +} + +void MappedAttrParser::TellStyleAlreadyParsedResult( + const SVGAnimatedPathSegList& aPath) { + SVGElement::UpdateDeclarationBlockFromPath(EnsureDeclarationBlock(), aPath, + SVGElement::ValToUse::Base); +} + +} // namespace + +//---------------------------------------------------------------------- +// Implementation Helpers: + +void SVGElement::UpdateContentDeclarationBlock() { + MOZ_ASSERT(!mContentDeclarationBlock, + "we already have a content declaration block"); + + MappedAttrParser mappedAttrParser(*this); + + bool lengthAffectsStyle = + SVGGeometryProperty::ElementMapsLengthsToStyle(this); + + uint32_t i = 0; + while (BorrowedAttrInfo info = GetAttrInfoAt(i++)) { + const nsAttrName* attrName = info.mName; + if (!attrName->IsAtom() || !IsAttributeMapped(attrName->Atom())) { + continue; + } + + // FIXME(emilio): This check is dead, since IsAtom() implies that + // NamespaceID() == None. + if (attrName->NamespaceID() != kNameSpaceID_None && + !attrName->Equals(nsGkAtoms::lang, kNameSpaceID_XML)) { + continue; + } + + if (attrName->Equals(nsGkAtoms::lang, kNameSpaceID_None) && + HasAttr(kNameSpaceID_XML, nsGkAtoms::lang)) { + // xml:lang has precedence, and will get set via Gecko_GetXMLLangValue(). + continue; + } + + if (lengthAffectsStyle) { + auto const* length = GetAnimatedLength(attrName->Atom()); + + if (length && length->HasBaseVal()) { + // This is an element with geometry property set via SVG attribute, + // and the attribute is already successfully parsed. We want to go + // through the optimized path to tell the style system the result + // directly, rather than let it parse the same thing again. + mappedAttrParser.TellStyleAlreadyParsedResult(attrName->Atom(), + *length); + continue; + } + } + + if (attrName->Equals(nsGkAtoms::d, kNameSpaceID_None)) { + const auto* path = GetAnimPathSegList(); + // Note: Only SVGPathElement has d attribute. + MOZ_ASSERT( + path, + "SVGPathElement should have the non-null SVGAnimatedPathSegList"); + // The attribute should have been already successfully parsed. + // We want to go through the optimized path to tell the style system + // the result directly, rather than let it parse the same thing again. + mappedAttrParser.TellStyleAlreadyParsedResult(*path); + // Some other notes: + // The syntax of CSS d property is different from SVG d attribute. + // 1. CSS d proeprty accepts: none | path(<quoted string>); + // 2. SVG d attribtue accepts: none | <string> + // So we cannot use css parser to parse the SVG d attribute directly. + // Besides, |mAttrs.AttrAt(i)| removes the quotes already, so the svg path + // in |mAttrs.AttrAt(i)| would be something like `M0,0L1,1z` without the + // quotes. So css tokenizer cannot recognize this as a quoted string, and + // so svg_path::SVGPathData::parse() doesn't work for this. Fortunately, + // we still can rely on the parsed result from + // SVGElement::ParseAttribute() for d attribute. + continue; + } + + nsAutoString value; + info.mValue->ToString(value); + mappedAttrParser.ParseMappedAttrValue(attrName->Atom(), value); + } + mContentDeclarationBlock = mappedAttrParser.TakeDeclarationBlock(); +} + +const DeclarationBlock* SVGElement::GetContentDeclarationBlock() const { + return mContentDeclarationBlock; +} + +/** + * Helper methods for the type-specific WillChangeXXX methods. + * + * This method sends out appropriate pre-change notifications so that selector + * restyles (e.g. due to changes that cause |elem[attr="val"]| to start/stop + * matching) work, and it returns an nsAttrValue that _may_ contain the + * attribute's pre-change value. + * + * The nsAttrValue returned by this method depends on whether there are + * mutation event listeners listening for changes to this element's attributes. + * If not, then the object returned is empty. If there are, then the + * nsAttrValue returned contains a serialized copy of the attribute's value + * prior to the change, and this object should be passed to the corresponding + * DidChangeXXX method call (assuming a WillChangeXXX call is required for the + * SVG type - see comment below). This is necessary so that the 'prevValue' + * property of the mutation event that is dispatched will correctly contain the + * old value. + * + * The reason we need to serialize the old value if there are mutation + * event listeners is because the underlying nsAttrValue for the attribute + * points directly to a parsed representation of the attribute (e.g. an + * SVGAnimatedLengthList*) that is a member of the SVG element. That object + * will have changed by the time DidChangeXXX has been called, so without the + * serialization of the old attribute value that we provide, DidChangeXXX + * would have no way to get the old value to pass to SetAttrAndNotify. + * + * We only return the old value when there are mutation event listeners because + * it's not needed otherwise, and because it's expensive to serialize the old + * value. This is especially true for list type attributes, which may be built + * up via the SVG DOM resulting in a large number of Will/DidModifyXXX calls + * before the script finally finishes setting the attribute. + * + * Note that unlike using SetParsedAttr, using Will/DidChangeXXX does NOT check + * and filter out redundant changes. Before calling WillChangeXXX, the caller + * should check whether the new and old values are actually the same, and skip + * calling Will/DidChangeXXX if they are. + * + * Also note that not all SVG types use this scheme. For types that can be + * represented by an nsAttrValue without pointing back to an SVG object (e.g. + * enums, booleans, integers) we can simply use SetParsedAttr which will do all + * of the above for us. For such types there is no matching WillChangeXXX + * method, only DidChangeXXX which calls SetParsedAttr. + */ +nsAttrValue SVGElement::WillChangeValue( + nsAtom* aName, const mozAutoDocUpdate& aProofOfUpdate) { + // We need an empty attr value: + // a) to pass to BeforeSetAttr when GetParsedAttr returns nullptr + // b) to store the old value in the case we have mutation listeners + // + // We can use the same value for both purposes, because if GetParsedAttr + // returns non-null its return value is what will get passed to BeforeSetAttr, + // not matter what our mutation listener situation is. + // + // Also, we should be careful to always return this value to benefit from + // return value optimization. + nsAttrValue emptyOrOldAttrValue; + const nsAttrValue* attrValue = GetParsedAttr(aName); + + // We only need to set the old value if we have listeners since otherwise it + // isn't used. + if (attrValue && nsContentUtils::HasMutationListeners( + this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this)) { + emptyOrOldAttrValue.SetToSerialized(*attrValue); + } + + uint8_t modType = + attrValue ? static_cast<uint8_t>(MutationEvent_Binding::MODIFICATION) + : static_cast<uint8_t>(MutationEvent_Binding::ADDITION); + MutationObservers::NotifyAttributeWillChange(this, kNameSpaceID_None, aName, + modType); + + // This is not strictly correct--the attribute value parameter for + // BeforeSetAttr should reflect the value that *will* be set but that implies + // allocating, e.g. an extra SVGAnimatedLength, and isn't necessary at the + // moment since no SVG elements overload BeforeSetAttr. For now we just pass + // the current value. + nsAttrValueOrString attrStringOrValue(attrValue ? *attrValue + : emptyOrOldAttrValue); + DebugOnly<nsresult> rv = BeforeSetAttr( + kNameSpaceID_None, aName, &attrStringOrValue, kNotifyDocumentObservers); + // SVG elements aren't expected to overload BeforeSetAttr in such a way that + // it may fail. So long as this is the case we don't need to check and pass on + // the return value which simplifies the calling code significantly. + MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected failure from BeforeSetAttr"); + + return emptyOrOldAttrValue; +} + +/** + * Helper methods for the type-specific DidChangeXXX methods. + * + * aEmptyOrOldValue will normally be the object returned from the corresponding + * WillChangeXXX call. This is because: + * a) WillChangeXXX will ensure the object is set when we have mutation + * listeners, and + * b) WillChangeXXX will ensure the object represents a serialized version of + * the old attribute value so that the value doesn't change when the + * underlying SVG type is updated. + * + * aNewValue is replaced with the old value. + */ +void SVGElement::DidChangeValue(nsAtom* aName, + const nsAttrValue& aEmptyOrOldValue, + nsAttrValue& aNewValue, + const mozAutoDocUpdate& aProofOfUpdate) { + bool hasListeners = nsContentUtils::HasMutationListeners( + this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this); + uint8_t modType = + HasAttr(kNameSpaceID_None, aName) + ? static_cast<uint8_t>(MutationEvent_Binding::MODIFICATION) + : static_cast<uint8_t>(MutationEvent_Binding::ADDITION); + + // XXX Really, the fourth argument to SetAttrAndNotify should be null if + // aEmptyOrOldValue does not represent the actual previous value of the + // attribute, but currently SVG elements do not even use the old attribute + // value in |AfterSetAttr|, so this should be ok. + SetAttrAndNotify(kNameSpaceID_None, aName, nullptr, &aEmptyOrOldValue, + aNewValue, nullptr, modType, hasListeners, + kNotifyDocumentObservers, kCallAfterSetAttr, + GetComposedDoc(), aProofOfUpdate); +} + +void SVGElement::MaybeSerializeAttrBeforeRemoval(nsAtom* aName, bool aNotify) { + if (!aNotify || !nsContentUtils::HasMutationListeners( + this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this)) { + return; + } + + const nsAttrValue* attrValue = mAttrs.GetAttr(aName); + if (!attrValue) return; + + nsAutoString serializedValue; + attrValue->ToString(serializedValue); + nsAttrValue oldAttrValue(serializedValue); + bool oldValueSet; + mAttrs.SetAndSwapAttr(aName, oldAttrValue, &oldValueSet); +} + +nsAtom* SVGElement::GetEventNameForAttr(nsAtom* aAttr) { + if (IsSVGElement(nsGkAtoms::svg)) { + if (aAttr == nsGkAtoms::onload) return nsGkAtoms::onSVGLoad; + if (aAttr == nsGkAtoms::onscroll) return nsGkAtoms::onSVGScroll; + } + if (aAttr == nsGkAtoms::onbegin) return nsGkAtoms::onbeginEvent; + if (aAttr == nsGkAtoms::onrepeat) return nsGkAtoms::onrepeatEvent; + if (aAttr == nsGkAtoms::onend) return nsGkAtoms::onendEvent; + + return SVGElementBase::GetEventNameForAttr(aAttr); +} + +SVGViewportElement* SVGElement::GetCtx() const { + return SVGContentUtils::GetNearestViewportElement(this); +} + +/* virtual */ +gfxMatrix SVGElement::PrependLocalTransformsTo(const gfxMatrix& aMatrix, + SVGTransformTypes aWhich) const { + return aMatrix; +} + +SVGElement::LengthAttributesInfo SVGElement::GetLengthInfo() { + return LengthAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::SetLength(nsAtom* aName, const SVGAnimatedLength& aLength) { + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lengthInfo.mCount; i++) { + if (aName == lengthInfo.mInfos[i].mName) { + lengthInfo.mValues[i] = aLength; + DidAnimateLength(i); + return; + } + } + MOZ_ASSERT(false, "no length found to set"); +} + +nsAttrValue SVGElement::WillChangeLength( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetLengthInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeLength(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + LengthAttributesInfo info = GetLengthInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeLength on element with no length attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateLength(uint8_t aAttrEnum) { + // We need to do this here. Normally the SMIL restyle would also cause us to + // do this from DidSetComputedStyle, but we don't have that guarantee if our + // frame gets reconstructed. + ClearAnyCachedPath(); + + if (SVGGeometryProperty::ElementMapsLengthsToStyle(this)) { + nsCSSPropertyID propId = + SVGGeometryProperty::AttrEnumToCSSPropId(this, aAttrEnum); + + // We don't map use element width/height currently. We can remove this + // test when we do. + if (propId != eCSSProperty_UNKNOWN) { + SMILOverrideStyle()->SetSMILValue(propId, + GetLengthInfo().mValues[aAttrEnum]); + return; + } + } + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + LengthAttributesInfo info = GetLengthInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedLength* SVGElement::GetAnimatedLength(uint8_t aAttrEnum) { + LengthAttributesInfo info = GetLengthInfo(); + if (aAttrEnum < info.mCount) { + return &info.mValues[aAttrEnum]; + } + MOZ_ASSERT_UNREACHABLE("Bad attrEnum"); + return nullptr; +} + +SVGAnimatedLength* SVGElement::GetAnimatedLength(const nsAtom* aAttrName) { + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lengthInfo.mCount; i++) { + if (aAttrName == lengthInfo.mInfos[i].mName) { + return &lengthInfo.mValues[i]; + } + } + return nullptr; +} + +void SVGElement::GetAnimatedLengthValues(float* aFirst, ...) { + LengthAttributesInfo info = GetLengthInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetAnimatedLengthValues on element with no length attribs"); + + SVGViewportElement* ctx = nullptr; + + float* f = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (f && i < info.mCount) { + uint8_t type = info.mValues[i].GetSpecifiedUnitType(); + if (!ctx) { + if (type != SVGLength_Binding::SVG_LENGTHTYPE_NUMBER && + type != SVGLength_Binding::SVG_LENGTHTYPE_PX) + ctx = GetCtx(); + } + if (type == SVGLength_Binding::SVG_LENGTHTYPE_EMS || + type == SVGLength_Binding::SVG_LENGTHTYPE_EXS) + *f = info.mValues[i++].GetAnimValue(this); + else + *f = info.mValues[i++].GetAnimValue(ctx); + f = va_arg(args, float*); + } + + va_end(args); +} + +SVGElement::LengthListAttributesInfo SVGElement::GetLengthListInfo() { + return LengthListAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeLengthList( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetLengthListInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeLengthList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + LengthListAttributesInfo info = GetLengthListInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeLengthList on element with no length list attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateLengthList(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + LengthListAttributesInfo info = GetLengthListInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +void SVGElement::GetAnimatedLengthListValues(SVGUserUnitList* aFirst, ...) { + LengthListAttributesInfo info = GetLengthListInfo(); + + NS_ASSERTION( + info.mCount > 0, + "GetAnimatedLengthListValues on element with no length list attribs"); + + SVGUserUnitList* list = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (list && i < info.mCount) { + list->Init(&(info.mValues[i].GetAnimValue()), this, info.mInfos[i].mAxis); + ++i; + list = va_arg(args, SVGUserUnitList*); + } + + va_end(args); +} + +SVGAnimatedLengthList* SVGElement::GetAnimatedLengthList(uint8_t aAttrEnum) { + LengthListAttributesInfo info = GetLengthListInfo(); + if (aAttrEnum < info.mCount) { + return &(info.mValues[aAttrEnum]); + } + MOZ_ASSERT_UNREACHABLE("Bad attrEnum"); + return nullptr; +} + +SVGElement::NumberListAttributesInfo SVGElement::GetNumberListInfo() { + return NumberListAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeNumberList( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetNumberListInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeNumberList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + NumberListAttributesInfo info = GetNumberListInfo(); + + MOZ_ASSERT(info.mCount > 0, + "DidChangeNumberList on element with no number list attribs"); + MOZ_ASSERT(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateNumberList(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberListAttributesInfo info = GetNumberListInfo(); + MOZ_ASSERT(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedNumberList* SVGElement::GetAnimatedNumberList(uint8_t aAttrEnum) { + NumberListAttributesInfo info = GetNumberListInfo(); + if (aAttrEnum < info.mCount) { + return &(info.mValues[aAttrEnum]); + } + MOZ_ASSERT(false, "Bad attrEnum"); + return nullptr; +} + +SVGAnimatedNumberList* SVGElement::GetAnimatedNumberList(nsAtom* aAttrName) { + NumberListAttributesInfo info = GetNumberListInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aAttrName == info.mInfos[i].mName) { + return &info.mValues[i]; + } + } + MOZ_ASSERT(false, "Bad caller"); + return nullptr; +} + +nsAttrValue SVGElement::WillChangePointList( + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPointListAttrName(), "Changing non-existent point list?"); + return WillChangeValue(GetPointListAttrName(), aProofOfUpdate); +} + +void SVGElement::DidChangePointList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPointListAttrName(), "Changing non-existent point list?"); + + nsAttrValue newValue; + newValue.SetTo(GetAnimatedPointList()->GetBaseValue(), nullptr); + + DidChangeValue(GetPointListAttrName(), aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimatePointList() { + MOZ_ASSERT(GetPointListAttrName(), "Animating non-existent path data?"); + + ClearAnyCachedPath(); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, GetPointListAttrName(), + MutationEvent_Binding::SMIL); + } +} + +nsAttrValue SVGElement::WillChangePathSegList( + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPathDataAttrName(), "Changing non-existent path seg list?"); + return WillChangeValue(GetPathDataAttrName(), aProofOfUpdate); +} + +void SVGElement::DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPathDataAttrName(), "Changing non-existent path seg list?"); + + nsAttrValue newValue; + newValue.SetTo(GetAnimPathSegList()->GetBaseValue(), nullptr); + + DidChangeValue(GetPathDataAttrName(), aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimatePathSegList() { + nsStaticAtom* name = GetPathDataAttrName(); + MOZ_ASSERT(name, "Animating non-existent path data?"); + + ClearAnyCachedPath(); + + // Notify style we have to update the d property because of SMIL animation. + if (StaticPrefs::layout_css_d_property_enabled() && name == nsGkAtoms::d) { + SMILOverrideStyle()->SetSMILValue(nsCSSPropertyID::eCSSProperty_d, + *GetAnimPathSegList()); + return; + } + + if (nsIFrame* frame = GetPrimaryFrame()) { + frame->AttributeChanged(kNameSpaceID_None, name, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::NumberAttributesInfo SVGElement::GetNumberInfo() { + return NumberAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeNumber(uint8_t aAttrEnum) { + NumberAttributesInfo info = GetNumberInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeNumber on element with no number attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue; + attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateNumber(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberAttributesInfo info = GetNumberInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +void SVGElement::GetAnimatedNumberValues(float* aFirst, ...) { + NumberAttributesInfo info = GetNumberInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetAnimatedNumberValues on element with no number attribs"); + + float* f = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (f && i < info.mCount) { + *f = info.mValues[i++].GetAnimValue(); + f = va_arg(args, float*); + } + va_end(args); +} + +SVGElement::NumberPairAttributesInfo SVGElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeNumberPair(uint8_t aAttrEnum) { + mozAutoDocUpdate updateBatch(GetComposedDoc(), kDontNotifyDocumentObservers); + return WillChangeValue(GetNumberPairInfo().mInfos[aAttrEnum].mName, + updateBatch); +} + +void SVGElement::DidChangeNumberPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) { + NumberPairAttributesInfo info = GetNumberPairInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangePairNumber on element with no number pair attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + + mozAutoDocUpdate updateBatch(GetComposedDoc(), kNotifyDocumentObservers); + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + updateBatch); +} + +void SVGElement::DidAnimateNumberPair(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberPairAttributesInfo info = GetNumberPairInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::IntegerAttributesInfo SVGElement::GetIntegerInfo() { + return IntegerAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeInteger(uint8_t aAttrEnum) { + IntegerAttributesInfo info = GetIntegerInfo(); + NS_ASSERTION(info.mCount > 0, + "DidChangeInteger on element with no integer attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue; + attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateInteger(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + IntegerAttributesInfo info = GetIntegerInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +void SVGElement::GetAnimatedIntegerValues(int32_t* aFirst, ...) { + IntegerAttributesInfo info = GetIntegerInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetAnimatedIntegerValues on element with no integer attribs"); + + int32_t* n = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (n && i < info.mCount) { + *n = info.mValues[i++].GetAnimValue(); + n = va_arg(args, int32_t*); + } + va_end(args); +} + +SVGElement::IntegerPairAttributesInfo SVGElement::GetIntegerPairInfo() { + return IntegerPairAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeIntegerPair( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetIntegerPairInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeIntegerPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeIntegerPair on element with no integer pair attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateIntegerPair(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::BooleanAttributesInfo SVGElement::GetBooleanInfo() { + return BooleanAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeBoolean(uint8_t aAttrEnum) { + BooleanAttributesInfo info = GetBooleanInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeBoolean on element with no boolean attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom()); + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateBoolean(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + BooleanAttributesInfo info = GetBooleanInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::EnumAttributesInfo SVGElement::GetEnumInfo() { + return EnumAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeEnum(uint8_t aAttrEnum) { + EnumAttributesInfo info = GetEnumInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeEnum on element with no enum attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom(this)); + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateEnum(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + EnumAttributesInfo info = GetEnumInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedOrient* SVGElement::GetAnimatedOrient() { return nullptr; } + +nsAttrValue SVGElement::WillChangeOrient( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(nsGkAtoms::orient, aProofOfUpdate); +} + +void SVGElement::DidChangeOrient(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + + NS_ASSERTION(orient, "DidChangeOrient on element with no orient attrib"); + + nsAttrValue newValue; + newValue.SetTo(*orient, nullptr); + + DidChangeValue(nsGkAtoms::orient, aEmptyOrOldValue, newValue, aProofOfUpdate); +} + +void SVGElement::DidAnimateOrient() { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::orient, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedViewBox* SVGElement::GetAnimatedViewBox() { return nullptr; } + +nsAttrValue SVGElement::WillChangeViewBox( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(nsGkAtoms::viewBox, aProofOfUpdate); +} + +void SVGElement::DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + + NS_ASSERTION(viewBox, "DidChangeViewBox on element with no viewBox attrib"); + + nsAttrValue newValue; + newValue.SetTo(*viewBox, nullptr); + + DidChangeValue(nsGkAtoms::viewBox, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateViewBox() { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::viewBox, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedPreserveAspectRatio* SVGElement::GetAnimatedPreserveAspectRatio() { + return nullptr; +} + +nsAttrValue SVGElement::WillChangePreserveAspectRatio( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(nsGkAtoms::preserveAspectRatio, aProofOfUpdate); +} + +void SVGElement::DidChangePreserveAspectRatio( + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + + NS_ASSERTION(preserveAspectRatio, + "DidChangePreserveAspectRatio on element with no " + "preserveAspectRatio attrib"); + + nsAttrValue newValue; + newValue.SetTo(*preserveAspectRatio, nullptr); + + DidChangeValue(nsGkAtoms::preserveAspectRatio, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimatePreserveAspectRatio() { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::preserveAspectRatio, + MutationEvent_Binding::SMIL); + } +} + +nsAttrValue SVGElement::WillChangeTransformList( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetTransformListAttrName(), aProofOfUpdate); +} + +void SVGElement::DidChangeTransformList( + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetTransformListAttrName(), + "Changing non-existent transform list?"); + + // The transform attribute is being set, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + nsAttrValue newValue; + newValue.SetTo(GetAnimatedTransformList(DO_ALLOCATE)->GetBaseValue(), + nullptr); + + DidChangeValue(GetTransformListAttrName(), aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateTransformList(int32_t aModType) { + MOZ_ASSERT(GetTransformListAttrName(), + "Animating non-existent transform data?"); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + nsAtom* transformAttr = GetTransformListAttrName(); + frame->AttributeChanged(kNameSpaceID_None, transformAttr, aModType); + // When script changes the 'transform' attribute, Element::SetAttrAndNotify + // will call MutationObservers::NotifyAttributeChanged, under which + // SVGTransformableElement::GetAttributeChangeHint will be called and an + // appropriate change event posted to update our frame's overflow rects. + // The SetAttrAndNotify doesn't happen for transform changes caused by + // 'animateTransform' though (and sending out the mutation events that + // MutationObservers::NotifyAttributeChanged dispatches would be + // inappropriate anyway), so we need to post the change event ourself. + nsChangeHint changeHint = GetAttributeChangeHint(transformAttr, aModType); + if (changeHint) { + nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, changeHint); + } + } +} + +SVGElement::StringAttributesInfo SVGElement::GetStringInfo() { + return StringAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::GetStringBaseValue(uint8_t aAttrEnum, + nsAString& aResult) const { + SVGElement::StringAttributesInfo info = + const_cast<SVGElement*>(this)->GetStringInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetBaseValue on element with no string attribs"); + + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + GetAttr(info.mInfos[aAttrEnum].mNamespaceID, info.mInfos[aAttrEnum].mName, + aResult); +} + +void SVGElement::SetStringBaseValue(uint8_t aAttrEnum, + const nsAString& aValue) { + SVGElement::StringAttributesInfo info = GetStringInfo(); + + NS_ASSERTION(info.mCount > 0, + "SetBaseValue on element with no string attribs"); + + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + SetAttr(info.mInfos[aAttrEnum].mNamespaceID, info.mInfos[aAttrEnum].mName, + aValue, true); +} + +void SVGElement::DidAnimateString(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + StringAttributesInfo info = GetStringInfo(); + frame->AttributeChanged(info.mInfos[aAttrEnum].mNamespaceID, + info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::StringListAttributesInfo SVGElement::GetStringListInfo() { + return StringListAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeStringList( + bool aIsConditionalProcessingAttribute, uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate) { + nsStaticAtom* name; + if (aIsConditionalProcessingAttribute) { + nsCOMPtr<SVGTests> tests(do_QueryInterface(this)); + name = tests->GetAttrName(aAttrEnum); + } else { + name = GetStringListInfo().mInfos[aAttrEnum].mName; + } + return WillChangeValue(name, aProofOfUpdate); +} + +void SVGElement::DidChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + nsStaticAtom* name; + nsAttrValue newValue; + nsCOMPtr<SVGTests> tests; + + if (aIsConditionalProcessingAttribute) { + tests = do_QueryObject(this); + name = tests->GetAttrName(aAttrEnum); + tests->GetAttrValue(aAttrEnum, newValue); + } else { + StringListAttributesInfo info = GetStringListInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeStringList on element with no string list attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + name = info.mInfos[aAttrEnum].mName; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + } + + DidChangeValue(name, aEmptyOrOldValue, newValue, aProofOfUpdate); + + if (aIsConditionalProcessingAttribute) { + tests->MaybeInvalidate(); + } +} + +nsresult SVGElement::ReportAttributeParseFailure(Document* aDocument, + nsAtom* aAttribute, + const nsAString& aValue) { + AutoTArray<nsString, 2> strings; + strings.AppendElement(nsDependentAtomString(aAttribute)); + strings.AppendElement(aValue); + return SVGContentUtils::ReportToConsole(aDocument, "AttributeParseWarning", + strings); +} + +UniquePtr<SMILAttr> SVGElement::GetAnimatedAttr(int32_t aNamespaceID, + nsAtom* aName) { + if (aNamespaceID == kNameSpaceID_None) { + // Transforms: + if (GetTransformListAttrName() == aName) { + // The transform attribute is being animated, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + return GetAnimatedTransformList(DO_ALLOCATE)->ToSMILAttr(this); + } + + // Motion (fake 'attribute' for animateMotion) + if (aName == nsGkAtoms::mozAnimateMotionDummyAttr) { + return MakeUnique<SVGMotionSMILAttr>(this); + } + + // Lengths: + LengthAttributesInfo info = GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + + // Numbers: + { + NumberAttributesInfo info = GetNumberInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Number Pairs: + { + NumberPairAttributesInfo info = GetNumberPairInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Integers: + { + IntegerAttributesInfo info = GetIntegerInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Integer Pairs: + { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Enumerations: + { + EnumAttributesInfo info = GetEnumInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Booleans: + { + BooleanAttributesInfo info = GetBooleanInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // orient: + if (aName == nsGkAtoms::orient) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + return orient ? orient->ToSMILAttr(this) : nullptr; + } + + // viewBox: + if (aName == nsGkAtoms::viewBox) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + return viewBox ? viewBox->ToSMILAttr(this) : nullptr; + } + + // preserveAspectRatio: + if (aName == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + return preserveAspectRatio ? preserveAspectRatio->ToSMILAttr(this) + : nullptr; + } + + // NumberLists: + { + NumberListAttributesInfo info = GetNumberListInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); + return info.mValues[i].ToSMILAttr(this, uint8_t(i)); + } + } + } + + // LengthLists: + { + LengthListAttributesInfo info = GetLengthListInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); + return info.mValues[i].ToSMILAttr(this, uint8_t(i), + info.mInfos[i].mAxis, + info.mInfos[i].mCouldZeroPadList); + } + } + } + + // PointLists: + { + if (GetPointListAttrName() == aName) { + SVGAnimatedPointList* pointList = GetAnimatedPointList(); + if (pointList) { + return pointList->ToSMILAttr(this); + } + } + } + + // PathSegLists: + { + if (GetPathDataAttrName() == aName) { + SVGAnimatedPathSegList* segList = GetAnimPathSegList(); + if (segList) { + return segList->ToSMILAttr(this); + } + } + } + + if (aName == nsGkAtoms::_class) { + return mClassAttribute.ToSMILAttr(this); + } + } + + // Strings + { + StringAttributesInfo info = GetStringInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aNamespaceID == info.mInfos[i].mNamespaceID && + aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + return nullptr; +} + +void SVGElement::AnimationNeedsResample() { + Document* doc = GetComposedDoc(); + if (doc && doc->HasAnimationController()) { + doc->GetAnimationController()->SetResampleNeeded(); + } +} + +void SVGElement::FlushAnimations() { + Document* doc = GetComposedDoc(); + if (doc && doc->HasAnimationController()) { + doc->GetAnimationController()->FlushResampleRequests(); + } +} + +void SVGElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, + size_t* aNodeSize) const { + Element::AddSizeOfExcludingThis(aSizes, aNodeSize); + + // These are owned by the element and not referenced from the stylesheets. + // They're referenced from the rule tree, but the rule nodes don't measure + // their style source (since they're non-owning), so unconditionally reporting + // them even though it's a refcounted object is ok. + if (mContentDeclarationBlock) { + aSizes.mLayoutSvgMappedDeclarations += + mContentDeclarationBlock->SizeofIncludingThis( + aSizes.mState.mMallocSizeOf); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGElement.h b/dom/svg/SVGElement.h new file mode 100644 index 0000000000..b373113da9 --- /dev/null +++ b/dom/svg/SVGElement.h @@ -0,0 +1,606 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGELEMENT_H_ +#define DOM_SVG_SVGELEMENT_H_ + +/* + SVGElement is the base class for all SVG content elements. + It implements all the common DOM interfaces and handles attributes. +*/ + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGAnimatedClass.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/Element.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/UniquePtr.h" +#include "nsCSSPropertyID.h" +#include "nsChangeHint.h" +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "nsISupportsImpl.h" +#include "nsStyledElement.h" +#include "gfxMatrix.h" + +// {70db954d-e452-4be3-83aa-f54a51cf7890} +#define MOZILLA_SVGELEMENT_IID \ + { \ + 0x70db954d, 0xe452, 0x4be3, { \ + 0x82, 0xaa, 0xf5, 0x4a, 0x51, 0xcf, 0x78, 0x90 \ + } \ + } + +nsresult NS_NewSVGElement(mozilla::dom::Element** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +class mozAutoDocUpdate; + +namespace mozilla { +class DeclarationBlock; + +class SVGAnimatedBoolean; +class SVGAnimatedEnumeration; +class SVGAnimatedInteger; +class SVGAnimatedIntegerPair; +class SVGAnimatedLength; +class SVGAnimatedLengthList; +class SVGAnimatedNumber; +class SVGAnimatedNumberList; +class SVGAnimatedNumberPair; +class SVGAnimatedOrient; +class SVGAnimatedPathSegList; +class SVGAnimatedPointList; +class SVGAnimatedString; +class SVGAnimatedPreserveAspectRatio; +class SVGAnimatedTransformList; +class SVGAnimatedViewBox; +class SVGNumberList; +class SVGStringList; +class SVGUserUnitList; + +struct SVGEnumMapping; + +namespace dom { +class DOMSVGStringList; +class SVGSVGElement; +class SVGViewportElement; + +using SVGElementBase = nsStyledElement; + +class SVGElement : public SVGElementBase // nsIContent +{ + protected: + explicit SVGElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + friend nsresult( + ::NS_NewSVGElement(mozilla::dom::Element** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + nsresult Init(); + virtual ~SVGElement(); + + public: + nsresult Clone(mozilla::dom::NodeInfo*, + nsINode** aResult) const MOZ_MUST_OVERRIDE override; + + // From Element + nsresult CopyInnerTo(mozilla::dom::Element* aDest); + + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_SVGELEMENT_IID) + // nsISupports + NS_INLINE_DECL_REFCOUNTING_INHERITED(SVGElement, SVGElementBase) + + NS_DECL_ADDSIZEOFEXCLUDINGTHIS + + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + + void DidAnimateClass(); + + void SetNonce(const nsAString& aNonce) { + SetProperty(nsGkAtoms::nonce, new nsString(aNonce), + nsINode::DeleteProperty<nsString>); + } + void RemoveNonce() { RemoveProperty(nsGkAtoms::nonce); } + void GetNonce(nsAString& aNonce) const { + nsString* cspNonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + aNonce = *cspNonce; + } + } + + // nsIContent interface methods + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute, + int32_t aModType) const override; + + bool IsNodeOfType(uint32_t aFlags) const override; + virtual bool IsSVGGraphicsElement() const { return false; } + + /** + * We override the default to unschedule computation of Servo declaration + * blocks when adopted across documents. + */ + void NodeInfoChanged(Document* aOldDoc) override; + + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + static const MappedAttributeEntry sFillStrokeMap[]; + static const MappedAttributeEntry sGraphicsMap[]; + static const MappedAttributeEntry sTextContentElementsMap[]; + static const MappedAttributeEntry sFontSpecificationMap[]; + static const MappedAttributeEntry sGradientStopMap[]; + static const MappedAttributeEntry sViewportsMap[]; + static const MappedAttributeEntry sMarkersMap[]; + static const MappedAttributeEntry sColorMap[]; + static const MappedAttributeEntry sFiltersMap[]; + static const MappedAttributeEntry sFEFloodMap[]; + static const MappedAttributeEntry sLightingEffectsMap[]; + static const MappedAttributeEntry sMaskMap[]; + + NS_IMPL_FROMNODE(SVGElement, kNameSpaceID_SVG) + + // Gets the element that establishes the rectangular viewport against which + // we should resolve percentage lengths (our "coordinate context"). Returns + // nullptr for outer <svg> or SVG without an <svg> parent (invalid SVG). + mozilla::dom::SVGViewportElement* GetCtx() const; + + /** + * Returns aMatrix pre-multiplied by (explicit or implicit) transforms that + * are introduced by attributes on this element. + * + * If aWhich is eAllTransforms, then all the transforms from the coordinate + * space established by this element for its children to the coordinate + * space established by this element's parent element for this element, are + * included. + * + * If aWhich is eUserSpaceToParent, then only the transforms from this + * element's userspace to the coordinate space established by its parent is + * included. This includes any transforms introduced by the 'transform' + * attribute, transform animations and animateMotion, but not any offsets + * due to e.g. 'x'/'y' attributes, or any transform due to a 'viewBox' + * attribute. (SVG userspace is defined to be the coordinate space in which + * coordinates on an element apply.) + * + * If aWhich is eChildToUserSpace, then only the transforms from the + * coordinate space established by this element for its childre to this + * elements userspace are included. This includes any offsets due to e.g. + * 'x'/'y' attributes, and any transform due to a 'viewBox' attribute, but + * does not include any transforms due to the 'transform' attribute. + */ + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const; + + // Setter for to set the current <animateMotion> transformation + // Only visible for SVGGraphicElement, so it's a no-op here, and that + // subclass has the useful implementation. + virtual void SetAnimateMotionTransform( + const mozilla::gfx::Matrix* aMatrix) { /*no-op*/ + } + virtual const mozilla::gfx::Matrix* GetAnimateMotionTransform() const { + return nullptr; + } + + bool IsStringAnimatable(uint8_t aAttrEnum) { + return GetStringInfo().mInfos[aAttrEnum].mIsAnimatable; + } + bool NumberAttrAllowsPercentage(uint8_t aAttrEnum) { + return GetNumberInfo().mInfos[aAttrEnum].mPercentagesAllowed; + } + virtual bool HasValidDimensions() const { return true; } + void SetLength(nsAtom* aName, const SVGAnimatedLength& aLength); + + enum class ValToUse { Base, Anim }; + static bool UpdateDeclarationBlockFromLength(DeclarationBlock& aBlock, + nsCSSPropertyID aPropId, + const SVGAnimatedLength& aLength, + ValToUse aValToUse); + static bool UpdateDeclarationBlockFromPath( + DeclarationBlock& aBlock, const SVGAnimatedPathSegList& aPath, + ValToUse aValToUse); + + nsAttrValue WillChangeLength(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeNumberPair(uint8_t aAttrEnum); + nsAttrValue WillChangeIntegerPair(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeOrient(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeViewBox(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangePreserveAspectRatio( + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeNumberList(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeLengthList(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangePointList(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangePathSegList(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeTransformList(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + + void DidChangeLength(uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeNumber(uint8_t aAttrEnum); + void DidChangeNumberPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue); + void DidChangeInteger(uint8_t aAttrEnum); + void DidChangeIntegerPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeBoolean(uint8_t aAttrEnum); + void DidChangeEnum(uint8_t aAttrEnum); + void DidChangeOrient(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangePreserveAspectRatio(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeNumberList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeLengthList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangePointList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeTransformList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeString(uint8_t aAttrEnum) {} + void DidChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + + void DidAnimateLength(uint8_t aAttrEnum); + void DidAnimateNumber(uint8_t aAttrEnum); + void DidAnimateNumberPair(uint8_t aAttrEnum); + void DidAnimateInteger(uint8_t aAttrEnum); + void DidAnimateIntegerPair(uint8_t aAttrEnum); + void DidAnimateBoolean(uint8_t aAttrEnum); + void DidAnimateEnum(uint8_t aAttrEnum); + void DidAnimateOrient(); + void DidAnimateViewBox(); + void DidAnimatePreserveAspectRatio(); + void DidAnimateNumberList(uint8_t aAttrEnum); + void DidAnimateLengthList(uint8_t aAttrEnum); + void DidAnimatePointList(); + void DidAnimatePathSegList(); + void DidAnimateTransformList(int32_t aModType); + void DidAnimateString(uint8_t aAttrEnum); + + enum { + /** + * Flag to indicate to GetAnimatedXxx() methods that the object being + * requested should be allocated if it hasn't already been allocated, and + * that the method should not return null. Only applicable to methods that + * need to allocate the object that they return. + */ + DO_ALLOCATE = 0x1 + }; + + SVGAnimatedLength* GetAnimatedLength(uint8_t aAttrEnum); + SVGAnimatedLength* GetAnimatedLength(const nsAtom* aAttrName); + void GetAnimatedLengthValues(float* aFirst, ...); + void GetAnimatedNumberValues(float* aFirst, ...); + void GetAnimatedIntegerValues(int32_t* aFirst, ...); + SVGAnimatedNumberList* GetAnimatedNumberList(uint8_t aAttrEnum); + SVGAnimatedNumberList* GetAnimatedNumberList(nsAtom* aAttrName); + void GetAnimatedLengthListValues(SVGUserUnitList* aFirst, ...); + SVGAnimatedLengthList* GetAnimatedLengthList(uint8_t aAttrEnum); + virtual SVGAnimatedPointList* GetAnimatedPointList() { return nullptr; } + virtual SVGAnimatedPathSegList* GetAnimPathSegList() { + // DOM interface 'SVGAnimatedPathData' (*inherited* by SVGPathElement) + // has a member called 'animatedPathSegList' member, so we have a shorter + // name so we don't get hidden by the GetAnimatedPathSegList declared by + // NS_DECL_NSIDOMSVGANIMATEDPATHDATA. + return nullptr; + } + /** + * Get the SVGAnimatedTransformList for this element. + * + * Despite the fact that animated transform lists are used for a variety of + * attributes, no SVG element uses more than one. + * + * It's relatively uncommon for elements to have their transform attribute + * set, so to save memory the SVGAnimatedTransformList is not allocated + * until the attribute is set/animated or its DOM wrapper is created. Callers + * that require the SVGAnimatedTransformList to be allocated and for this + * method to return non-null must pass the DO_ALLOCATE flag. + */ + virtual SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) { + return nullptr; + } + + mozilla::UniquePtr<SMILAttr> GetAnimatedAttr(int32_t aNamespaceID, + nsAtom* aName) override; + void AnimationNeedsResample(); + void FlushAnimations(); + + void GetStringBaseValue(uint8_t aAttrEnum, nsAString& aResult) const; + void SetStringBaseValue(uint8_t aAttrEnum, const nsAString& aValue); + + virtual nsStaticAtom* GetPointListAttrName() const { return nullptr; } + virtual nsStaticAtom* GetPathDataAttrName() const { return nullptr; } + virtual nsStaticAtom* GetTransformListAttrName() const { return nullptr; } + const nsAttrValue* GetAnimatedClassName() const { + if (!mClassAttribute.IsAnimated()) { + return nullptr; + } + return mClassAnimAttr.get(); + } + + virtual void ClearAnyCachedPath() {} + virtual bool IsTransformable() { return false; } + + // WebIDL + mozilla::dom::SVGSVGElement* GetOwnerSVGElement(); + SVGElement* GetViewportElement(); + already_AddRefed<mozilla::dom::DOMSVGAnimatedString> ClassName(); + + void UpdateContentDeclarationBlock(); + const mozilla::DeclarationBlock* GetContentDeclarationBlock() const; + + bool Autofocus() const { return GetBoolAttr(nsGkAtoms::autofocus); } + void SetAutofocus(bool aAutofocus, ErrorResult& aRv) { + if (aAutofocus) { + SetAttr(nsGkAtoms::autofocus, u""_ns, aRv); + } else { + UnsetAttr(nsGkAtoms::autofocus, aRv); + } + } + + protected: + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + // We define BeforeSetAttr here and mark it final to ensure it is NOT used + // by SVG elements. + // This is because we're not currently passing the correct value for aValue to + // BeforeSetAttr since it would involve allocating extra SVG value types. + // See the comment in SVGElement::WillChangeValue. + nsresult BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValueOrString* aValue, bool aNotify) final; + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + static nsresult ReportAttributeParseFailure(Document* aDocument, + nsAtom* aAttribute, + const nsAString& aValue); + + nsAttrValue WillChangeValue(nsAtom* aName, + const mozAutoDocUpdate& aProofOfUpdate); + // aNewValue is set to the old value. This value may be invalid if + // !StoresOwnData. + void DidChangeValue(nsAtom* aName, const nsAttrValue& aEmptyOrOldValue, + nsAttrValue& aNewValue, + const mozAutoDocUpdate& aProofOfUpdate); + void MaybeSerializeAttrBeforeRemoval(nsAtom* aName, bool aNotify); + + nsAtom* GetEventNameForAttr(nsAtom* aAttr) override; + + struct LengthInfo { + nsStaticAtom* const mName; + const float mDefaultValue; + const uint8_t mDefaultUnitType; + const uint8_t mCtxType; + }; + + template <typename Value, typename InfoValue> + struct AttributesInfo { + Value* const mValues; + const InfoValue* const mInfos; + const uint32_t mCount; + + AttributesInfo(Value* aValues, const InfoValue* aInfos, uint32_t aCount) + : mValues(aValues), mInfos(aInfos), mCount(aCount) {} + + void CopyAllFrom(const AttributesInfo&); + void ResetAll(); + void Reset(uint8_t aEnum); + }; + + using LengthAttributesInfo = AttributesInfo<SVGAnimatedLength, LengthInfo>; + + struct NumberInfo { + nsStaticAtom* const mName; + const float mDefaultValue; + const bool mPercentagesAllowed; + }; + + using NumberAttributesInfo = AttributesInfo<SVGAnimatedNumber, NumberInfo>; + + struct NumberPairInfo { + nsStaticAtom* const mName; + const float mDefaultValue1; + const float mDefaultValue2; + }; + + using NumberPairAttributesInfo = + AttributesInfo<SVGAnimatedNumberPair, NumberPairInfo>; + + struct IntegerInfo { + nsStaticAtom* const mName; + const int32_t mDefaultValue; + }; + + using IntegerAttributesInfo = AttributesInfo<SVGAnimatedInteger, IntegerInfo>; + + struct IntegerPairInfo { + nsStaticAtom* const mName; + const int32_t mDefaultValue1; + const int32_t mDefaultValue2; + }; + + using IntegerPairAttributesInfo = + AttributesInfo<SVGAnimatedIntegerPair, IntegerPairInfo>; + + struct BooleanInfo { + nsStaticAtom* const mName; + const bool mDefaultValue; + }; + + using BooleanAttributesInfo = AttributesInfo<SVGAnimatedBoolean, BooleanInfo>; + + friend class mozilla::SVGAnimatedEnumeration; + + struct EnumInfo { + nsStaticAtom* const mName; + const SVGEnumMapping* const mMapping; + const uint16_t mDefaultValue; + }; + + using EnumAttributesInfo = AttributesInfo<SVGAnimatedEnumeration, EnumInfo>; + + struct NumberListInfo { + nsStaticAtom* const mName; + }; + + using NumberListAttributesInfo = + AttributesInfo<SVGAnimatedNumberList, NumberListInfo>; + + struct LengthListInfo { + nsStaticAtom* const mName; + const uint8_t mAxis; + /** + * Flag to indicate whether appending zeros to the end of the list would + * change the rendering of the SVG for the attribute in question. For x and + * y on the <text> element this is true, but for dx and dy on <text> this + * is false. This flag is fed down to SVGLengthListSMILType so it can + * determine if it can sensibly animate from-to lists of different lengths, + * which is desirable in the case of dx and dy. + */ + const bool mCouldZeroPadList; + }; + + using LengthListAttributesInfo = + AttributesInfo<SVGAnimatedLengthList, LengthListInfo>; + + struct StringInfo { + nsStaticAtom* const mName; + const int32_t mNamespaceID; + const bool mIsAnimatable; + }; + + using StringAttributesInfo = AttributesInfo<SVGAnimatedString, StringInfo>; + + friend class DOMSVGStringList; + + struct StringListInfo { + nsStaticAtom* const mName; + }; + + using StringListAttributesInfo = + AttributesInfo<SVGStringList, StringListInfo>; + + virtual LengthAttributesInfo GetLengthInfo(); + virtual NumberAttributesInfo GetNumberInfo(); + virtual NumberPairAttributesInfo GetNumberPairInfo(); + virtual IntegerAttributesInfo GetIntegerInfo(); + virtual IntegerPairAttributesInfo GetIntegerPairInfo(); + virtual BooleanAttributesInfo GetBooleanInfo(); + virtual EnumAttributesInfo GetEnumInfo(); + // We assume all orients, viewboxes and preserveAspectRatios are alike + // so we don't need to wrap the class + virtual SVGAnimatedOrient* GetAnimatedOrient(); + virtual SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio(); + virtual SVGAnimatedViewBox* GetAnimatedViewBox(); + virtual NumberListAttributesInfo GetNumberListInfo(); + virtual LengthListAttributesInfo GetLengthListInfo(); + virtual StringAttributesInfo GetStringInfo(); + virtual StringListAttributesInfo GetStringListInfo(); + + static SVGEnumMapping sSVGUnitTypesMap[]; + + private: + void UnsetAttrInternal(int32_t aNameSpaceID, nsAtom* aName, bool aNotify); + + SVGAnimatedClass mClassAttribute; + UniquePtr<nsAttrValue> mClassAnimAttr; + RefPtr<mozilla::DeclarationBlock> mContentDeclarationBlock; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGElement, MOZILLA_SVGELEMENT_IID) + +/** + * A macro to implement the NS_NewSVGXXXElement() functions. + */ +#define NS_IMPL_NS_NEW_SVG_ELEMENT(_elementName) \ + nsresult NS_NewSVG##_elementName##Element( \ + nsIContent** aResult, \ + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) { \ + RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); \ + auto* nim = nodeInfo->NodeInfoManager(); \ + RefPtr<mozilla::dom::SVG##_elementName##Element> it = \ + new (nim) mozilla::dom::SVG##_elementName##Element(nodeInfo.forget()); \ + \ + nsresult rv = it->Init(); \ + \ + if (NS_FAILED(rv)) { \ + return rv; \ + } \ + \ + it.forget(aResult); \ + \ + return rv; \ + } + +#define NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(_elementName) \ + nsresult NS_NewSVG##_elementName##Element( \ + nsIContent** aResult, \ + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \ + mozilla::dom::FromParser aFromParser) { \ + RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); \ + auto* nim = nodeInfo->NodeInfoManager(); \ + RefPtr<mozilla::dom::SVG##_elementName##Element> it = \ + new (nim) mozilla::dom::SVG##_elementName##Element(nodeInfo.forget(), \ + aFromParser); \ + \ + nsresult rv = it->Init(); \ + \ + if (NS_FAILED(rv)) { \ + return rv; \ + } \ + \ + it.forget(aResult); \ + \ + return rv; \ + } + +// No unlinking, we'd need to null out the value pointer (the object it +// points to is held by the element) and null-check it everywhere. +#define NS_SVG_VAL_IMPL_CYCLE_COLLECTION(_val, _element) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_element) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_val) + +#define NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(_val, _element) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_val) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_element) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \ + NS_IMPL_CYCLE_COLLECTION_TRACE_END + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGELEMENT_H_ diff --git a/dom/svg/SVGElementFactory.cpp b/dom/svg/SVGElementFactory.cpp new file mode 100644 index 0000000000..61cd2f65dd --- /dev/null +++ b/dom/svg/SVGElementFactory.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGElementFactory.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "mozilla/dom/NodeInfo.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FromParser.h" +#include "mozilla/StaticPtr.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// Hash table that maps nsAtom* SVG tags to a SVGContentCreatorFunction. +using TagAtomTable = + nsTHashMap<nsPtrHashKey<nsAtom>, SVGContentCreatorFunction>; +StaticAutoPtr<TagAtomTable> sTagAtomTable; + +#define SVG_TAG(_tag, _classname) \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); \ + \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \ + FromParser aFromParser) { \ + return NS_NewSVG##_classname##Element(aResult, std::move(aNodeInfo)); \ + } + +#define SVG_FROM_PARSER_TAG(_tag, _classname) + +#include "SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG + +nsresult NS_NewSVGElement(Element** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +enum SVGTag { +#define SVG_TAG(_tag, _classname) eSVGTag_##_tag, +#define SVG_FROM_PARSER_TAG(_tag, _classname) eSVGTag_##_tag, +#include "SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG + eSVGTag_Count +}; + +void SVGElementFactory::Init() { + sTagAtomTable = new TagAtomTable(64); + +#define SVG_TAG(_tag, _classname) \ + sTagAtomTable->InsertOrUpdate( \ + nsGkAtoms::_tag, \ + SVGContentCreatorFunction(NS_NewSVG##_classname##Element)); +#define SVG_FROM_PARSER_TAG(_tag, _classname) \ + sTagAtomTable->InsertOrUpdate( \ + nsGkAtoms::_tag, \ + SVGContentCreatorFunction(NS_NewSVG##_classname##Element)); +#include "SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG +} + +void SVGElementFactory::Shutdown() { sTagAtomTable = nullptr; } + +nsresult NS_NewSVGElement(Element** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser) { + NS_ASSERTION(sTagAtomTable, "no lookup table, needs SVGElementFactory::Init"); + + RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; + nsAtom* name = ni->NameAtom(); + + NS_ASSERTION( + ni->NamespaceEquals(kNameSpaceID_SVG), + "Trying to create SVG elements that aren't in the SVG namespace"); + + SVGContentCreatorFunction cb = sTagAtomTable->Get(name); + if (cb) { + nsCOMPtr<nsIContent> content; + nsresult rv = cb(getter_AddRefs(content), ni.forget(), aFromParser); + *aResult = content.forget().take()->AsElement(); + return rv; + } + + // if we don't know what to create, just create a standard svg element: + return NS_NewSVGElement(aResult, ni.forget()); +} + +nsresult NS_NewSVGUnknownElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser) { + RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; + nsCOMPtr<Element> element; + nsresult rv = NS_NewSVGElement(getter_AddRefs(element), ni.forget()); + element.forget(aResult); + return rv; +} diff --git a/dom/svg/SVGElementFactory.h b/dom/svg/SVGElementFactory.h new file mode 100644 index 0000000000..36a32f6904 --- /dev/null +++ b/dom/svg/SVGElementFactory.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGELEMENTFACTORY_H_ +#define DOM_SVG_SVGELEMENTFACTORY_H_ + +#include "nsError.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/FromParser.h" +#include "mozilla/dom/NodeInfo.h" + +class nsAtom; +class nsIContent; + +namespace mozilla::dom { + +class SVGElementFactory { + public: + static void Init(); + static void Shutdown(); +}; + +using SVGContentCreatorFunction = nsresult (*)( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +} // namespace mozilla::dom + +#define SVG_TAG(_tag, _classname) \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \ + mozilla::dom::FromParser aFromParser); + +#define SVG_FROM_PARSER_TAG(_tag, _classname) \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \ + mozilla::dom::FromParser aFromParser); +#include "mozilla/SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG + +nsresult NS_NewSVGUnknownElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +#endif // DOM_SVG_SVGELEMENTFACTORY_H_ diff --git a/dom/svg/SVGEllipseElement.cpp b/dom/svg/SVGEllipseElement.cpp new file mode 100644 index 0000000000..b81d465915 --- /dev/null +++ b/dom/svg/SVGEllipseElement.cpp @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ComputedStyle.h" +#include "mozilla/dom/SVGEllipseElement.h" +#include "mozilla/dom/SVGEllipseElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/RefPtr.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Ellipse) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGEllipseElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGEllipseElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGEllipseElement::sLengthInfo[4] = { + {nsGkAtoms::cx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::cy, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::rx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::ry, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGEllipseElement::SVGEllipseElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGEllipseElementBase(std::move(aNodeInfo)) {} + +bool SVGEllipseElement::IsAttributeMapped(const nsAtom* aAttribute) const { + return IsInLengthInfo(aAttribute, sLengthInfo) || + SVGEllipseElementBase::IsAttributeMapped(aAttribute); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGEllipseElement) + +//---------------------------------------------------------------------- +// nsIDOMSVGEllipseElement methods + +already_AddRefed<DOMSVGAnimatedLength> SVGEllipseElement::Cx() { + return mLengthAttributes[CX].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGEllipseElement::Cy() { + return mLengthAttributes[CY].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGEllipseElement::Rx() { + return mLengthAttributes[RX].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGEllipseElement::Ry() { + return mLengthAttributes[RY].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGEllipseElement::HasValidDimensions() const { + float rx, ry; + + if (SVGGeometryProperty::ResolveAll<SVGT::Rx, SVGT::Ry>(this, &rx, &ry)) { + return rx > 0 && ry > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + bool hasRx = mLengthAttributes[RX].IsExplicitlySet(); + bool hasRy = mLengthAttributes[RY].IsExplicitlySet(); + if ((hasRx && mLengthAttributes[RX].GetAnimValInSpecifiedUnits() <= 0) || + (hasRy && mLengthAttributes[RY].GetAnimValInSpecifiedUnits() <= 0)) { + return false; + } + return hasRx || hasRy; +} + +SVGElement::LengthAttributesInfo SVGEllipseElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGEllipseElement::GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) { + float x, y, rx, ry; + + DebugOnly<bool> ok = + SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::Rx, SVGT::Ry>( + this, &x, &y, &rx, &ry); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (rx <= 0.f || ry <= 0.f) { + // Rendering of the element is disabled + *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x, y)), Size()); + return true; + } + + if (aToBoundsSpace.IsRectilinear()) { + // Optimize the case where we can treat the ellipse as a rectangle and + // still get tight bounds. + if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + Rect userBounds(x - rx, y - ry, 2 * rx, 2 * ry); + SVGContentUtils::RectilinearGetStrokeBounds( + userBounds, aToBoundsSpace, *aToNonScalingStrokeSpace, + aStrokeOptions.mLineWidth, aBounds); + return true; + } + return false; + } + rx += aStrokeOptions.mLineWidth / 2.f; + ry += aStrokeOptions.mLineWidth / 2.f; + } + Rect rect(x - rx, y - ry, 2 * rx, 2 * ry); + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; + } + + return false; +} + +already_AddRefed<Path> SVGEllipseElement::BuildPath(PathBuilder* aBuilder) { + float x, y, rx, ry; + + if (!SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::Rx, SVGT::Ry>( + this, &x, &y, &rx, &ry)) { + // This function might be called for element in display:none subtree + // (e.g. getTotalLength), we fall back to use SVG attributes. + GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr); + } + + if (rx <= 0.0f || ry <= 0.0f) { + return nullptr; + } + + EllipseToBezier(aBuilder, Point(x, y), Size(rx, ry)); + + return aBuilder->Finish(); +} + +bool SVGEllipseElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + const auto& newSVGReset = *aNewStyle.StyleSVGReset(); + const auto& oldSVGReset = *aOldStyle.StyleSVGReset(); + return newSVGReset.mCx != oldSVGReset.mCx || + newSVGReset.mCy != oldSVGReset.mCy || + newSVGReset.mRx != oldSVGReset.mRx || + newSVGReset.mRy != oldSVGReset.mRy; +} + +nsCSSPropertyID SVGEllipseElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case CX: + return eCSSProperty_cx; + case CY: + return eCSSProperty_cy; + case RX: + return eCSSProperty_rx; + case RY: + return eCSSProperty_ry; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGEllipseElement.h b/dom/svg/SVGEllipseElement.h new file mode 100644 index 0000000000..c4fa308556 --- /dev/null +++ b/dom/svg/SVGEllipseElement.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGELLIPSEELEMENT_H_ +#define DOM_SVG_SVGELLIPSEELEMENT_H_ + +#include "nsCSSPropertyID.h" +#include "SVGAnimatedLength.h" +#include "SVGGeometryElement.h" + +nsresult NS_NewSVGEllipseElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class ComputedStyle; + +namespace dom { + +using SVGEllipseElementBase = SVGGeometryElement; + +class SVGEllipseElement final : public SVGEllipseElementBase { + protected: + explicit SVGEllipseElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGEllipseElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + public: + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + virtual bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> Cx(); + already_AddRefed<DOMSVGAnimatedLength> Cy(); + already_AddRefed<DOMSVGAnimatedLength> Rx(); + already_AddRefed<DOMSVGAnimatedLength> Ry(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { CX, CY, RX, RY }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGELLIPSEELEMENT_H_ diff --git a/dom/svg/SVGFEBlendElement.cpp b/dom/svg/SVGFEBlendElement.cpp new file mode 100644 index 0000000000..b5ba3ab198 --- /dev/null +++ b/dom/svg/SVGFEBlendElement.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEBlendElement.h" +#include "mozilla/dom/SVGFEBlendElementBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEBlend) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEBlendElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEBlendElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGEnumMapping SVGFEBlendElement::sModeMap[] = { + {nsGkAtoms::normal, SVG_FEBLEND_MODE_NORMAL}, + {nsGkAtoms::multiply, SVG_FEBLEND_MODE_MULTIPLY}, + {nsGkAtoms::screen, SVG_FEBLEND_MODE_SCREEN}, + {nsGkAtoms::darken, SVG_FEBLEND_MODE_DARKEN}, + {nsGkAtoms::lighten, SVG_FEBLEND_MODE_LIGHTEN}, + {nsGkAtoms::overlay, SVG_FEBLEND_MODE_OVERLAY}, + {nsGkAtoms::colorDodge, SVG_FEBLEND_MODE_COLOR_DODGE}, + {nsGkAtoms::colorBurn, SVG_FEBLEND_MODE_COLOR_BURN}, + {nsGkAtoms::hardLight, SVG_FEBLEND_MODE_HARD_LIGHT}, + {nsGkAtoms::softLight, SVG_FEBLEND_MODE_SOFT_LIGHT}, + {nsGkAtoms::difference, SVG_FEBLEND_MODE_DIFFERENCE}, + {nsGkAtoms::exclusion, SVG_FEBLEND_MODE_EXCLUSION}, + {nsGkAtoms::hue, SVG_FEBLEND_MODE_HUE}, + {nsGkAtoms::saturation, SVG_FEBLEND_MODE_SATURATION}, + {nsGkAtoms::color, SVG_FEBLEND_MODE_COLOR}, + {nsGkAtoms::luminosity, SVG_FEBLEND_MODE_LUMINOSITY}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEBlendElement::sEnumInfo[1] = { + {nsGkAtoms::mode, sModeMap, SVG_FEBLEND_MODE_NORMAL}}; + +SVGElement::StringInfo SVGFEBlendElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}, + {nsGkAtoms::in2, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEBlendElement) + +//---------------------------------------------------------------------- +// nsIDOMSVGFEBlendElement methods + +already_AddRefed<DOMSVGAnimatedString> SVGFEBlendElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGFEBlendElement::In2() { + return mStringAttributes[IN2].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGFEBlendElement::Mode() { + return mEnumAttributes[MODE].ToDOMAnimatedEnum(this); +} + +FilterPrimitiveDescription SVGFEBlendElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + uint32_t mode = mEnumAttributes[MODE].GetAnimValue(); + BlendAttributes attributes; + attributes.mBlendMode = mode; + return FilterPrimitiveDescription(AsVariant(std::move(attributes))); +} + +bool SVGFEBlendElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFEBlendElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::in2 || + aAttribute == nsGkAtoms::mode)); +} + +void SVGFEBlendElement::GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN2], this)); +} + +nsresult SVGFEBlendElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feBlend); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::EnumAttributesInfo SVGFEBlendElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEBlendElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEBlendElement.h b/dom/svg/SVGFEBlendElement.h new file mode 100644 index 0000000000..37ce4559b7 --- /dev/null +++ b/dom/svg/SVGFEBlendElement.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEBLENDELEMENT_H_ +#define DOM_SVG_SVGFEBLENDELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEBlendElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); +namespace mozilla::dom { + +using SVGFEBlendElementBase = SVGFE; + +class SVGFEBlendElement : public SVGFEBlendElementBase { + friend nsresult(::NS_NewSVGFEBlendElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEBlendElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEBlendElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedString> In2(); + already_AddRefed<DOMSVGAnimatedEnumeration> Mode(); + + protected: + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { MODE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sModeMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1, IN2 }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEBLENDELEMENT_H_ diff --git a/dom/svg/SVGFEColorMatrixElement.cpp b/dom/svg/SVGFEColorMatrixElement.cpp new file mode 100644 index 0000000000..9295a8bdbb --- /dev/null +++ b/dom/svg/SVGFEColorMatrixElement.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEColorMatrixElement.h" + +#include "DOMSVGAnimatedNumberList.h" +#include "mozilla/dom/SVGFEColorMatrixElementBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +#define NUM_ENTRIES_IN_4x5_MATRIX 20 + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEColorMatrix) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEColorMatrixElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEColorMatrixElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGEnumMapping SVGFEColorMatrixElement::sTypeMap[] = { + {nsGkAtoms::matrix, SVG_FECOLORMATRIX_TYPE_MATRIX}, + {nsGkAtoms::saturate, SVG_FECOLORMATRIX_TYPE_SATURATE}, + {nsGkAtoms::hueRotate, SVG_FECOLORMATRIX_TYPE_HUE_ROTATE}, + {nsGkAtoms::luminanceToAlpha, SVG_FECOLORMATRIX_TYPE_LUMINANCE_TO_ALPHA}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEColorMatrixElement::sEnumInfo[1] = { + {nsGkAtoms::type, sTypeMap, SVG_FECOLORMATRIX_TYPE_MATRIX}}; + +SVGElement::StringInfo SVGFEColorMatrixElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +SVGElement::NumberListInfo SVGFEColorMatrixElement::sNumberListInfo[1] = { + {nsGkAtoms::values}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEColorMatrixElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGFEColorMatrixElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGFEColorMatrixElement::Type() { + return mEnumAttributes[TYPE].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedNumberList> SVGFEColorMatrixElement::Values() { + return DOMSVGAnimatedNumberList::GetDOMWrapper(&mNumberListAttributes[VALUES], + this, VALUES); +} + +void SVGFEColorMatrixElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +FilterPrimitiveDescription SVGFEColorMatrixElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + uint32_t type = mEnumAttributes[TYPE].GetAnimValue(); + const SVGNumberList& values = mNumberListAttributes[VALUES].GetAnimValue(); + + ColorMatrixAttributes atts; + if (!mNumberListAttributes[VALUES].IsExplicitlySet() && + (type == SVG_FECOLORMATRIX_TYPE_MATRIX || + type == SVG_FECOLORMATRIX_TYPE_SATURATE || + type == SVG_FECOLORMATRIX_TYPE_HUE_ROTATE)) { + atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_MATRIX; + static const float identityMatrix[] = { + // clang-format off + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + // clang-format on + }; + atts.mValues.AppendElements(identityMatrix, 20); + } else { + atts.mType = type; + if (values.Length()) { + atts.mValues.AppendElements(&values[0], values.Length()); + } + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEColorMatrixElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEColorMatrixElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::type || + aAttribute == nsGkAtoms::values)); +} + +nsresult SVGFEColorMatrixElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feColorMatrix); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::EnumAttributesInfo SVGFEColorMatrixElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEColorMatrixElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +SVGElement::NumberListAttributesInfo +SVGFEColorMatrixElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEColorMatrixElement.h b/dom/svg/SVGFEColorMatrixElement.h new file mode 100644 index 0000000000..d4f28ea425 --- /dev/null +++ b/dom/svg/SVGFEColorMatrixElement.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECOLORMATRIXELEMENT_H_ +#define DOM_SVG_SVGFECOLORMATRIXELEMENT_H_ + +#include "SVGAnimatedNumberList.h" +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEColorMatrixElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class DOMSVGAnimatedNumberList; + +using SVGFEColorMatrixElementBase = SVGFE; + +class SVGFEColorMatrixElement : public SVGFEColorMatrixElementBase { + friend nsresult(::NS_NewSVGFEColorMatrixElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEColorMatrixElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEColorMatrixElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedEnumeration> Type(); + already_AddRefed<DOMSVGAnimatedNumberList> Values(); + + protected: + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { TYPE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sTypeMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + enum { VALUES }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECOLORMATRIXELEMENT_H_ diff --git a/dom/svg/SVGFEComponentTransferElement.cpp b/dom/svg/SVGFEComponentTransferElement.cpp new file mode 100644 index 0000000000..d313c9ad15 --- /dev/null +++ b/dom/svg/SVGFEComponentTransferElement.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEComponentTransferElement.h" + +#include "mozilla/dom/SVGComponentTransferFunctionElement.h" +#include "mozilla/dom/SVGFEComponentTransferElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEComponentTransfer) + +using namespace mozilla::gfx; +; + +namespace mozilla::dom { + +JSObject* SVGFEComponentTransferElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGFEComponentTransferElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEComponentTransferElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEComponentTransferElement) + +already_AddRefed<DOMSVGAnimatedString> SVGFEComponentTransferElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo +SVGFEComponentTransferElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//-------------------------------------------- + +FilterPrimitiveDescription +SVGFEComponentTransferElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + RefPtr<SVGComponentTransferFunctionElement> childForChannel[4]; + + for (nsIContent* childContent = nsINode::GetFirstChild(); childContent; + childContent = childContent->GetNextSibling()) { + RefPtr<SVGComponentTransferFunctionElement> child; + CallQueryInterface( + childContent, + (SVGComponentTransferFunctionElement**)getter_AddRefs(child)); + if (child) { + childForChannel[child->GetChannel()] = child; + } + } + + ComponentTransferAttributes atts; + for (int32_t i = 0; i < 4; i++) { + if (childForChannel[i]) { + childForChannel[i]->ComputeAttributes(i, atts); + } else { + atts.mTypes[i] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY; + } + } + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEComponentTransferElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEComponentTransferElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::in); +} + +void SVGFEComponentTransferElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +nsresult SVGFEComponentTransferElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feComponentTransfer); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEComponentTransferElement.h b/dom/svg/SVGFEComponentTransferElement.h new file mode 100644 index 0000000000..927cb217ce --- /dev/null +++ b/dom/svg/SVGFEComponentTransferElement.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECOMPONENTTRANSFERELEMENT_H_ +#define DOM_SVG_SVGFECOMPONENTTRANSFERELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEComponentTransferElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEComponentTransferElementBase = SVGFE; + +class SVGFEComponentTransferElement : public SVGFEComponentTransferElementBase { + friend nsresult(::NS_NewSVGFEComponentTransferElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEComponentTransferElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEComponentTransferElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECOMPONENTTRANSFERELEMENT_H_ diff --git a/dom/svg/SVGFECompositeElement.cpp b/dom/svg/SVGFECompositeElement.cpp new file mode 100644 index 0000000000..04cff7eb23 --- /dev/null +++ b/dom/svg/SVGFECompositeElement.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFECompositeElement.h" +#include "mozilla/dom/SVGFECompositeElementBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEComposite) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFECompositeElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFECompositeElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFECompositeElement::sNumberInfo[4] = { + {nsGkAtoms::k1, 0, false}, + {nsGkAtoms::k2, 0, false}, + {nsGkAtoms::k3, 0, false}, + {nsGkAtoms::k4, 0, false}}; + +SVGEnumMapping SVGFECompositeElement::sOperatorMap[] = { + {nsGkAtoms::over, SVG_FECOMPOSITE_OPERATOR_OVER}, + {nsGkAtoms::in, SVG_FECOMPOSITE_OPERATOR_IN}, + {nsGkAtoms::out, SVG_FECOMPOSITE_OPERATOR_OUT}, + {nsGkAtoms::atop, SVG_FECOMPOSITE_OPERATOR_ATOP}, + {nsGkAtoms::xor_, SVG_FECOMPOSITE_OPERATOR_XOR}, + {nsGkAtoms::arithmetic, SVG_FECOMPOSITE_OPERATOR_ARITHMETIC}, + {nsGkAtoms::lighter, SVG_FECOMPOSITE_OPERATOR_LIGHTER}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFECompositeElement::sEnumInfo[1] = { + {nsGkAtoms::_operator, sOperatorMap, SVG_FECOMPOSITE_OPERATOR_OVER}}; + +SVGElement::StringInfo SVGFECompositeElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}, + {nsGkAtoms::in2, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFECompositeElement) + +already_AddRefed<DOMSVGAnimatedString> SVGFECompositeElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGFECompositeElement::In2() { + return mStringAttributes[IN2].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGFECompositeElement::Operator() { + return mEnumAttributes[OPERATOR].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFECompositeElement::K1() { + return mNumberAttributes[ATTR_K1].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFECompositeElement::K2() { + return mNumberAttributes[ATTR_K2].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFECompositeElement::K3() { + return mNumberAttributes[ATTR_K3].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFECompositeElement::K4() { + return mNumberAttributes[ATTR_K4].ToDOMAnimatedNumber(this); +} + +void SVGFECompositeElement::SetK(float k1, float k2, float k3, float k4) { + mNumberAttributes[ATTR_K1].SetBaseValue(k1, this); + mNumberAttributes[ATTR_K2].SetBaseValue(k2, this); + mNumberAttributes[ATTR_K3].SetBaseValue(k3, this); + mNumberAttributes[ATTR_K4].SetBaseValue(k4, this); +} + +FilterPrimitiveDescription SVGFECompositeElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + CompositeAttributes atts; + uint32_t op = mEnumAttributes[OPERATOR].GetAnimValue(); + atts.mOperator = op; + + if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) { + float k[4]; + GetAnimatedNumberValues(k, k + 1, k + 2, k + 3, nullptr); + atts.mCoefficients.AppendElements(k, 4); + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFECompositeElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFECompositeElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::in2 || + aAttribute == nsGkAtoms::k1 || aAttribute == nsGkAtoms::k2 || + aAttribute == nsGkAtoms::k3 || aAttribute == nsGkAtoms::k4 || + aAttribute == nsGkAtoms::_operator)); +} + +void SVGFECompositeElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN2], this)); +} + +nsresult SVGFECompositeElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feComposite); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFECompositeElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::EnumAttributesInfo SVGFECompositeElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFECompositeElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFECompositeElement.h b/dom/svg/SVGFECompositeElement.h new file mode 100644 index 0000000000..934c96648a --- /dev/null +++ b/dom/svg/SVGFECompositeElement.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECOMPOSITEELEMENT_H_ +#define DOM_SVG_SVGFECOMPOSITEELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFECompositeElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFECompositeElementBase = SVGFE; + +class SVGFECompositeElement : public SVGFECompositeElementBase { + friend nsresult(::NS_NewSVGFECompositeElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFECompositeElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFECompositeElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedString> In2(); + already_AddRefed<DOMSVGAnimatedEnumeration> Operator(); + already_AddRefed<DOMSVGAnimatedNumber> K1(); + already_AddRefed<DOMSVGAnimatedNumber> K2(); + already_AddRefed<DOMSVGAnimatedNumber> K3(); + already_AddRefed<DOMSVGAnimatedNumber> K4(); + void SetK(float k1, float k2, float k3, float k4); + + protected: + NumberAttributesInfo GetNumberInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { ATTR_K1, ATTR_K2, ATTR_K3, ATTR_K4 }; + SVGAnimatedNumber mNumberAttributes[4]; + static NumberInfo sNumberInfo[4]; + + enum { OPERATOR }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sOperatorMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1, IN2 }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECOMPOSITEELEMENT_H_ diff --git a/dom/svg/SVGFEConvolveMatrixElement.cpp b/dom/svg/SVGFEConvolveMatrixElement.cpp new file mode 100644 index 0000000000..4961419e7f --- /dev/null +++ b/dom/svg/SVGFEConvolveMatrixElement.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEConvolveMatrixElement.h" +#include "mozilla/dom/SVGFEConvolveMatrixElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "DOMSVGAnimatedNumberList.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEConvolveMatrix) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEConvolveMatrixElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGFEConvolveMatrixElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEConvolveMatrixElement::sNumberInfo[2] = { + {nsGkAtoms::divisor, 1, false}, {nsGkAtoms::bias, 0, false}}; + +SVGElement::NumberPairInfo SVGFEConvolveMatrixElement::sNumberPairInfo[1] = { + {nsGkAtoms::kernelUnitLength, 0, 0}}; + +SVGElement::IntegerInfo SVGFEConvolveMatrixElement::sIntegerInfo[2] = { + {nsGkAtoms::targetX, 0}, {nsGkAtoms::targetY, 0}}; + +SVGElement::IntegerPairInfo SVGFEConvolveMatrixElement::sIntegerPairInfo[1] = { + {nsGkAtoms::order, 3, 3}}; + +SVGElement::BooleanInfo SVGFEConvolveMatrixElement::sBooleanInfo[1] = { + {nsGkAtoms::preserveAlpha, false}}; + +SVGEnumMapping SVGFEConvolveMatrixElement::sEdgeModeMap[] = { + {nsGkAtoms::duplicate, SVG_EDGEMODE_DUPLICATE}, + {nsGkAtoms::wrap, SVG_EDGEMODE_WRAP}, + {nsGkAtoms::none, SVG_EDGEMODE_NONE}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEConvolveMatrixElement::sEnumInfo[1] = { + {nsGkAtoms::edgeMode, sEdgeModeMap, SVG_EDGEMODE_DUPLICATE}}; + +SVGElement::StringInfo SVGFEConvolveMatrixElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +SVGElement::NumberListInfo SVGFEConvolveMatrixElement::sNumberListInfo[1] = { + {nsGkAtoms::kernelMatrix}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEConvolveMatrixElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGFEConvolveMatrixElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::OrderX() { + return mIntegerPairAttributes[ORDER].ToDOMAnimatedInteger( + SVGAnimatedIntegerPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::OrderY() { + return mIntegerPairAttributes[ORDER].ToDOMAnimatedInteger( + SVGAnimatedIntegerPair::eSecond, this); +} + +already_AddRefed<DOMSVGAnimatedNumberList> +SVGFEConvolveMatrixElement::KernelMatrix() { + return DOMSVGAnimatedNumberList::GetDOMWrapper( + &mNumberListAttributes[KERNELMATRIX], this, KERNELMATRIX); +} + +already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::TargetX() { + return mIntegerAttributes[TARGET_X].ToDOMAnimatedInteger(this); +} + +already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::TargetY() { + return mIntegerAttributes[TARGET_Y].ToDOMAnimatedInteger(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGFEConvolveMatrixElement::EdgeMode() { + return mEnumAttributes[EDGEMODE].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedBoolean> +SVGFEConvolveMatrixElement::PreserveAlpha() { + return mBooleanAttributes[PRESERVEALPHA].ToDOMAnimatedBoolean(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEConvolveMatrixElement::Divisor() { + return mNumberAttributes[DIVISOR].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEConvolveMatrixElement::Bias() { + return mNumberAttributes[BIAS].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEConvolveMatrixElement::KernelUnitLengthX() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEConvolveMatrixElement::KernelUnitLengthY() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEConvolveMatrixElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +FilterPrimitiveDescription SVGFEConvolveMatrixElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + FilterPrimitiveDescription failureDescription; + + const SVGNumberList& kernelMatrix = + mNumberListAttributes[KERNELMATRIX].GetAnimValue(); + uint32_t kmLength = kernelMatrix.Length(); + + int32_t orderX = mIntegerPairAttributes[ORDER].GetAnimValue( + SVGAnimatedIntegerPair::eFirst); + int32_t orderY = mIntegerPairAttributes[ORDER].GetAnimValue( + SVGAnimatedIntegerPair::eSecond); + + if (orderX <= 0 || orderY <= 0 || + static_cast<uint32_t>(orderX * orderY) != kmLength) { + return failureDescription; + } + + int32_t targetX, targetY; + GetAnimatedIntegerValues(&targetX, &targetY, nullptr); + + if (mIntegerAttributes[TARGET_X].IsExplicitlySet()) { + if (targetX < 0 || targetX >= orderX) return failureDescription; + } else { + targetX = orderX / 2; + } + if (mIntegerAttributes[TARGET_Y].IsExplicitlySet()) { + if (targetY < 0 || targetY >= orderY) return failureDescription; + } else { + targetY = orderY / 2; + } + + if (orderX > NS_SVG_OFFSCREEN_MAX_DIMENSION || + orderY > NS_SVG_OFFSCREEN_MAX_DIMENSION) + return failureDescription; + UniquePtr<float[]> kernel = MakeUniqueFallible<float[]>(orderX * orderY); + if (!kernel) return failureDescription; + for (uint32_t i = 0; i < kmLength; i++) { + kernel[kmLength - 1 - i] = kernelMatrix[i]; + } + + float divisor; + if (mNumberAttributes[DIVISOR].IsExplicitlySet()) { + divisor = mNumberAttributes[DIVISOR].GetAnimValue(); + if (divisor == 0) return failureDescription; + } else { + divisor = kernel[0]; + for (uint32_t i = 1; i < kmLength; i++) divisor += kernel[i]; + if (divisor == 0) divisor = 1; + } + + uint32_t edgeMode = mEnumAttributes[EDGEMODE].GetAnimValue(); + bool preserveAlpha = mBooleanAttributes[PRESERVEALPHA].GetAnimValue(); + float bias = mNumberAttributes[BIAS].GetAnimValue(); + + Size kernelUnitLength = GetKernelUnitLength( + aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]); + + if (kernelUnitLength.width <= 0 || kernelUnitLength.height <= 0) { + // According to spec, A negative or zero value is an error. See link below + // for details. + // https://www.w3.org/TR/SVG/filters.html#feConvolveMatrixElementKernelUnitLengthAttribute + return failureDescription; + } + + ConvolveMatrixAttributes atts; + atts.mKernelSize = IntSize(orderX, orderY); + atts.mKernelMatrix.AppendElements(&kernelMatrix[0], kmLength); + atts.mDivisor = divisor; + atts.mBias = bias; + atts.mTarget = IntPoint(targetX, targetY); + atts.mEdgeMode = edgeMode; + atts.mKernelUnitLength = kernelUnitLength; + atts.mPreserveAlpha = preserveAlpha; + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEConvolveMatrixElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEConvolveMatrixElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::divisor || + aAttribute == nsGkAtoms::bias || + aAttribute == nsGkAtoms::kernelUnitLength || + aAttribute == nsGkAtoms::targetX || + aAttribute == nsGkAtoms::targetY || aAttribute == nsGkAtoms::order || + aAttribute == nsGkAtoms::preserveAlpha || + aAttribute == nsGkAtoms::edgeMode || + aAttribute == nsGkAtoms::kernelMatrix)); +} + +nsresult SVGFEConvolveMatrixElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feConvolveMatrix); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEConvolveMatrixElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo +SVGFEConvolveMatrixElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::IntegerAttributesInfo SVGFEConvolveMatrixElement::GetIntegerInfo() { + return IntegerAttributesInfo(mIntegerAttributes, sIntegerInfo, + ArrayLength(sIntegerInfo)); +} + +SVGElement::IntegerPairAttributesInfo +SVGFEConvolveMatrixElement::GetIntegerPairInfo() { + return IntegerPairAttributesInfo(mIntegerPairAttributes, sIntegerPairInfo, + ArrayLength(sIntegerPairInfo)); +} + +SVGElement::BooleanAttributesInfo SVGFEConvolveMatrixElement::GetBooleanInfo() { + return BooleanAttributesInfo(mBooleanAttributes, sBooleanInfo, + ArrayLength(sBooleanInfo)); +} + +SVGElement::EnumAttributesInfo SVGFEConvolveMatrixElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEConvolveMatrixElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +SVGElement::NumberListAttributesInfo +SVGFEConvolveMatrixElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEConvolveMatrixElement.h b/dom/svg/SVGFEConvolveMatrixElement.h new file mode 100644 index 0000000000..6736a6a26a --- /dev/null +++ b/dom/svg/SVGFEConvolveMatrixElement.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECONVOLVEMATRIXELEMENT_H_ +#define DOM_SVG_SVGFECONVOLVEMATRIXELEMENT_H_ + +#include "SVGAnimatedBoolean.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedInteger.h" +#include "SVGAnimatedIntegerPair.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberList.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEConvolveMatrixElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { +class DOMSVGAnimatedNumberList; +class DOMSVGAnimatedBoolean; + +using SVGFEConvolveMatrixElementBase = SVGFE; + +class SVGFEConvolveMatrixElement : public SVGFEConvolveMatrixElementBase { + friend nsresult(::NS_NewSVGFEConvolveMatrixElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEConvolveMatrixElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEConvolveMatrixElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedInteger> OrderX(); + already_AddRefed<DOMSVGAnimatedInteger> OrderY(); + already_AddRefed<DOMSVGAnimatedNumberList> KernelMatrix(); + already_AddRefed<DOMSVGAnimatedInteger> TargetX(); + already_AddRefed<DOMSVGAnimatedInteger> TargetY(); + already_AddRefed<DOMSVGAnimatedEnumeration> EdgeMode(); + already_AddRefed<DOMSVGAnimatedBoolean> PreserveAlpha(); + already_AddRefed<DOMSVGAnimatedNumber> Divisor(); + already_AddRefed<DOMSVGAnimatedNumber> Bias(); + already_AddRefed<DOMSVGAnimatedNumber> KernelUnitLengthX(); + already_AddRefed<DOMSVGAnimatedNumber> KernelUnitLengthY(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + IntegerAttributesInfo GetIntegerInfo() override; + IntegerPairAttributesInfo GetIntegerPairInfo() override; + BooleanAttributesInfo GetBooleanInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { DIVISOR, BIAS }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; + + enum { KERNEL_UNIT_LENGTH }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { TARGET_X, TARGET_Y }; + SVGAnimatedInteger mIntegerAttributes[2]; + static IntegerInfo sIntegerInfo[2]; + + enum { ORDER }; + SVGAnimatedIntegerPair mIntegerPairAttributes[1]; + static IntegerPairInfo sIntegerPairInfo[1]; + + enum { PRESERVEALPHA }; + SVGAnimatedBoolean mBooleanAttributes[1]; + static BooleanInfo sBooleanInfo[1]; + + enum { EDGEMODE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sEdgeModeMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + enum { KERNELMATRIX }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECONVOLVEMATRIXELEMENT_H_ diff --git a/dom/svg/SVGFEDiffuseLightingElement.cpp b/dom/svg/SVGFEDiffuseLightingElement.cpp new file mode 100644 index 0000000000..f80daa5fd1 --- /dev/null +++ b/dom/svg/SVGFEDiffuseLightingElement.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDiffuseLightingElement.h" +#include "mozilla/dom/SVGFEDiffuseLightingElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDiffuseLighting) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDiffuseLightingElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGFEDiffuseLightingElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDiffuseLightingElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGFEDiffuseLightingElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEDiffuseLightingElement::SurfaceScale() { + return mNumberAttributes[SURFACE_SCALE].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEDiffuseLightingElement::DiffuseConstant() { + return mNumberAttributes[DIFFUSE_CONSTANT].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEDiffuseLightingElement::KernelUnitLengthX() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEDiffuseLightingElement::KernelUnitLengthY() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +FilterPrimitiveDescription SVGFEDiffuseLightingElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + float diffuseConstant = mNumberAttributes[DIFFUSE_CONSTANT].GetAnimValue(); + + DiffuseLightingAttributes atts; + atts.mLightingConstant = diffuseConstant; + if (!AddLightingAttributes(&atts, aInstance)) { + return FilterPrimitiveDescription(); + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEDiffuseLightingElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEDiffuseLightingElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::diffuseConstant); +} + +nsresult SVGFEDiffuseLightingElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feDiffuseLighting); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDiffuseLightingElement.h b/dom/svg/SVGFEDiffuseLightingElement.h new file mode 100644 index 0000000000..5adbc3d29e --- /dev/null +++ b/dom/svg/SVGFEDiffuseLightingElement.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDIFFUSELIGHTINGELEMENT_H_ +#define DOM_SVG_SVGFEDIFFUSELIGHTINGELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDiffuseLightingElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDiffuseLightingElementBase = SVGFELightingElement; + +class SVGFEDiffuseLightingElement : public SVGFEDiffuseLightingElementBase { + friend nsresult(::NS_NewSVGFEDiffuseLightingElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEDiffuseLightingElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEDiffuseLightingElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedNumber> SurfaceScale(); + already_AddRefed<DOMSVGAnimatedNumber> DiffuseConstant(); + already_AddRefed<DOMSVGAnimatedNumber> KernelUnitLengthX(); + already_AddRefed<DOMSVGAnimatedNumber> KernelUnitLengthY(); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDIFFUSELIGHTINGELEMENT_H_ diff --git a/dom/svg/SVGFEDisplacementMapElement.cpp b/dom/svg/SVGFEDisplacementMapElement.cpp new file mode 100644 index 0000000000..8bbe5ebc7a --- /dev/null +++ b/dom/svg/SVGFEDisplacementMapElement.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDisplacementMapElement.h" +#include "mozilla/dom/SVGFEDisplacementMapElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDisplacementMap) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDisplacementMapElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGFEDisplacementMapElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEDisplacementMapElement::sNumberInfo[1] = { + {nsGkAtoms::scale, 0, false}, +}; + +SVGEnumMapping SVGFEDisplacementMapElement::sChannelMap[] = { + {nsGkAtoms::R, SVG_CHANNEL_R}, + {nsGkAtoms::G, SVG_CHANNEL_G}, + {nsGkAtoms::B, SVG_CHANNEL_B}, + {nsGkAtoms::A, SVG_CHANNEL_A}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEDisplacementMapElement::sEnumInfo[2] = { + {nsGkAtoms::xChannelSelector, sChannelMap, SVG_CHANNEL_A}, + {nsGkAtoms::yChannelSelector, sChannelMap, SVG_CHANNEL_A}}; + +SVGElement::StringInfo SVGFEDisplacementMapElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}, + {nsGkAtoms::in2, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDisplacementMapElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGFEDisplacementMapElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGFEDisplacementMapElement::In2() { + return mStringAttributes[IN2].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEDisplacementMapElement::Scale() { + return mNumberAttributes[SCALE].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGFEDisplacementMapElement::XChannelSelector() { + return mEnumAttributes[CHANNEL_X].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGFEDisplacementMapElement::YChannelSelector() { + return mEnumAttributes[CHANNEL_Y].ToDOMAnimatedEnum(this); +} + +FilterPrimitiveDescription SVGFEDisplacementMapElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + if (aInputsAreTainted[1]) { + // If the map is tainted, refuse to apply the effect and act as a + // pass-through filter instead, as required by the spec. + OffsetAttributes atts; + atts.mValue = IntPoint(0, 0); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); + } + + float scale = aInstance->GetPrimitiveNumber(SVGContentUtils::XY, + &mNumberAttributes[SCALE]); + uint32_t xChannel = mEnumAttributes[CHANNEL_X].GetAnimValue(); + uint32_t yChannel = mEnumAttributes[CHANNEL_Y].GetAnimValue(); + DisplacementMapAttributes atts; + atts.mScale = scale; + atts.mXChannel = xChannel; + atts.mYChannel = yChannel; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEDisplacementMapElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEDisplacementMapElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::in2 || + aAttribute == nsGkAtoms::scale || + aAttribute == nsGkAtoms::xChannelSelector || + aAttribute == nsGkAtoms::yChannelSelector)); +} + +void SVGFEDisplacementMapElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN2], this)); +} + +nsresult SVGFEDisplacementMapElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feDisplacementMap); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEDisplacementMapElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::EnumAttributesInfo SVGFEDisplacementMapElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEDisplacementMapElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDisplacementMapElement.h b/dom/svg/SVGFEDisplacementMapElement.h new file mode 100644 index 0000000000..6c413fa088 --- /dev/null +++ b/dom/svg/SVGFEDisplacementMapElement.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDISPLACEMENTMAPELEMENT_H_ +#define DOM_SVG_SVGFEDISPLACEMENTMAPELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDisplacementMapElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDisplacementMapElementBase = SVGFE; + +class SVGFEDisplacementMapElement : public SVGFEDisplacementMapElementBase { + protected: + friend nsresult(::NS_NewSVGFEDisplacementMapElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGFEDisplacementMapElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEDisplacementMapElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedString> In2(); + already_AddRefed<DOMSVGAnimatedNumber> Scale(); + already_AddRefed<DOMSVGAnimatedEnumeration> XChannelSelector(); + already_AddRefed<DOMSVGAnimatedEnumeration> YChannelSelector(); + + protected: + virtual bool OperatesOnSRGB(int32_t aInputIndex, + bool aInputIsAlreadySRGB) override { + switch (aInputIndex) { + case 0: + return aInputIsAlreadySRGB; + case 1: + return SVGFEDisplacementMapElementBase::OperatesOnSRGB( + aInputIndex, aInputIsAlreadySRGB); + default: + NS_ERROR("Will not give correct color model"); + return false; + } + } + + NumberAttributesInfo GetNumberInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { SCALE }; + SVGAnimatedNumber mNumberAttributes[1]; + static NumberInfo sNumberInfo[1]; + + enum { CHANNEL_X, CHANNEL_Y }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static SVGEnumMapping sChannelMap[]; + static EnumInfo sEnumInfo[2]; + + enum { RESULT, IN1, IN2 }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDISPLACEMENTMAPELEMENT_H_ diff --git a/dom/svg/SVGFEDistantLightElement.cpp b/dom/svg/SVGFEDistantLightElement.cpp new file mode 100644 index 0000000000..f2b5b19c33 --- /dev/null +++ b/dom/svg/SVGFEDistantLightElement.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDistantLightElement.h" +#include "mozilla/dom/SVGFEDistantLightElementBinding.h" +#include "mozilla/SVGFilterInstance.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDistantLight) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDistantLightElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGFEDistantLightElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEDistantLightElement::sNumberInfo[2] = { + {nsGkAtoms::azimuth, 0, false}, {nsGkAtoms::elevation, 0, false}}; + +//---------------------------------------------------------------------- +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDistantLightElement) + +// nsFEUnstyledElement methods + +bool SVGFEDistantLightElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::azimuth || + aAttribute == nsGkAtoms::elevation); +} + +LightType SVGFEDistantLightElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) { + float azimuth, elevation; + GetAnimatedNumberValues(&azimuth, &elevation, nullptr); + + aFloatAttributes.SetLength(kDistantLightNumAttributes); + aFloatAttributes[kDistantLightAzimuthIndex] = azimuth; + aFloatAttributes[kDistantLightElevationIndex] = elevation; + return LightType::Distant; +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEDistantLightElement::Azimuth() { + return mNumberAttributes[AZIMUTH].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEDistantLightElement::Elevation() { + return mNumberAttributes[ELEVATION].ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEDistantLightElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDistantLightElement.h b/dom/svg/SVGFEDistantLightElement.h new file mode 100644 index 0000000000..533fcb067b --- /dev/null +++ b/dom/svg/SVGFEDistantLightElement.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDISTANTLIGHTELEMENT_H_ +#define DOM_SVG_SVGFEDISTANTLIGHTELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDistantLightElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDistantLightElementBase = SVGFELightElement; + +class SVGFEDistantLightElement : public SVGFEDistantLightElementBase { + friend nsresult(::NS_NewSVGFEDistantLightElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEDistantLightElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEDistantLightElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedNumber> Azimuth(); + already_AddRefed<DOMSVGAnimatedNumber> Elevation(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + + enum { AZIMUTH, ELEVATION }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDISTANTLIGHTELEMENT_H_ diff --git a/dom/svg/SVGFEDropShadowElement.cpp b/dom/svg/SVGFEDropShadowElement.cpp new file mode 100644 index 0000000000..c013a522a0 --- /dev/null +++ b/dom/svg/SVGFEDropShadowElement.cpp @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDropShadowElement.h" +#include "mozilla/dom/SVGFEDropShadowElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "nsIFrame.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDropShadow) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDropShadowElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEDropShadowElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEDropShadowElement::sNumberInfo[2] = { + {nsGkAtoms::dx, 2, false}, {nsGkAtoms::dy, 2, false}}; + +SVGElement::NumberPairInfo SVGFEDropShadowElement::sNumberPairInfo[1] = { + {nsGkAtoms::stdDeviation, 2, 2}}; + +SVGElement::StringInfo SVGFEDropShadowElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDropShadowElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGFEDropShadowElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEDropShadowElement::Dx() { + return mNumberAttributes[DX].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEDropShadowElement::Dy() { + return mNumberAttributes[DY].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEDropShadowElement::StdDeviationX() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEDropShadowElement::StdDeviationY() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEDropShadowElement::SetStdDeviation(float stdDeviationX, + float stdDeviationY) { + mNumberPairAttributes[STD_DEV].SetBaseValues(stdDeviationX, stdDeviationY, + this); +} + +FilterPrimitiveDescription SVGFEDropShadowElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + float stdX = aInstance->GetPrimitiveNumber(SVGContentUtils::X, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eFirst); + float stdY = aInstance->GetPrimitiveNumber(SVGContentUtils::Y, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eSecond); + if (stdX < 0 || stdY < 0) { + return FilterPrimitiveDescription(); + } + + Point offset( + aInstance->GetPrimitiveNumber(SVGContentUtils::X, &mNumberAttributes[DX]), + aInstance->GetPrimitiveNumber(SVGContentUtils::Y, + &mNumberAttributes[DY])); + + DropShadowAttributes atts; + atts.mStdDeviation = Size(stdX, stdY); + atts.mOffset = offset; + + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + const nsStyleSVGReset* styleSVGReset = frame->Style()->StyleSVGReset(); + sRGBColor color( + sRGBColor::FromABGR(styleSVGReset->mFloodColor.CalcColor(frame))); + color.a *= styleSVGReset->mFloodOpacity; + atts.mColor = color; + } else { + atts.mColor = sRGBColor(); + } + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEDropShadowElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEDropShadowElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || + aAttribute == nsGkAtoms::stdDeviation || + aAttribute == nsGkAtoms::dx || aAttribute == nsGkAtoms::dy)); +} + +void SVGFEDropShadowElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGFEDropShadowElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap}; + + return FindAttributeDependence(name, map) || + SVGFEDropShadowElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEDropShadowElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo +SVGFEDropShadowElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::StringAttributesInfo SVGFEDropShadowElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDropShadowElement.h b/dom/svg/SVGFEDropShadowElement.h new file mode 100644 index 0000000000..5bb1b3dcd8 --- /dev/null +++ b/dom/svg/SVGFEDropShadowElement.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDROPSHADOWELEMENT_H_ +#define DOM_SVG_SVGFEDROPSHADOWELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDropShadowElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDropShadowElementBase = SVGFE; + +class SVGFEDropShadowElement : public SVGFEDropShadowElementBase { + friend nsresult(::NS_NewSVGFEDropShadowElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEDropShadowElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEDropShadowElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedNumber> Dx(); + already_AddRefed<DOMSVGAnimatedNumber> Dy(); + already_AddRefed<DOMSVGAnimatedNumber> StdDeviationX(); + already_AddRefed<DOMSVGAnimatedNumber> StdDeviationY(); + void SetStdDeviation(float stdDeviationX, float stdDeviationY); + + protected: + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { DX, DY }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; + + enum { STD_DEV }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDROPSHADOWELEMENT_H_ diff --git a/dom/svg/SVGFEFloodElement.cpp b/dom/svg/SVGFEFloodElement.cpp new file mode 100644 index 0000000000..91ede01760 --- /dev/null +++ b/dom/svg/SVGFEFloodElement.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEFloodElement.h" + +#include "FilterSupport.h" +#include "mozilla/dom/SVGFEFloodElementBinding.h" +#include "nsColor.h" +#include "nsIFrame.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFlood) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEFloodElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEFloodElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEFloodElement::sStringInfo[1] = { + {nsGkAtoms::result, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFloodElement) + +FilterPrimitiveDescription SVGFEFloodElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + FloodAttributes atts; + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + const nsStyleSVGReset* styleSVGReset = frame->Style()->StyleSVGReset(); + sRGBColor color( + sRGBColor::FromABGR(styleSVGReset->mFloodColor.CalcColor(frame))); + color.a *= styleSVGReset->mFloodOpacity; + atts.mColor = color; + } else { + atts.mColor = sRGBColor(); + } + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGFEFloodElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sColorMap, sFEFloodMap}; + + return FindAttributeDependence(name, map) || + SVGFEFloodElementBase::IsAttributeMapped(name); +} + +nsresult SVGFEFloodElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feFlood); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFEFloodElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEFloodElement.h b/dom/svg/SVGFEFloodElement.h new file mode 100644 index 0000000000..73d6e97442 --- /dev/null +++ b/dom/svg/SVGFEFloodElement.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEFLOODELEMENT_H_ +#define DOM_SVG_SVGFEFLOODELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEFloodElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEFloodElementBase = SVGFE; + +class SVGFEFloodElement : public SVGFEFloodElementBase { + friend nsresult(::NS_NewSVGFEFloodElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEFloodElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEFloodElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + protected: + bool ProducesSRGB() override { return true; } + + StringAttributesInfo GetStringInfo() override; + + enum { RESULT }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEFLOODELEMENT_H_ diff --git a/dom/svg/SVGFEGaussianBlurElement.cpp b/dom/svg/SVGFEGaussianBlurElement.cpp new file mode 100644 index 0000000000..2bc0c95eae --- /dev/null +++ b/dom/svg/SVGFEGaussianBlurElement.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEGaussianBlurElement.h" +#include "mozilla/dom/SVGFEGaussianBlurElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEGaussianBlur) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEGaussianBlurElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGFEGaussianBlurElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberPairInfo SVGFEGaussianBlurElement::sNumberPairInfo[1] = { + {nsGkAtoms::stdDeviation, 0, 0}}; + +SVGElement::StringInfo SVGFEGaussianBlurElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEGaussianBlurElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGFEGaussianBlurElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEGaussianBlurElement::StdDeviationX() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFEGaussianBlurElement::StdDeviationY() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEGaussianBlurElement::SetStdDeviation(float stdDeviationX, + float stdDeviationY) { + mNumberPairAttributes[STD_DEV].SetBaseValues(stdDeviationX, stdDeviationY, + this); +} + +FilterPrimitiveDescription SVGFEGaussianBlurElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + float stdX = aInstance->GetPrimitiveNumber(SVGContentUtils::X, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eFirst); + float stdY = aInstance->GetPrimitiveNumber(SVGContentUtils::Y, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eSecond); + if (stdX < 0 || stdY < 0) { + return FilterPrimitiveDescription(); + } + + GaussianBlurAttributes atts; + atts.mStdDeviation = Size(stdX, stdY); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEGaussianBlurElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEGaussianBlurElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || + aAttribute == nsGkAtoms::stdDeviation)); +} + +void SVGFEGaussianBlurElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +nsresult SVGFEGaussianBlurElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feGaussianBlur); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberPairAttributesInfo +SVGFEGaussianBlurElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::StringAttributesInfo SVGFEGaussianBlurElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEGaussianBlurElement.h b/dom/svg/SVGFEGaussianBlurElement.h new file mode 100644 index 0000000000..863abd3cd5 --- /dev/null +++ b/dom/svg/SVGFEGaussianBlurElement.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEGAUSSIANBLURELEMENT_H_ +#define DOM_SVG_SVGFEGAUSSIANBLURELEMENT_H_ + +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEGaussianBlurElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEGaussianBlurElementBase = SVGFE; + +class SVGFEGaussianBlurElement : public SVGFEGaussianBlurElementBase { + friend nsresult(::NS_NewSVGFEGaussianBlurElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEGaussianBlurElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEGaussianBlurElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedNumber> StdDeviationX(); + already_AddRefed<DOMSVGAnimatedNumber> StdDeviationY(); + void SetStdDeviation(float stdDeviationX, float stdDeviationY); + + protected: + NumberPairAttributesInfo GetNumberPairInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { STD_DEV }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEGAUSSIANBLURELEMENT_H_ diff --git a/dom/svg/SVGFEImageElement.cpp b/dom/svg/SVGFEImageElement.cpp new file mode 100644 index 0000000000..7aac0ad133 --- /dev/null +++ b/dom/svg/SVGFEImageElement.cpp @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEImageElement.h" + +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/SVGFEImageElementBinding.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" +#include "nsNetUtil.h" +#include "imgIContainer.h" +#include "gfx2DGlue.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEImage) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEImageElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEImageElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEImageElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGFEImageElement, SVGFEImageElementBase, + imgINotificationObserver, nsIImageLoadingContent) + +//---------------------------------------------------------------------- +// Implementation + +SVGFEImageElement::SVGFEImageElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEImageElementBase(std::move(aNodeInfo)), mImageAnimationMode(0) { + // We start out broken + AddStatesSilently(ElementState::BROKEN); +} + +SVGFEImageElement::~SVGFEImageElement() { nsImageLoadingContent::Destroy(); } + +//---------------------------------------------------------------------- + +nsresult SVGFEImageElement::LoadSVGImage(bool aForce, bool aNotify) { + // resolve href attribute + nsIURI* baseURI = GetBaseURI(); + + nsAutoString href; + if (mStringAttributes[HREF].IsExplicitlySet()) { + mStringAttributes[HREF].GetAnimValue(href, this); + } else { + mStringAttributes[XLINK_HREF].GetAnimValue(href, this); + } + href.Trim(" \t\n\r"); + + if (baseURI && !href.IsEmpty()) NS_MakeAbsoluteURI(href, href, baseURI); + + // Make sure we don't get in a recursive death-spiral + Document* doc = OwnerDoc(); + nsCOMPtr<nsIURI> hrefAsURI; + if (NS_SUCCEEDED(StringToURI(href, doc, getter_AddRefs(hrefAsURI)))) { + bool isEqual; + if (NS_SUCCEEDED(hrefAsURI->Equals(baseURI, &isEqual)) && isEqual) { + // Image URI matches our URI exactly! Bail out. + return NS_OK; + } + } + + // Mark channel as urgent-start before load image if the image load is + // initaiated by a user interaction. + mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); + return LoadImage(href, aForce, aNotify, eImageLoadType_Normal); +} + +bool SVGFEImageElement::ShouldLoadImage() const { + return LoadingEnabled() && OwnerDoc()->ShouldLoadImages(); +} + +//---------------------------------------------------------------------- +// EventTarget methods: + +void SVGFEImageElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { + nsImageLoadingContent::AsyncEventRunning(aEvent); +} + +//---------------------------------------------------------------------- +// nsIContent methods: + +NS_IMETHODIMP_(bool) +SVGFEImageElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sGraphicsMap}; + + return FindAttributeDependence(name, map) || + SVGFEImageElementBase::IsAttributeMapped(name); +} + +nsresult SVGFEImageElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (aName == nsGkAtoms::href && (aNamespaceID == kNameSpaceID_XLink || + aNamespaceID == kNameSpaceID_None)) { + if (aValue) { + if (ShouldLoadImage()) { + LoadSVGImage(true, aNotify); + } + } else { + CancelImageRequests(aNotify); + } + } + + return SVGFEImageElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +void SVGFEImageElement::MaybeLoadSVGImage() { + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + (NS_FAILED(LoadSVGImage(false, true)) || !LoadingEnabled())) { + CancelImageRequests(true); + } +} + +nsresult SVGFEImageElement::BindToTree(BindContext& aContext, + nsINode& aParent) { + nsresult rv = SVGFEImageElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsImageLoadingContent::BindToTree(aContext, aParent); + + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + ShouldLoadImage()) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod("dom::SVGFEImageElement::MaybeLoadSVGImage", this, + &SVGFEImageElement::MaybeLoadSVGImage)); + } + + if (aContext.InComposedDoc()) { + aContext.OwnerDoc().SetUseCounter(eUseCounter_custom_feImage); + } + + return rv; +} + +void SVGFEImageElement::UnbindFromTree(bool aNullParent) { + nsImageLoadingContent::UnbindFromTree(aNullParent); + SVGFEImageElementBase::UnbindFromTree(aNullParent); +} + +ElementState SVGFEImageElement::IntrinsicState() const { + return SVGFEImageElementBase::IntrinsicState() | + nsImageLoadingContent::ImageState(); +} + +void SVGFEImageElement::DestroyContent() { + nsImageLoadingContent::Destroy(); + SVGFEImageElementBase::DestroyContent(); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEImageElement) + +already_AddRefed<DOMSVGAnimatedString> SVGFEImageElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIDOMSVGFEImageElement methods + +FilterPrimitiveDescription SVGFEImageElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + nsIFrame* frame = GetPrimaryFrame(); + if (!frame) { + return FilterPrimitiveDescription(); + } + + nsCOMPtr<imgIRequest> currentRequest; + GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + + nsCOMPtr<imgIContainer> imageContainer; + if (currentRequest) { + currentRequest->GetImage(getter_AddRefs(imageContainer)); + } + + RefPtr<SourceSurface> image; + if (imageContainer) { + uint32_t flags = + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY; + image = imageContainer->GetFrame(imgIContainer::FRAME_CURRENT, flags); + } + + if (!image) { + return FilterPrimitiveDescription(); + } + + IntSize nativeSize; + imageContainer->GetWidth(&nativeSize.width); + imageContainer->GetHeight(&nativeSize.height); + + Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform( + aFilterSubregion.width, aFilterSubregion.height, 0, 0, nativeSize.width, + nativeSize.height, mPreserveAspectRatio); + Matrix TM = viewBoxTM; + TM.PostTranslate(aFilterSubregion.x, aFilterSubregion.y); + + SamplingFilter samplingFilter = + nsLayoutUtils::GetSamplingFilterForFrame(frame); + + ImageAttributes atts; + atts.mFilter = (uint32_t)samplingFilter; + atts.mTransform = TM; + + // Append the image to aInputImages and store its index in the description. + size_t imageIndex = aInputImages.Length(); + aInputImages.AppendElement(image); + atts.mInputIndex = (uint32_t)imageIndex; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEImageElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + // nsGkAtoms::href is deliberately omitted as the frame has special + // handling to load the image + return SVGFEImageElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::preserveAspectRatio); +} + +bool SVGFEImageElement::OutputIsTainted(const nsTArray<bool>& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal) { + nsresult rv; + nsCOMPtr<imgIRequest> currentRequest; + GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + + if (!currentRequest) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal; + rv = currentRequest->GetImagePrincipal(getter_AddRefs(principal)); + if (NS_FAILED(rv) || !principal) { + return true; + } + + int32_t corsmode; + if (NS_SUCCEEDED(currentRequest->GetCORSMode(&corsmode)) && + corsmode != CORS_NONE) { + // If CORS was used to load the image, the page is allowed to read from it. + return false; + } + + if (aReferencePrincipal->Subsumes(principal)) { + // The page is allowed to read from the image. + return false; + } + + return true; +} + +//---------------------------------------------------------------------- +// SVGElement methods + +already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> +SVGFEImageElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +SVGAnimatedPreserveAspectRatio* +SVGFEImageElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +SVGElement::StringAttributesInfo SVGFEImageElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//---------------------------------------------------------------------- +// nsIImageLoadingContent methods +NS_IMETHODIMP_(void) +SVGFEImageElement::FrameCreated(nsIFrame* aFrame) { + nsImageLoadingContent::FrameCreated(aFrame); + + uint64_t mode = aFrame->PresContext()->ImageAnimationMode(); + if (mode == mImageAnimationMode) { + return; + } + + mImageAnimationMode = mode; + + if (mPendingRequest) { + nsCOMPtr<imgIContainer> container; + mPendingRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->SetAnimationMode(mode); + } + } + + if (mCurrentRequest) { + nsCOMPtr<imgIContainer> container; + mCurrentRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->SetAnimationMode(mode); + } + } +} + +//---------------------------------------------------------------------- +// imgINotificationObserver methods + +void SVGFEImageElement::Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) { + nsImageLoadingContent::Notify(aRequest, aType, aData); + + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + // Request a decode + nsCOMPtr<imgIContainer> container; + aRequest->GetImage(getter_AddRefs(container)); + MOZ_ASSERT(container, "who sent the notification then?"); + container->StartDecoding(imgIContainer::FLAG_NONE); + container->SetAnimationMode(mImageAnimationMode); + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE || + aType == imgINotificationObserver::FRAME_UPDATE || + aType == imgINotificationObserver::SIZE_AVAILABLE) { + if (GetParent() && GetParent()->IsSVGElement(nsGkAtoms::filter)) { + SVGObserverUtils::InvalidateDirectRenderingObservers( + static_cast<SVGFilterElement*>(GetParent())); + } + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEImageElement.h b/dom/svg/SVGFEImageElement.h new file mode 100644 index 0000000000..5bd4cc5861 --- /dev/null +++ b/dom/svg/SVGFEImageElement.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEIMAGEELEMENT_H_ +#define DOM_SVG_SVGFEIMAGEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" +#include "SVGAnimatedPreserveAspectRatio.h" + +nsresult NS_NewSVGFEImageElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGFEImageFrame; + +namespace dom { + +using SVGFEImageElementBase = SVGFE; + +class SVGFEImageElement final : public SVGFEImageElementBase, + public nsImageLoadingContent { + friend class mozilla::SVGFEImageFrame; + + protected: + friend nsresult(::NS_NewSVGFEImageElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGFEImageElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + virtual ~SVGFEImageElement(); + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + // EventTarget + void AsyncEventRunning(AsyncEventDispatcher* aEvent) override; + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + bool OutputIsTainted(const nsTArray<bool>& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal) override; + + // nsIContent + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + ElementState IntrinsicState() const override; + void DestroyContent() override; + + NS_DECL_IMGINOTIFICATIONOBSERVER + + // Override for nsIImageLoadingContent. + NS_IMETHOD_(void) FrameCreated(nsIFrame* aFrame) override; + + void MaybeLoadSVGImage(); + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> Href(); + already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> PreserveAspectRatio(); + + private: + nsresult LoadSVGImage(bool aForce, bool aNotify); + bool ShouldLoadImage() const; + + protected: + bool ProducesSRGB() override { return true; } + + SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() override; + StringAttributesInfo GetStringInfo() override; + + // Override for nsImageLoadingContent. + nsIContent* AsContent() override { return this; } + + enum { RESULT, HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; + + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + uint16_t mImageAnimationMode; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFEIMAGEELEMENT_H_ diff --git a/dom/svg/SVGFEMergeElement.cpp b/dom/svg/SVGFEMergeElement.cpp new file mode 100644 index 0000000000..98ccc95e51 --- /dev/null +++ b/dom/svg/SVGFEMergeElement.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEMergeElement.h" +#include "mozilla/dom/SVGFEMergeElementBinding.h" +#include "mozilla/dom/SVGFEMergeNodeElement.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEMerge) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEMergeElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEMergeElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEMergeElement::sStringInfo[1] = { + {nsGkAtoms::result, kNameSpaceID_None, true}}; + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEMergeElement) + +FilterPrimitiveDescription SVGFEMergeElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + return FilterPrimitiveDescription(AsVariant(MergeAttributes())); +} + +void SVGFEMergeElement::GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) { + for (nsIContent* child = nsINode::GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsSVGElement(nsGkAtoms::feMergeNode)) { + SVGFEMergeNodeElement* node = static_cast<SVGFEMergeNodeElement*>(child); + aSources.AppendElement(SVGStringInfo(node->GetIn1(), node)); + } + } +} + +nsresult SVGFEMergeElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feMerge); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFEMergeElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEMergeElement.h b/dom/svg/SVGFEMergeElement.h new file mode 100644 index 0000000000..44c2f1e788 --- /dev/null +++ b/dom/svg/SVGFEMergeElement.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEMERGEELEMENT_H_ +#define DOM_SVG_SVGFEMERGEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEMergeElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEMergeElementBase = SVGFE; + +class SVGFEMergeElement : public SVGFEMergeElementBase { + friend nsresult(::NS_NewSVGFEMergeElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEMergeElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEMergeElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { RESULT }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEMERGEELEMENT_H_ diff --git a/dom/svg/SVGFEMergeNodeElement.cpp b/dom/svg/SVGFEMergeNodeElement.cpp new file mode 100644 index 0000000000..21c07f6421 --- /dev/null +++ b/dom/svg/SVGFEMergeNodeElement.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEMergeNodeElement.h" +#include "mozilla/dom/SVGFEMergeNodeElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEMergeNode) + +namespace mozilla::dom { + +JSObject* SVGFEMergeNodeElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEMergeNodeElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEMergeNodeElement::sStringInfo[1] = { + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEMergeNodeElement) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGFEMergeNodeElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::in; +} + +already_AddRefed<DOMSVGAnimatedString> SVGFEMergeNodeElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFEMergeNodeElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEMergeNodeElement.h b/dom/svg/SVGFEMergeNodeElement.h new file mode 100644 index 0000000000..723a0fed6c --- /dev/null +++ b/dom/svg/SVGFEMergeNodeElement.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEMERGENODEELEMENT_H_ +#define DOM_SVG_SVGFEMERGENODEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEMergeNodeElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEMergeNodeElementBase = SVGFEUnstyledElement; + +class SVGFEMergeNodeElement : public SVGFEMergeNodeElementBase { + friend nsresult(::NS_NewSVGFEMergeNodeElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEMergeNodeElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEMergeNodeElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + const SVGAnimatedString* GetIn1() { return &mStringAttributes[IN1]; } + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { IN1 }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEMERGENODEELEMENT_H_ diff --git a/dom/svg/SVGFEMorphologyElement.cpp b/dom/svg/SVGFEMorphologyElement.cpp new file mode 100644 index 0000000000..1ba882c06c --- /dev/null +++ b/dom/svg/SVGFEMorphologyElement.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEMorphologyElement.h" +#include "mozilla/dom/SVGFEMorphologyElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/Document.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEMorphology) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEMorphologyElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEMorphologyElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberPairInfo SVGFEMorphologyElement::sNumberPairInfo[1] = { + {nsGkAtoms::radius, 0, 0}}; + +SVGEnumMapping SVGFEMorphologyElement::sOperatorMap[] = { + {nsGkAtoms::erode, SVG_OPERATOR_ERODE}, + {nsGkAtoms::dilate, SVG_OPERATOR_DILATE}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEMorphologyElement::sEnumInfo[1] = { + {nsGkAtoms::_operator, sOperatorMap, SVG_OPERATOR_ERODE}}; + +SVGElement::StringInfo SVGFEMorphologyElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEMorphologyElement) + +//---------------------------------------------------------------------- +// SVGFEMorphologyElement methods + +already_AddRefed<DOMSVGAnimatedString> SVGFEMorphologyElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGFEMorphologyElement::Operator() { + return mEnumAttributes[OPERATOR].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEMorphologyElement::RadiusX() { + return mNumberPairAttributes[RADIUS].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEMorphologyElement::RadiusY() { + return mNumberPairAttributes[RADIUS].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEMorphologyElement::SetRadius(float rx, float ry) { + mNumberPairAttributes[RADIUS].SetBaseValues(rx, ry, this); +} + +void SVGFEMorphologyElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +#define MORPHOLOGY_EPSILON 0.0001 + +void SVGFEMorphologyElement::GetRXY(int32_t* aRX, int32_t* aRY, + const SVGFilterInstance& aInstance) { + // Subtract an epsilon here because we don't want a value that's just + // slightly larger than an integer to round up to the next integer; it's + // probably meant to be the integer it's close to, modulo machine precision + // issues. + *aRX = NSToIntCeil(aInstance.GetPrimitiveNumber( + SVGContentUtils::X, &mNumberPairAttributes[RADIUS], + SVGAnimatedNumberPair::eFirst) - + MORPHOLOGY_EPSILON); + *aRY = NSToIntCeil(aInstance.GetPrimitiveNumber( + SVGContentUtils::Y, &mNumberPairAttributes[RADIUS], + SVGAnimatedNumberPair::eSecond) - + MORPHOLOGY_EPSILON); +} + +FilterPrimitiveDescription SVGFEMorphologyElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + int32_t rx, ry; + GetRXY(&rx, &ry, *aInstance); + MorphologyAttributes atts; + atts.mRadii = Size(rx, ry); + atts.mOperator = (uint32_t)mEnumAttributes[OPERATOR].GetAnimValue(); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEMorphologyElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEMorphologyElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::radius || + aAttribute == nsGkAtoms::_operator)); +} + +nsresult SVGFEMorphologyElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feMorphology); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberPairAttributesInfo +SVGFEMorphologyElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::EnumAttributesInfo SVGFEMorphologyElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEMorphologyElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEMorphologyElement.h b/dom/svg/SVGFEMorphologyElement.h new file mode 100644 index 0000000000..7212955802 --- /dev/null +++ b/dom/svg/SVGFEMorphologyElement.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEMORPHOLOGYELEMENT_H_ +#define DOM_SVG_SVGFEMORPHOLOGYELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEMorphologyElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEMorphologyElementBase = SVGFE; + +class SVGFEMorphologyElement : public SVGFEMorphologyElementBase { + friend nsresult(::NS_NewSVGFEMorphologyElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEMorphologyElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEMorphologyElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedEnumeration> Operator(); + already_AddRefed<DOMSVGAnimatedNumber> RadiusX(); + already_AddRefed<DOMSVGAnimatedNumber> RadiusY(); + void SetRadius(float rx, float ry); + + protected: + void GetRXY(int32_t* aRX, int32_t* aRY, const SVGFilterInstance& aInstance); + + NumberPairAttributesInfo GetNumberPairInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + void UpdateUseCounter() const; + + enum { RADIUS }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { OPERATOR }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sOperatorMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEMORPHOLOGYELEMENT_H_ diff --git a/dom/svg/SVGFEOffsetElement.cpp b/dom/svg/SVGFEOffsetElement.cpp new file mode 100644 index 0000000000..5ba5546d9f --- /dev/null +++ b/dom/svg/SVGFEOffsetElement.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEOffsetElement.h" +#include "mozilla/dom/SVGFEOffsetElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEOffset) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEOffsetElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEOffsetElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEOffsetElement::sNumberInfo[2] = { + {nsGkAtoms::dx, 0, false}, {nsGkAtoms::dy, 0, false}}; + +SVGElement::StringInfo SVGFEOffsetElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEOffsetElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedString> SVGFEOffsetElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEOffsetElement::Dx() { + return mNumberAttributes[DX].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEOffsetElement::Dy() { + return mNumberAttributes[DY].ToDOMAnimatedNumber(this); +} + +FilterPrimitiveDescription SVGFEOffsetElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + OffsetAttributes atts; + IntPoint offset(int32_t(aInstance->GetPrimitiveNumber( + SVGContentUtils::X, &mNumberAttributes[DX])), + int32_t(aInstance->GetPrimitiveNumber( + SVGContentUtils::Y, &mNumberAttributes[DY]))); + atts.mValue = offset; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEOffsetElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFEOffsetElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::dx || + aAttribute == nsGkAtoms::dy)); +} + +void SVGFEOffsetElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +nsresult SVGFEOffsetElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feOffset); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEOffsetElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::StringAttributesInfo SVGFEOffsetElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEOffsetElement.h b/dom/svg/SVGFEOffsetElement.h new file mode 100644 index 0000000000..03d9c58863 --- /dev/null +++ b/dom/svg/SVGFEOffsetElement.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEOFFSETELEMENT_H_ +#define DOM_SVG_SVGFEOFFSETELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEOffsetElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEOffsetElementBase = SVGFE; + +class SVGFEOffsetElement : public SVGFEOffsetElementBase { + friend nsresult(::NS_NewSVGFEOffsetElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEOffsetElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEOffsetElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedNumber> Dx(); + already_AddRefed<DOMSVGAnimatedNumber> Dy(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { DX, DY }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEOFFSETELEMENT_H_ diff --git a/dom/svg/SVGFEPointLightElement.cpp b/dom/svg/SVGFEPointLightElement.cpp new file mode 100644 index 0000000000..b8a8196f11 --- /dev/null +++ b/dom/svg/SVGFEPointLightElement.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEPointLightElement.h" +#include "mozilla/dom/SVGFEPointLightElementBinding.h" +#include "mozilla/SVGFilterInstance.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEPointLight) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEPointLightElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEPointLightElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEPointLightElement::sNumberInfo[3] = { + {nsGkAtoms::x, 0, false}, + {nsGkAtoms::y, 0, false}, + {nsGkAtoms::z, 0, false}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEPointLightElement) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGFEPointLightElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::z); +} + +//---------------------------------------------------------------------- + +LightType SVGFEPointLightElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) { + Point3D lightPos; + GetAnimatedNumberValues(&lightPos.x, &lightPos.y, &lightPos.z, nullptr); + lightPos = aInstance->ConvertLocation(lightPos); + aFloatAttributes.SetLength(kPointLightNumAttributes); + aFloatAttributes[kPointLightPositionXIndex] = lightPos.x; + aFloatAttributes[kPointLightPositionYIndex] = lightPos.y; + aFloatAttributes[kPointLightPositionZIndex] = lightPos.z; + return LightType::Point; +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEPointLightElement::X() { + return mNumberAttributes[ATTR_X].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEPointLightElement::Y() { + return mNumberAttributes[ATTR_Y].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFEPointLightElement::Z() { + return mNumberAttributes[ATTR_Z].ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEPointLightElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEPointLightElement.h b/dom/svg/SVGFEPointLightElement.h new file mode 100644 index 0000000000..9e0afeb8a9 --- /dev/null +++ b/dom/svg/SVGFEPointLightElement.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEPOINTLIGHTELEMENT_H_ +#define DOM_SVG_SVGFEPOINTLIGHTELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEPointLightElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEPointLightElementBase = SVGFELightElement; + +class SVGFEPointLightElement : public SVGFEPointLightElementBase { + friend nsresult(::NS_NewSVGFEPointLightElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFEPointLightElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEPointLightElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedNumber> X(); + already_AddRefed<DOMSVGAnimatedNumber> Y(); + already_AddRefed<DOMSVGAnimatedNumber> Z(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_Z }; + SVGAnimatedNumber mNumberAttributes[3]; + static NumberInfo sNumberInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEPOINTLIGHTELEMENT_H_ diff --git a/dom/svg/SVGFESpecularLightingElement.cpp b/dom/svg/SVGFESpecularLightingElement.cpp new file mode 100644 index 0000000000..ab1559ae68 --- /dev/null +++ b/dom/svg/SVGFESpecularLightingElement.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFESpecularLightingElement.h" +#include "mozilla/dom/SVGFESpecularLightingElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FESpecularLighting) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFESpecularLightingElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGFESpecularLightingElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFESpecularLightingElement) + +already_AddRefed<DOMSVGAnimatedString> SVGFESpecularLightingElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFESpecularLightingElement::SurfaceScale() { + return mNumberAttributes[SURFACE_SCALE].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFESpecularLightingElement::SpecularConstant() { + return mNumberAttributes[SPECULAR_CONSTANT].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFESpecularLightingElement::SpecularExponent() { + return mNumberAttributes[SPECULAR_EXPONENT].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFESpecularLightingElement::KernelUnitLengthX() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFESpecularLightingElement::KernelUnitLengthY() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +FilterPrimitiveDescription +SVGFESpecularLightingElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + float specularExponent = mNumberAttributes[SPECULAR_EXPONENT].GetAnimValue(); + float specularConstant = mNumberAttributes[SPECULAR_CONSTANT].GetAnimValue(); + + // specification defined range (15.22) + if (specularExponent < 1 || specularExponent > 128) { + return FilterPrimitiveDescription(); + } + + SpecularLightingAttributes atts; + atts.mLightingConstant = specularConstant; + atts.mSpecularExponent = specularExponent; + if (!AddLightingAttributes(static_cast<DiffuseLightingAttributes*>(&atts), + aInstance)) { + return FilterPrimitiveDescription(); + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFESpecularLightingElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFESpecularLightingElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::specularConstant || + aAttribute == nsGkAtoms::specularExponent)); +} + +nsresult SVGFESpecularLightingElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feSpecularLighting); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFESpecularLightingElement.h b/dom/svg/SVGFESpecularLightingElement.h new file mode 100644 index 0000000000..67c7ad3fee --- /dev/null +++ b/dom/svg/SVGFESpecularLightingElement.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFESPECULARLIGHTINGELEMENT_H_ +#define DOM_SVG_SVGFESPECULARLIGHTINGELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFESpecularLightingElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +//---------------------SpecularLighting------------------------ + +using SVGFESpecularLightingElementBase = SVGFELightingElement; + +class SVGFESpecularLightingElement : public SVGFESpecularLightingElementBase { + friend nsresult(::NS_NewSVGFESpecularLightingElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFESpecularLightingElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFESpecularLightingElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + already_AddRefed<DOMSVGAnimatedNumber> SurfaceScale(); + already_AddRefed<DOMSVGAnimatedNumber> SpecularConstant(); + already_AddRefed<DOMSVGAnimatedNumber> SpecularExponent(); + already_AddRefed<DOMSVGAnimatedNumber> KernelUnitLengthX(); + already_AddRefed<DOMSVGAnimatedNumber> KernelUnitLengthY(); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFESPECULARLIGHTINGELEMENT_H_ diff --git a/dom/svg/SVGFESpotLightElement.cpp b/dom/svg/SVGFESpotLightElement.cpp new file mode 100644 index 0000000000..aa771952b3 --- /dev/null +++ b/dom/svg/SVGFESpotLightElement.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFESpotLightElement.h" +#include "mozilla/dom/SVGFESpotLightElementBinding.h" +#include "mozilla/SVGFilterInstance.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FESpotLight) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFESpotLightElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFESpotLightElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFESpotLightElement::sNumberInfo[8] = { + {nsGkAtoms::x, 0, false}, + {nsGkAtoms::y, 0, false}, + {nsGkAtoms::z, 0, false}, + {nsGkAtoms::pointsAtX, 0, false}, + {nsGkAtoms::pointsAtY, 0, false}, + {nsGkAtoms::pointsAtZ, 0, false}, + {nsGkAtoms::specularExponent, 1, false}, + {nsGkAtoms::limitingConeAngle, 0, false}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFESpotLightElement) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGFESpotLightElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::z || aAttribute == nsGkAtoms::pointsAtX || + aAttribute == nsGkAtoms::pointsAtY || + aAttribute == nsGkAtoms::pointsAtZ || + aAttribute == nsGkAtoms::specularExponent || + aAttribute == nsGkAtoms::limitingConeAngle); +} + +//---------------------------------------------------------------------- + +LightType SVGFESpotLightElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) { + aFloatAttributes.SetLength(kSpotLightNumAttributes); + GetAnimatedNumberValues(&aFloatAttributes[kSpotLightPositionXIndex], + &aFloatAttributes[kSpotLightPositionYIndex], + &aFloatAttributes[kSpotLightPositionZIndex], + &aFloatAttributes[kSpotLightPointsAtXIndex], + &aFloatAttributes[kSpotLightPointsAtYIndex], + &aFloatAttributes[kSpotLightPointsAtZIndex], + &aFloatAttributes[kSpotLightFocusIndex], + &aFloatAttributes[kSpotLightLimitingConeAngleIndex], + nullptr); + if (!mNumberAttributes[SVGFESpotLightElement::LIMITING_CONE_ANGLE] + .IsExplicitlySet()) { + aFloatAttributes[kSpotLightLimitingConeAngleIndex] = 90; + } + + return LightType::Spot; +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFESpotLightElement::X() { + return mNumberAttributes[ATTR_X].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFESpotLightElement::Y() { + return mNumberAttributes[ATTR_Y].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFESpotLightElement::Z() { + return mNumberAttributes[ATTR_Z].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFESpotLightElement::PointsAtX() { + return mNumberAttributes[POINTS_AT_X].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFESpotLightElement::PointsAtY() { + return mNumberAttributes[POINTS_AT_Y].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFESpotLightElement::PointsAtZ() { + return mNumberAttributes[POINTS_AT_Z].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFESpotLightElement::SpecularExponent() { + return mNumberAttributes[SPECULAR_EXPONENT].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFESpotLightElement::LimitingConeAngle() { + return mNumberAttributes[LIMITING_CONE_ANGLE].ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFESpotLightElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFESpotLightElement.h b/dom/svg/SVGFESpotLightElement.h new file mode 100644 index 0000000000..f3ebd1cc7b --- /dev/null +++ b/dom/svg/SVGFESpotLightElement.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFESPOTLIGHTELEMENT_H_ +#define DOM_SVG_SVGFESPOTLIGHTELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFESpotLightElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFESpotLightElementBase = SVGFELightElement; + +class SVGFESpotLightElement : public SVGFESpotLightElementBase { + friend nsresult(::NS_NewSVGFESpotLightElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + friend class SVGFELightingElement; + + protected: + explicit SVGFESpotLightElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFESpotLightElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedNumber> X(); + already_AddRefed<DOMSVGAnimatedNumber> Y(); + already_AddRefed<DOMSVGAnimatedNumber> Z(); + already_AddRefed<DOMSVGAnimatedNumber> PointsAtX(); + already_AddRefed<DOMSVGAnimatedNumber> PointsAtY(); + already_AddRefed<DOMSVGAnimatedNumber> PointsAtZ(); + already_AddRefed<DOMSVGAnimatedNumber> SpecularExponent(); + already_AddRefed<DOMSVGAnimatedNumber> LimitingConeAngle(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + + enum { + ATTR_X, + ATTR_Y, + ATTR_Z, + POINTS_AT_X, + POINTS_AT_Y, + POINTS_AT_Z, + SPECULAR_EXPONENT, + LIMITING_CONE_ANGLE + }; + SVGAnimatedNumber mNumberAttributes[8]; + static NumberInfo sNumberInfo[8]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFESPOTLIGHTELEMENT_H_ diff --git a/dom/svg/SVGFETileElement.cpp b/dom/svg/SVGFETileElement.cpp new file mode 100644 index 0000000000..51781ec7d2 --- /dev/null +++ b/dom/svg/SVGFETileElement.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFETileElement.h" +#include "mozilla/dom/SVGFETileElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FETile) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFETileElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFETileElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFETileElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFETileElement) + +already_AddRefed<DOMSVGAnimatedString> SVGFETileElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +void SVGFETileElement::GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +FilterPrimitiveDescription SVGFETileElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + return FilterPrimitiveDescription(AsVariant(TileAttributes())); +} + +bool SVGFETileElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFETileElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::in); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFETileElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +nsresult SVGFETileElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feTile); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFETileElement.h b/dom/svg/SVGFETileElement.h new file mode 100644 index 0000000000..2de67fc1aa --- /dev/null +++ b/dom/svg/SVGFETileElement.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFETILEELEMENT_H_ +#define DOM_SVG_SVGFETILEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFETileElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFETileElementBase = SVGFE; + +class SVGFETileElement : public SVGFETileElementBase { + friend nsresult(::NS_NewSVGFETileElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFETileElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFETileElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> In1(); + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFETILEELEMENT_H_ diff --git a/dom/svg/SVGFETurbulenceElement.cpp b/dom/svg/SVGFETurbulenceElement.cpp new file mode 100644 index 0000000000..bd92766c44 --- /dev/null +++ b/dom/svg/SVGFETurbulenceElement.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFETurbulenceElement.h" +#include "mozilla/dom/SVGFETurbulenceElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FETurbulence) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +// Stitch Options +static const unsigned short SVG_STITCHTYPE_STITCH = 1; +static const unsigned short SVG_STITCHTYPE_NOSTITCH = 2; + +static const int32_t MAX_OCTAVES = 10; + +JSObject* SVGFETurbulenceElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFETurbulenceElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFETurbulenceElement::sNumberInfo[1] = { + {nsGkAtoms::seed, 0, false}}; + +SVGElement::NumberPairInfo SVGFETurbulenceElement::sNumberPairInfo[1] = { + {nsGkAtoms::baseFrequency, 0, 0}}; + +SVGElement::IntegerInfo SVGFETurbulenceElement::sIntegerInfo[1] = { + {nsGkAtoms::numOctaves, 1}}; + +SVGEnumMapping SVGFETurbulenceElement::sTypeMap[] = { + {nsGkAtoms::fractalNoise, SVG_TURBULENCE_TYPE_FRACTALNOISE}, + {nsGkAtoms::turbulence, SVG_TURBULENCE_TYPE_TURBULENCE}, + {nullptr, 0}}; + +SVGEnumMapping SVGFETurbulenceElement::sStitchTilesMap[] = { + {nsGkAtoms::stitch, SVG_STITCHTYPE_STITCH}, + {nsGkAtoms::noStitch, SVG_STITCHTYPE_NOSTITCH}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFETurbulenceElement::sEnumInfo[2] = { + {nsGkAtoms::type, sTypeMap, SVG_TURBULENCE_TYPE_TURBULENCE}, + {nsGkAtoms::stitchTiles, sStitchTilesMap, SVG_STITCHTYPE_NOSTITCH}}; + +SVGElement::StringInfo SVGFETurbulenceElement::sStringInfo[1] = { + {nsGkAtoms::result, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFETurbulenceElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFETurbulenceElement::BaseFrequencyX() { + return mNumberPairAttributes[BASE_FREQ].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGFETurbulenceElement::BaseFrequencyY() { + return mNumberPairAttributes[BASE_FREQ].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +already_AddRefed<DOMSVGAnimatedInteger> SVGFETurbulenceElement::NumOctaves() { + return mIntegerAttributes[OCTAVES].ToDOMAnimatedInteger(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGFETurbulenceElement::Seed() { + return mNumberAttributes[SEED].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGFETurbulenceElement::StitchTiles() { + return mEnumAttributes[STITCHTILES].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGFETurbulenceElement::Type() { + return mEnumAttributes[TYPE].ToDOMAnimatedEnum(this); +} + +FilterPrimitiveDescription SVGFETurbulenceElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + float fX = mNumberPairAttributes[BASE_FREQ].GetAnimValue( + SVGAnimatedNumberPair::eFirst); + float fY = mNumberPairAttributes[BASE_FREQ].GetAnimValue( + SVGAnimatedNumberPair::eSecond); + float seed = mNumberAttributes[OCTAVES].GetAnimValue(); + uint32_t octaves = + clamped(mIntegerAttributes[OCTAVES].GetAnimValue(), 0, MAX_OCTAVES); + uint32_t type = mEnumAttributes[TYPE].GetAnimValue(); + uint16_t stitch = mEnumAttributes[STITCHTILES].GetAnimValue(); + + if (fX == 0 && fY == 0) { + // A base frequency of zero results in transparent black for + // type="turbulence" and in 50% alpha 50% gray for type="fractalNoise". + if (type == SVG_TURBULENCE_TYPE_TURBULENCE) { + return FilterPrimitiveDescription(); + } + FloodAttributes atts; + atts.mColor = sRGBColor(0.5, 0.5, 0.5, 0.5); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); + } + + // We interpret the base frequency as relative to user space units. In other + // words, we consider one turbulence base period to be 1 / fX user space + // units wide and 1 / fY user space units high. We do not scale the frequency + // depending on the filter primitive region. + // We now convert the frequency from user space to filter space. + // If a frequency in user space units is zero, then it will also be zero in + // filter space. During the conversion we use a dummy period length of 1 + // for those frequencies but then ignore the converted length and use 0 + // for the converted frequency. This avoids division by zero. + gfxRect firstPeriodInUserSpace(0, 0, fX == 0 ? 1 : (1 / fX), + fY == 0 ? 1 : (1 / fY)); + gfxRect firstPeriodInFilterSpace = + aInstance->UserSpaceToFilterSpace(firstPeriodInUserSpace); + Size frequencyInFilterSpace( + fX == 0 ? 0 : (1 / firstPeriodInFilterSpace.width), + fY == 0 ? 0 : (1 / firstPeriodInFilterSpace.height)); + gfxPoint offset = firstPeriodInFilterSpace.TopLeft(); + + TurbulenceAttributes atts; + atts.mOffset = IntPoint::Truncate(offset.x, offset.y); + atts.mBaseFrequency = frequencyInFilterSpace; + atts.mSeed = seed; + atts.mOctaves = octaves; + atts.mStitchable = stitch == SVG_STITCHTYPE_STITCH; + atts.mType = type; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFETurbulenceElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFETurbulenceElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::seed || + aAttribute == nsGkAtoms::baseFrequency || + aAttribute == nsGkAtoms::numOctaves || + aAttribute == nsGkAtoms::type || + aAttribute == nsGkAtoms::stitchTiles)); +} + +nsresult SVGFETurbulenceElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feTurbulence); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFETurbulenceElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo +SVGFETurbulenceElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::IntegerAttributesInfo SVGFETurbulenceElement::GetIntegerInfo() { + return IntegerAttributesInfo(mIntegerAttributes, sIntegerInfo, + ArrayLength(sIntegerInfo)); +} + +SVGElement::EnumAttributesInfo SVGFETurbulenceElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFETurbulenceElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFETurbulenceElement.h b/dom/svg/SVGFETurbulenceElement.h new file mode 100644 index 0000000000..b2ccc2fcfa --- /dev/null +++ b/dom/svg/SVGFETurbulenceElement.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFETURBULENCEELEMENT_H_ +#define DOM_SVG_SVGFETURBULENCEELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedInteger.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFETurbulenceElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFETurbulenceElementBase = SVGFE; + +class SVGFETurbulenceElement : public SVGFETurbulenceElementBase { + friend nsresult(::NS_NewSVGFETurbulenceElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGFETurbulenceElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFETurbulenceElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedNumber> BaseFrequencyX(); + already_AddRefed<DOMSVGAnimatedNumber> BaseFrequencyY(); + already_AddRefed<DOMSVGAnimatedInteger> NumOctaves(); + already_AddRefed<DOMSVGAnimatedNumber> Seed(); + already_AddRefed<DOMSVGAnimatedEnumeration> StitchTiles(); + already_AddRefed<DOMSVGAnimatedEnumeration> Type(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + IntegerAttributesInfo GetIntegerInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { SEED }; // floating point seed?! + SVGAnimatedNumber mNumberAttributes[1]; + static NumberInfo sNumberInfo[1]; + + enum { BASE_FREQ }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { OCTAVES }; + SVGAnimatedInteger mIntegerAttributes[1]; + static IntegerInfo sIntegerInfo[1]; + + enum { TYPE, STITCHTILES }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static SVGEnumMapping sTypeMap[]; + static SVGEnumMapping sStitchTilesMap[]; + static EnumInfo sEnumInfo[2]; + + enum { RESULT }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFETURBULENCEELEMENT_H_ diff --git a/dom/svg/SVGFilterElement.cpp b/dom/svg/SVGFilterElement.cpp new file mode 100644 index 0000000000..ced8b71b31 --- /dev/null +++ b/dom/svg/SVGFilterElement.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFilterElement.h" + +#include "nsGkAtoms.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGFilterElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "nsQueryObject.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Filter) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGFilterElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFilterElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGFilterElement::sLengthInfo[4] = { + {nsGkAtoms::x, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +SVGElement::EnumInfo SVGFilterElement::sEnumInfo[2] = { + {nsGkAtoms::filterUnits, sSVGUnitTypesMap, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::primitiveUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_USERSPACEONUSE}}; + +SVGElement::StringInfo SVGFilterElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGFilterElement::SVGFilterElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFilterElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFilterElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGFilterElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGFilterElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGFilterElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGFilterElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGFilterElement::FilterUnits() { + return mEnumAttributes[FILTERUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGFilterElement::PrimitiveUnits() { + return mEnumAttributes[PRIMITIVEUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGFilterElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGFilterElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = { + sColorMap, sFEFloodMap, + sFiltersMap, sFontSpecificationMap, + sGradientStopMap, sLightingEffectsMap, + sMarkersMap, sTextContentElementsMap, + sViewportsMap}; + return FindAttributeDependence(name, map) || + SVGFilterElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGFilterElement::HasValidDimensions() const { + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGFilterElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGFilterElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFilterElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFilterElement.h b/dom/svg/SVGFilterElement.h new file mode 100644 index 0000000000..9fe2e182c8 --- /dev/null +++ b/dom/svg/SVGFilterElement.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFILTERELEMENT_H_ +#define DOM_SVG_SVGFILTERELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGElement.h" + +nsresult NS_NewSVGFilterElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGFilterFrame; +class SVGFilterInstance; + +namespace dom { +class DOMSVGAnimatedLength; + +using SVGFilterElementBase = SVGElement; + +class SVGFilterElement : public SVGFilterElementBase { + friend class mozilla::SVGFilterFrame; + friend class mozilla::SVGFilterInstance; + + protected: + friend nsresult(::NS_NewSVGFilterElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGFilterElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + already_AddRefed<DOMSVGAnimatedEnumeration> FilterUnits(); + already_AddRefed<DOMSVGAnimatedEnumeration> PrimitiveUnits(); + already_AddRefed<DOMSVGAnimatedString> Href(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { FILTERUNITS, PRIMITIVEUNITS }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static EnumInfo sEnumInfo[2]; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFILTERELEMENT_H_ diff --git a/dom/svg/SVGFilters.cpp b/dom/svg/SVGFilters.cpp new file mode 100644 index 0000000000..2caeec04d3 --- /dev/null +++ b/dom/svg/SVGFilters.cpp @@ -0,0 +1,470 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGFilters.h" + +#include <algorithm> +#include "DOMSVGAnimatedNumberList.h" +#include "DOMSVGAnimatedLength.h" +#include "nsGkAtoms.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "SVGNumberList.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/SVGComponentTransferFunctionElement.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGFEDistantLightElement.h" +#include "mozilla/dom/SVGFEFuncAElementBinding.h" +#include "mozilla/dom/SVGFEFuncBElementBinding.h" +#include "mozilla/dom/SVGFEFuncGElementBinding.h" +#include "mozilla/dom/SVGFEFuncRElementBinding.h" +#include "mozilla/dom/SVGFEPointLightElement.h" +#include "mozilla/dom/SVGFESpotLightElement.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "mozilla/dom/SVGLengthBinding.h" + +#if defined(XP_WIN) +// Prevent Windows redefining LoadImage +# undef LoadImage +#endif + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +//--------------------Filter Element Base Class----------------------- + +SVGElement::LengthInfo SVGFE::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGFE, SVGFEBase) +NS_IMPL_RELEASE_INHERITED(SVGFE, SVGFEBase) + +NS_INTERFACE_MAP_BEGIN(SVGFE) + NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGFE) +NS_INTERFACE_MAP_END_INHERITING(SVGFEBase) + +//---------------------------------------------------------------------- +// Implementation + +void SVGFE::GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) {} + +bool SVGFE::OutputIsTainted(const nsTArray<bool>& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal) { + // This is the default implementation for OutputIsTainted. + // Our output is tainted if we have at least one tainted input. + for (uint32_t i = 0; i < aInputsAreTainted.Length(); i++) { + if (aInputsAreTainted[i]) { + return true; + } + } + return false; +} + +bool SVGFE::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || + aAttribute == nsGkAtoms::result); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGFE::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGFE::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGFE::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGFE::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGFE::Result() { + return GetResultImageName().ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGFE::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFiltersMap}; + + return FindAttributeDependence(name, map) || + SVGFEBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +bool SVGFE::StyleIsSetToSRGB() { + nsIFrame* frame = GetPrimaryFrame(); + if (!frame) return false; + + ComputedStyle* style = frame->Style(); + return style->StyleSVG()->mColorInterpolationFilters == + StyleColorInterpolation::Srgb; +} + +/* virtual */ +bool SVGFE::HasValidDimensions() const { + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +Size SVGFE::GetKernelUnitLength(SVGFilterInstance* aInstance, + SVGAnimatedNumberPair* aKernelUnitLength) { + if (!aKernelUnitLength->IsExplicitlySet()) { + return Size(1, 1); + } + + float kernelX = aInstance->GetPrimitiveNumber( + SVGContentUtils::X, aKernelUnitLength, SVGAnimatedNumberPair::eFirst); + float kernelY = aInstance->GetPrimitiveNumber( + SVGContentUtils::Y, aKernelUnitLength, SVGAnimatedNumberPair::eSecond); + return Size(kernelX, kernelY); +} + +SVGElement::LengthAttributesInfo SVGFE::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::NumberListInfo + SVGComponentTransferFunctionElement::sNumberListInfo[1] = { + {nsGkAtoms::tableValues}}; + +SVGElement::NumberInfo SVGComponentTransferFunctionElement::sNumberInfo[5] = { + {nsGkAtoms::slope, 1, false}, + {nsGkAtoms::intercept, 0, false}, + {nsGkAtoms::amplitude, 1, false}, + {nsGkAtoms::exponent, 1, false}, + {nsGkAtoms::offset, 0, false}}; + +SVGEnumMapping SVGComponentTransferFunctionElement::sTypeMap[] = { + {nsGkAtoms::identity, SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY}, + {nsGkAtoms::table, SVG_FECOMPONENTTRANSFER_TYPE_TABLE}, + {nsGkAtoms::discrete, SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE}, + {nsGkAtoms::linear, SVG_FECOMPONENTTRANSFER_TYPE_LINEAR}, + {nsGkAtoms::gamma, SVG_FECOMPONENTTRANSFER_TYPE_GAMMA}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGComponentTransferFunctionElement::sEnumInfo[1] = { + {nsGkAtoms::type, sTypeMap, SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGComponentTransferFunctionElement, + SVGComponentTransferFunctionElementBase) +NS_IMPL_RELEASE_INHERITED(SVGComponentTransferFunctionElement, + SVGComponentTransferFunctionElementBase) + +NS_INTERFACE_MAP_BEGIN(SVGComponentTransferFunctionElement) + NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGComponentTransferFunctionElement) +NS_INTERFACE_MAP_END_INHERITING(SVGComponentTransferFunctionElementBase) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGComponentTransferFunctionElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::tableValues || + aAttribute == nsGkAtoms::slope || + aAttribute == nsGkAtoms::intercept || + aAttribute == nsGkAtoms::amplitude || + aAttribute == nsGkAtoms::exponent || + aAttribute == nsGkAtoms::offset || aAttribute == nsGkAtoms::type); +} + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGComponentTransferFunctionElement::Type() { + return mEnumAttributes[TYPE].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedNumberList> +SVGComponentTransferFunctionElement::TableValues() { + return DOMSVGAnimatedNumberList::GetDOMWrapper( + &mNumberListAttributes[TABLEVALUES], this, TABLEVALUES); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGComponentTransferFunctionElement::Slope() { + return mNumberAttributes[SLOPE].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGComponentTransferFunctionElement::Intercept() { + return mNumberAttributes[INTERCEPT].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGComponentTransferFunctionElement::Amplitude() { + return mNumberAttributes[AMPLITUDE].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGComponentTransferFunctionElement::Exponent() { + return mNumberAttributes[EXPONENT].ToDOMAnimatedNumber(this); +} + +already_AddRefed<DOMSVGAnimatedNumber> +SVGComponentTransferFunctionElement::Offset() { + return mNumberAttributes[OFFSET].ToDOMAnimatedNumber(this); +} + +void SVGComponentTransferFunctionElement::ComputeAttributes( + int32_t aChannel, ComponentTransferAttributes& aAttributes) { + uint32_t type = mEnumAttributes[TYPE].GetAnimValue(); + + float slope, intercept, amplitude, exponent, offset; + GetAnimatedNumberValues(&slope, &intercept, &litude, &exponent, &offset, + nullptr); + + const SVGNumberList& tableValues = + mNumberListAttributes[TABLEVALUES].GetAnimValue(); + + aAttributes.mTypes[aChannel] = (uint8_t)type; + switch (type) { + case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: { + aAttributes.mValues[aChannel].SetLength(2); + aAttributes.mValues[aChannel][kComponentTransferSlopeIndex] = slope; + aAttributes.mValues[aChannel][kComponentTransferInterceptIndex] = + intercept; + break; + } + case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: { + aAttributes.mValues[aChannel].SetLength(3); + aAttributes.mValues[aChannel][kComponentTransferAmplitudeIndex] = + amplitude; + aAttributes.mValues[aChannel][kComponentTransferExponentIndex] = exponent; + aAttributes.mValues[aChannel][kComponentTransferOffsetIndex] = offset; + break; + } + case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: + case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: { + if (!tableValues.IsEmpty()) { + aAttributes.mValues[aChannel].AppendElements(&tableValues[0], + tableValues.Length()); + } + break; + } + } +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberListAttributesInfo +SVGComponentTransferFunctionElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +SVGElement::EnumAttributesInfo +SVGComponentTransferFunctionElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::NumberAttributesInfo +SVGComponentTransferFunctionElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +/* virtual */ +JSObject* SVGFEFuncRElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEFuncRElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncR) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncRElement) + +/* virtual */ +JSObject* SVGFEFuncGElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEFuncGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncG) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncGElement) + +/* virtual */ +JSObject* SVGFEFuncBElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEFuncBElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncB) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncBElement) + +/* virtual */ +JSObject* SVGFEFuncAElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGFEFuncAElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncA) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncAElement) + +//-------------------------------------------------------------------- +// +SVGElement::NumberInfo SVGFELightingElement::sNumberInfo[4] = { + {nsGkAtoms::surfaceScale, 1, false}, + {nsGkAtoms::diffuseConstant, 1, false}, + {nsGkAtoms::specularConstant, 1, false}, + {nsGkAtoms::specularExponent, 1, false}}; + +SVGElement::NumberPairInfo SVGFELightingElement::sNumberPairInfo[1] = { + {nsGkAtoms::kernelUnitLength, 0, 0}}; + +SVGElement::StringInfo SVGFELightingElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// Implementation + +NS_IMETHODIMP_(bool) +SVGFELightingElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sColorMap, + sLightingEffectsMap}; + + return FindAttributeDependence(name, map) || + SVGFELightingElementBase::IsAttributeMapped(name); +} + +void SVGFELightingElement::GetSourceImageNames( + nsTArray<SVGStringInfo>& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +LightType SVGFELightingElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) { + // find specified light + for (nsCOMPtr<nsIContent> child = nsINode::GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsAnyOfSVGElements(nsGkAtoms::feDistantLight, + nsGkAtoms::fePointLight, + nsGkAtoms::feSpotLight)) { + return static_cast<SVGFELightElement*>(child.get()) + ->ComputeLightAttributes(aInstance, aFloatAttributes); + } + } + + return LightType::None; +} + +bool SVGFELightingElement::AddLightingAttributes( + mozilla::gfx::DiffuseLightingAttributes* aAttributes, + SVGFilterInstance* aInstance) { + nsIFrame* frame = GetPrimaryFrame(); + if (!frame) { + return false; + } + + const nsStyleSVGReset* styleSVGReset = frame->Style()->StyleSVGReset(); + sRGBColor color( + sRGBColor::FromABGR(styleSVGReset->mLightingColor.CalcColor(frame))); + color.a = 1.f; + float surfaceScale = mNumberAttributes[SURFACE_SCALE].GetAnimValue(); + Size kernelUnitLength = GetKernelUnitLength( + aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]); + + if (kernelUnitLength.width <= 0 || kernelUnitLength.height <= 0) { + // According to spec, A negative or zero value is an error. See link below + // for details. + // https://www.w3.org/TR/SVG/filters.html#feSpecularLightingKernelUnitLengthAttribute + return false; + } + + aAttributes->mLightType = + ComputeLightAttributes(aInstance, aAttributes->mLightValues); + aAttributes->mSurfaceScale = surfaceScale; + aAttributes->mKernelUnitLength = kernelUnitLength; + aAttributes->mColor = color; + + return true; +} + +bool SVGFELightingElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFELightingElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || + aAttribute == nsGkAtoms::surfaceScale || + aAttribute == nsGkAtoms::kernelUnitLength)); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFELightingElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo SVGFELightingElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::StringAttributesInfo SVGFELightingElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFilters.h b/dom/svg/SVGFilters.h new file mode 100644 index 0000000000..540281dd9b --- /dev/null +++ b/dom/svg/SVGFilters.h @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFILTERS_H_ +#define DOM_SVG_SVGFILTERS_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGElement.h" +#include "FilterDescription.h" +#include "nsImageLoadingContent.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" + +namespace mozilla { +class SVGFilterInstance; + +namespace dom { + +struct SVGStringInfo { + SVGStringInfo(const SVGAnimatedString* aString, SVGElement* aElement) + : mString(aString), mElement(aElement) {} + + const SVGAnimatedString* mString; + SVGElement* mElement; +}; + +using SVGFEBase = SVGElement; + +#define NS_SVG_FE_CID \ + { \ + 0x60483958, 0xd229, 0x4a77, { \ + 0x96, 0xb2, 0x62, 0x3e, 0x69, 0x95, 0x1e, 0x0e \ + } \ + } + +/** + * Base class for filter primitive elements + * Children of those elements e.g. feMergeNode + * derive from SVGFEUnstyledElement instead + */ +class SVGFE : public SVGFEBase { + friend class mozilla::SVGFilterInstance; + + protected: + using SourceSurface = mozilla::gfx::SourceSurface; + using Size = mozilla::gfx::Size; + using IntRect = mozilla::gfx::IntRect; + using ColorSpace = mozilla::gfx::ColorSpace; + using FilterPrimitiveDescription = mozilla::gfx::FilterPrimitiveDescription; + + explicit SVGFE(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEBase(std::move(aNodeInfo)) {} + virtual ~SVGFE() = default; + + public: + using PrimitiveAttributes = mozilla::gfx::PrimitiveAttributes; + + ColorSpace GetInputColorSpace(int32_t aInputIndex, + ColorSpace aUnchangedInputColorSpace) { + return OperatesOnSRGB(aInputIndex, + aUnchangedInputColorSpace == ColorSpace::SRGB) + ? ColorSpace::SRGB + : ColorSpace::LinearRGB; + } + + // This is only called for filter primitives without inputs. For primitives + // with inputs, the output color model is the same as of the first input. + ColorSpace GetOutputColorSpace() { + return ProducesSRGB() ? ColorSpace::SRGB : ColorSpace::LinearRGB; + } + + // See http://www.w3.org/TR/SVG/filters.html#FilterPrimitiveSubRegion + virtual bool SubregionIsUnionOfRegions() { return true; } + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_SVG_FE_CID) + + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGElement interface + nsresult Clone(mozilla::dom::NodeInfo*, nsINode** aResult) const override = 0; + + bool HasValidDimensions() const override; + + virtual SVGAnimatedString& GetResultImageName() = 0; + // Return a list of all image names used as sources. Default is to + // return no sources. + virtual void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources); + + virtual FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray<bool>& aInputsAreTainted, + nsTArray<RefPtr<SourceSurface>>& aInputImages) = 0; + + // returns true if changes to the attribute should cause us to + // repaint the filter + virtual bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const; + + // Return whether this filter primitive has tainted output. A filter's + // output is tainted if it depends on things that the web page is not + // allowed to read from, e.g. the source graphic or cross-origin images. + // aReferencePrincipal is the node principal of the filtered frame's element. + virtual bool OutputIsTainted(const nsTArray<bool>& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal); + + static nsIntRect GetMaxRect() { + // Try to avoid overflow errors dealing with this rect. It will + // be intersected with some other reasonable-sized rect eventually. + return nsIntRect(INT32_MIN / 2, INT32_MIN / 2, INT32_MAX, INT32_MAX); + } + + operator nsISupports*() { return static_cast<nsIContent*>(this); } + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + already_AddRefed<DOMSVGAnimatedString> Result(); + + protected: + virtual bool OperatesOnSRGB(int32_t aInputIndex, bool aInputIsAlreadySRGB) { + return StyleIsSetToSRGB(); + } + + // Only called for filter primitives without inputs. + virtual bool ProducesSRGB() { return StyleIsSetToSRGB(); } + + bool StyleIsSetToSRGB(); + + // SVGElement specializations: + LengthAttributesInfo GetLengthInfo() override; + + Size GetKernelUnitLength(SVGFilterInstance* aInstance, + SVGAnimatedNumberPair* aKernelUnitLength); + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGFE, NS_SVG_FE_CID) + +using SVGFEUnstyledElementBase = SVGElement; + +class SVGFEUnstyledElement : public SVGFEUnstyledElementBase { + protected: + explicit SVGFEUnstyledElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFEUnstyledElementBase(std::move(aNodeInfo)) {} + + public: + nsresult Clone(mozilla::dom::NodeInfo*, nsINode** aResult) const override = 0; + + // returns true if changes to the attribute should cause us to + // repaint the filter + virtual bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const = 0; +}; + +//------------------------------------------------------------ + +using SVGFELightingElementBase = SVGFE; + +class SVGFELightingElement : public SVGFELightingElementBase { + protected: + explicit SVGFELightingElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFELightingElementBase(std::move(aNodeInfo)) {} + + virtual ~SVGFELightingElement() = default; + + public: + // interfaces: + NS_INLINE_DECL_REFCOUNTING_INHERITED(SVGFELightingElement, + SVGFELightingElementBase) + + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray<SVGStringInfo>& aSources) override; + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + protected: + bool OperatesOnSRGB(int32_t aInputIndex, bool aInputIsAlreadySRGB) override { + return true; + } + + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + StringAttributesInfo GetStringInfo() override; + + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes); + + bool AddLightingAttributes( + mozilla::gfx::DiffuseLightingAttributes* aAttributes, + SVGFilterInstance* aInstance); + + enum { + SURFACE_SCALE, + DIFFUSE_CONSTANT, + SPECULAR_CONSTANT, + SPECULAR_EXPONENT + }; + SVGAnimatedNumber mNumberAttributes[4]; + static NumberInfo sNumberInfo[4]; + + enum { KERNEL_UNIT_LENGTH }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +using SVGFELightElementBase = SVGFEUnstyledElement; + +class SVGFELightElement : public SVGFELightElementBase { + protected: + explicit SVGFELightElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGFELightElementBase(std::move(aNodeInfo)) {} + + public: + using PrimitiveAttributes = gfx::PrimitiveAttributes; + + virtual mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) = 0; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFILTERS_H_ diff --git a/dom/svg/SVGForeignObjectElement.cpp b/dom/svg/SVGForeignObjectElement.cpp new file mode 100644 index 0000000000..02d92d401e --- /dev/null +++ b/dom/svg/SVGForeignObjectElement.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGForeignObjectElement.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGDocument.h" +#include "mozilla/dom/SVGForeignObjectElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(ForeignObject) + +namespace mozilla::dom { + +JSObject* SVGForeignObjectElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGForeignObjectElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGForeignObjectElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGForeignObjectElement::SVGForeignObjectElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGraphicsElement(std::move(aNodeInfo)) {} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGForeignObjectElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGForeignObjectElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGForeignObjectElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGForeignObjectElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGForeignObjectElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +gfxMatrix SVGForeignObjectElement::PrependLocalTransformsTo( + const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const { + // 'transform' attribute: + gfxMatrix fromUserSpace = + SVGGraphicsElement::PrependLocalTransformsTo(aMatrix, aWhich); + if (aWhich == eUserSpaceToParent) { + return fromUserSpace; + } + // our 'x' and 'y' attributes: + float x, y; + + if (!SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(this, &x, &y)) { + // This function might be called for element in display:none subtree + // (e.g. getScreenCTM), we fall back to use SVG attributes. + const_cast<SVGForeignObjectElement*>(this)->GetAnimatedLengthValues( + &x, &y, nullptr); + } + + gfxMatrix toUserSpace = gfxMatrix::Translation(x, y); + if (aWhich == eChildToUserSpace) { + return toUserSpace * aMatrix; + } + MOZ_ASSERT(aWhich == eAllTransforms, "Unknown TransformTypes"); + return toUserSpace * fromUserSpace; +} + +/* virtual */ +bool SVGForeignObjectElement::HasValidDimensions() const { + float width, height; + + DebugOnly<bool> ok = + SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>( + const_cast<SVGForeignObjectElement*>(this), &width, &height); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + return width > 0 && height > 0; +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGForeignObjectElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return IsInLengthInfo(name, sLengthInfo) || + FindAttributeDependence(name, map) || + SVGGraphicsElement::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::LengthAttributesInfo SVGForeignObjectElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +nsCSSPropertyID SVGForeignObjectElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_X: + return eCSSProperty_x; + case ATTR_Y: + return eCSSProperty_y; + case ATTR_WIDTH: + return eCSSProperty_width; + case ATTR_HEIGHT: + return eCSSProperty_height; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGForeignObjectElement.h b/dom/svg/SVGForeignObjectElement.h new file mode 100644 index 0000000000..bd313a689d --- /dev/null +++ b/dom/svg/SVGForeignObjectElement.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFOREIGNOBJECTELEMENT_H_ +#define DOM_SVG_SVGFOREIGNOBJECTELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "nsCSSPropertyID.h" +#include "SVGAnimatedLength.h" + +nsresult NS_NewSVGForeignObjectElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGForeignObjectFrame; + +namespace dom { + +class SVGForeignObjectElement final : public SVGGraphicsElement { + friend class mozilla::SVGForeignObjectFrame; + + protected: + friend nsresult(::NS_NewSVGForeignObjectElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGForeignObjectElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // SVGElement specializations: + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; + bool HasValidDimensions() const override; + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFOREIGNOBJECTELEMENT_H_ diff --git a/dom/svg/SVGFragmentIdentifier.cpp b/dom/svg/SVGFragmentIdentifier.cpp new file mode 100644 index 0000000000..b274ef279b --- /dev/null +++ b/dom/svg/SVGFragmentIdentifier.cpp @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGFragmentIdentifier.h" + +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGViewElement.h" +#include "mozilla/SVGOuterSVGFrame.h" +#include "nsCharSeparatedTokenizer.h" +#include "SVGAnimatedTransformList.h" + +namespace mozilla { + +using namespace dom; + +static bool IsMatchingParameter(const nsAString& aString, + const nsAString& aParameterName) { + // The first two tests ensure aString.Length() > aParameterName.Length() + // so it's then safe to do the third test + return StringBeginsWith(aString, aParameterName) && aString.Last() == ')' && + aString.CharAt(aParameterName.Length()) == '('; +} + +static SVGViewElement* GetViewElement(Document* aDocument, + const nsAString& aId) { + Element* element = aDocument->GetElementById(aId); + return (element && element->IsSVGElement(nsGkAtoms::view)) + ? static_cast<SVGViewElement*>(element) + : nullptr; +} + +// Handles setting/clearing the root's mSVGView pointer. +class MOZ_RAII AutoSVGViewHandler { + public: + explicit AutoSVGViewHandler(SVGSVGElement* aRoot) + : mRoot(aRoot), mValid(false) { + mWasOverridden = mRoot->UseCurrentView(); + mRoot->mSVGView = nullptr; + mRoot->mCurrentViewID = nullptr; + } + + ~AutoSVGViewHandler() { + if (!mWasOverridden && !mValid) { + // we weren't overridden before and we aren't + // overridden now so nothing has changed. + return; + } + if (mValid) { + mRoot->mSVGView = std::move(mSVGView); + } + mRoot->InvalidateTransformNotifyFrame(); + if (nsIFrame* f = mRoot->GetPrimaryFrame()) { + if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) { + osf->MaybeSendIntrinsicSizeAndRatioToEmbedder(); + } + } + } + + void CreateSVGView() { + MOZ_ASSERT(!mSVGView, "CreateSVGView should not be called multiple times"); + mSVGView = MakeUnique<SVGView>(); + } + + bool ProcessAttr(const nsAString& aToken, const nsAString& aParams) { + MOZ_ASSERT(mSVGView, "CreateSVGView should have been called"); + + // SVGViewAttributes may occur in any order, but each type may only occur + // at most one time in a correctly formed SVGViewSpec. + // If we encounter any attribute more than once or get any syntax errors + // we're going to return false and cancel any changes. + + if (IsMatchingParameter(aToken, u"viewBox"_ns)) { + if (mSVGView->mViewBox.IsExplicitlySet() || + NS_FAILED( + mSVGView->mViewBox.SetBaseValueString(aParams, mRoot, false))) { + return false; + } + } else if (IsMatchingParameter(aToken, u"preserveAspectRatio"_ns)) { + if (mSVGView->mPreserveAspectRatio.IsExplicitlySet() || + NS_FAILED(mSVGView->mPreserveAspectRatio.SetBaseValueString( + aParams, mRoot, false))) { + return false; + } + } else if (IsMatchingParameter(aToken, u"transform"_ns)) { + if (mSVGView->mTransforms) { + return false; + } + mSVGView->mTransforms = MakeUnique<SVGAnimatedTransformList>(); + if (NS_FAILED( + mSVGView->mTransforms->SetBaseValueString(aParams, mRoot))) { + return false; + } + } else if (IsMatchingParameter(aToken, u"zoomAndPan"_ns)) { + if (mSVGView->mZoomAndPan.IsExplicitlySet()) { + return false; + } + nsAtom* valAtom = NS_GetStaticAtom(aParams); + if (!valAtom || !mSVGView->mZoomAndPan.SetBaseValueAtom(valAtom, mRoot)) { + return false; + } + } else { + return false; + } + return true; + } + + void SetValid() { mValid = true; } + + private: + SVGSVGElement* mRoot; + UniquePtr<SVGView> mSVGView; + bool mValid; + bool mWasOverridden; +}; + +bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec, + SVGSVGElement* aRoot) { + AutoSVGViewHandler viewHandler(aRoot); + + if (!IsMatchingParameter(aViewSpec, u"svgView"_ns)) { + return false; + } + + // Each token is a SVGViewAttribute + int32_t bracketPos = aViewSpec.FindChar('('); + uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2; + nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing> tokenizer( + Substring(aViewSpec, bracketPos + 1, lengthOfViewSpec), ';'); + + if (!tokenizer.hasMoreTokens()) { + return false; + } + viewHandler.CreateSVGView(); + + do { + nsAutoString token(tokenizer.nextToken()); + + bracketPos = token.FindChar('('); + if (bracketPos < 1 || token.Last() != ')') { + // invalid SVGViewAttribute syntax + return false; + } + + const nsAString& params = + Substring(token, bracketPos + 1, token.Length() - bracketPos - 2); + + if (!viewHandler.ProcessAttr(token, params)) { + return false; + } + + } while (tokenizer.hasMoreTokens()); + + viewHandler.SetValid(); + return true; +} + +bool SVGFragmentIdentifier::ProcessFragmentIdentifier( + Document* aDocument, const nsAString& aAnchorName) { + MOZ_ASSERT(aDocument->GetRootElement()->IsSVGElement(nsGkAtoms::svg), + "expecting an SVG root element"); + + SVGSVGElement* rootElement = + static_cast<SVGSVGElement*>(aDocument->GetRootElement()); + + const SVGViewElement* viewElement = GetViewElement(aDocument, aAnchorName); + + if (viewElement) { + if (!rootElement->mCurrentViewID) { + rootElement->mCurrentViewID = MakeUnique<nsString>(); + } + *rootElement->mCurrentViewID = aAnchorName; + rootElement->mSVGView = nullptr; + rootElement->InvalidateTransformNotifyFrame(); + if (nsIFrame* f = rootElement->GetPrimaryFrame()) { + if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) { + osf->MaybeSendIntrinsicSizeAndRatioToEmbedder(); + } + } + // not an svgView()-style fragment identifier, return false so the caller + // continues processing to match any :target pseudo elements + return false; + } + + return ProcessSVGViewSpec(aAnchorName, rootElement); +} + +} // namespace mozilla diff --git a/dom/svg/SVGFragmentIdentifier.h b/dom/svg/SVGFragmentIdentifier.h new file mode 100644 index 0000000000..464412d1ed --- /dev/null +++ b/dom/svg/SVGFragmentIdentifier.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFRAGMENTIDENTIFIER_H_ +#define DOM_SVG_SVGFRAGMENTIDENTIFIER_H_ + +#include "nsString.h" + +namespace mozilla { + +namespace dom { +class Document; +class SVGSVGElement; +} // namespace dom + +/** + * Implements support for parsing SVG fragment identifiers + * http://www.w3.org/TR/SVG/linking.html#SVGFragmentIdentifiers + */ +class SVGFragmentIdentifier { + // To prevent the class being instantiated + SVGFragmentIdentifier() = delete; + + public: + /** + * Process the SVG fragment identifier, if there is one. + * @return true if we found a valid svgView()-style fragment identifier, + * in which case further processing by the caller can stop. Otherwise return + * false as we may have an ordinary anchor which needs to be :target matched. + */ + static bool ProcessFragmentIdentifier(dom::Document* aDocument, + const nsAString& aAnchorName); + + private: + /** + * Parse an SVG ViewSpec and set applicable attributes on the root element. + * @return true if there is a valid ViewSpec + */ + static bool ProcessSVGViewSpec(const nsAString& aViewSpec, + dom::SVGSVGElement* root); +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGFRAGMENTIDENTIFIER_H_ diff --git a/dom/svg/SVGGElement.cpp b/dom/svg/SVGGElement.cpp new file mode 100644 index 0000000000..ac39edff83 --- /dev/null +++ b/dom/svg/SVGGElement.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGGElement.h" +#include "mozilla/dom/SVGGElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(G) + +namespace mozilla::dom { + +JSObject* SVGGElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGGElement::SVGGElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGraphicsElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGGElement) + +bool SVGGElement::IsNodeOfType(uint32_t aFlags) const { + return !(aFlags & ~eUSE_TARGET); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGGElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return FindAttributeDependence(name, map) || + SVGGraphicsElement::IsAttributeMapped(name); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGElement.h b/dom/svg/SVGGElement.h new file mode 100644 index 0000000000..c8a913b978 --- /dev/null +++ b/dom/svg/SVGGElement.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGELEMENT_H_ +#define DOM_SVG_SVGGELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" + +nsresult NS_NewSVGGElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGGElement final : public SVGGraphicsElement { + protected: + explicit SVGGElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGGElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + public: + // nsIContent + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + bool IsNodeOfType(uint32_t aFlags) const override; + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGGELEMENT_H_ diff --git a/dom/svg/SVGGeometryElement.cpp b/dom/svg/SVGGeometryElement.cpp new file mode 100644 index 0000000000..5f92563b4b --- /dev/null +++ b/dom/svg/SVGGeometryElement.cpp @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGGeometryElement.h" + +#include "DOMSVGPoint.h" +#include "gfxPlatform.h" +#include "nsCOMPtr.h" +#include "SVGAnimatedLength.h" +#include "SVGCircleElement.h" +#include "SVGEllipseElement.h" +#include "SVGGeometryProperty.h" +#include "SVGPathElement.h" +#include "SVGRectElement.h" +#include "mozilla/dom/DOMPointBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGContentUtils.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +SVGElement::NumberInfo SVGGeometryElement::sNumberInfo = {nsGkAtoms::pathLength, + 0, false}; + +//---------------------------------------------------------------------- +// Implementation + +SVGGeometryElement::SVGGeometryElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGeometryElementBase(std::move(aNodeInfo)) {} + +SVGElement::NumberAttributesInfo SVGGeometryElement::GetNumberInfo() { + return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1); +} + +nsresult SVGGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (mCachedPath && aNamespaceID == kNameSpaceID_None && + AttributeDefinesGeometry(aName)) { + mCachedPath = nullptr; + } + return SVGGeometryElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +bool SVGGeometryElement::IsNodeOfType(uint32_t aFlags) const { + return !(aFlags & ~(eSHAPE | eUSE_TARGET)); +} + +bool SVGGeometryElement::AttributeDefinesGeometry(const nsAtom* aName) { + if (aName == nsGkAtoms::pathLength) { + return true; + } + + // Check for SVGAnimatedLength attribute + LengthAttributesInfo info = GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return true; + } + } + + return false; +} + +bool SVGGeometryElement::GeometryDependsOnCoordCtx() { + // Check the SVGAnimatedLength attribute + LengthAttributesInfo info = + const_cast<SVGGeometryElement*>(this)->GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (info.mValues[i].GetSpecifiedUnitType() == + SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE) { + return true; + } + } + return false; +} + +bool SVGGeometryElement::IsMarkable() { return false; } + +void SVGGeometryElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {} + +already_AddRefed<Path> SVGGeometryElement::GetOrBuildPath( + const DrawTarget* aDrawTarget, FillRule aFillRule) { + // We only cache the path if it matches the backend used for screen painting, + // and it's not a capturing drawtarget. A capturing DT might start using the + // the Path object on a different thread (OMTP), and we might have a data race + // if we keep a handle to it. + bool cacheable = aDrawTarget->GetBackendType() == + gfxPlatform::GetPlatform()->GetDefaultContentBackend(); + + if (cacheable && mCachedPath && mCachedPath->GetFillRule() == aFillRule && + aDrawTarget->GetBackendType() == mCachedPath->GetBackendType()) { + RefPtr<Path> path(mCachedPath); + return path.forget(); + } + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(aFillRule); + RefPtr<Path> path = BuildPath(builder); + if (cacheable) { + mCachedPath = path; + } + return path.forget(); +} + +already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForMeasuring() { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); + return GetOrBuildPath(drawTarget, fillRule); +} + +// This helper is currently identical to GetOrBuildPathForMeasuring. +// We keep it a separate method because for length measuring purpose, +// fillRule isn't really needed. Derived class (e.g. SVGPathElement) +// may override GetOrBuildPathForMeasuring() to ignore fillRule. And +// GetOrBuildPathForMeasuring() itself may be modified in the future. +already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForHitTest() { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); + return GetOrBuildPath(drawTarget, fillRule); +} + +bool SVGGeometryElement::IsGeometryChangedViaCSS( + ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const { + nsAtom* name = NodeInfo()->NameAtom(); + if (name == nsGkAtoms::rect) { + return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::circle) { + return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::ellipse) { + return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::path) { + return StaticPrefs::layout_css_d_property_enabled() && + SVGPathElement::IsDPropertyChangedViaCSS(aNewStyle, aOldStyle); + } + return false; +} + +FillRule SVGGeometryElement::GetFillRule() { + FillRule fillRule = + FillRule::FILL_WINDING; // Equivalent to StyleFillRule::Nonzero + + bool res = SVGGeometryProperty::DoForComputedStyle( + this, [&](const ComputedStyle* s) { + const auto* styleSVG = s->StyleSVG(); + + MOZ_ASSERT(styleSVG->mFillRule == StyleFillRule::Nonzero || + styleSVG->mFillRule == StyleFillRule::Evenodd); + + if (styleSVG->mFillRule == StyleFillRule::Evenodd) { + fillRule = FillRule::FILL_EVEN_ODD; + } + }); + + if (!res) { + NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule"); + } + + return fillRule; +} + +static Point GetPointFrom(const DOMPointInit& aPoint) { + return Point(aPoint.mX, aPoint.mY); +} + +bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + + RefPtr<Path> path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + auto point = GetPointFrom(aPoint); + return path->ContainsPoint(point, {}); +} + +bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) { + // stroke-* attributes and the d attribute are presentation attributes, so we + // flush the layout before building the path. + if (nsCOMPtr<Document> doc = GetComposedDoc()) { + doc->FlushPendingNotifications(FlushType::Layout); + } + + RefPtr<Path> path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + auto point = GetPointFrom(aPoint); + bool res = false; + SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) { + // Per spec, we should take vector-effect into account. + if (s->StyleSVGReset()->HasNonScalingStroke()) { + auto mat = SVGContentUtils::GetCTM(this, true); + if (mat.HasNonTranslation()) { + // We have non-scaling-stroke as well as a non-translation transform. + // We should transform the path first then apply the stroke on the + // transformed path to preserve the stroke-width. + RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(mat); + + path = builder->Finish(); + point = mat.TransformPoint(point); + } + } + + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr); + + res = path->StrokeContainsPoint(strokeOptions, point, {}); + }); + + return res; +} + +float SVGGeometryElement::GetTotalLengthForBinding() { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + return GetTotalLength(); +} + +already_AddRefed<DOMSVGPoint> SVGGeometryElement::GetPointAtLength( + float distance, ErrorResult& rv) { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + RefPtr<Path> path = GetOrBuildPathForMeasuring(); + if (!path) { + rv.ThrowInvalidStateError("No path available for measuring"); + return nullptr; + } + + RefPtr<DOMSVGPoint> point = new DOMSVGPoint(path->ComputePointAtLength( + clamped(distance, 0.f, path->ComputeLength()))); + return point.forget(); +} + +float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) { + MOZ_ASSERT(aFor == eForTextPath || aFor == eForStroking, "Unknown enum"); + if (mPathLength.IsExplicitlySet()) { + float authorsPathLengthEstimate = mPathLength.GetAnimValue(); + if (authorsPathLengthEstimate > 0) { + RefPtr<Path> path = GetOrBuildPathForMeasuring(); + if (!path) { + // The path is empty or invalid so its length must be zero and + // we know that 0 / authorsPathLengthEstimate = 0. + return 0.0; + } + if (aFor == eForTextPath) { + // For textPath, a transform on the referenced path affects the + // textPath layout, so when calculating the actual path length + // we need to take that into account. + gfxMatrix matrix = PrependLocalTransformsTo(gfxMatrix()); + if (!matrix.IsIdentity()) { + RefPtr<PathBuilder> builder = + path->TransformedCopyToBuilder(ToMatrix(matrix)); + path = builder->Finish(); + } + } + return path->ComputeLength() / authorsPathLengthEstimate; + } + } + return 1.0; +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGGeometryElement::PathLength() { + return mPathLength.ToDOMAnimatedNumber(this); +} + +float SVGGeometryElement::GetTotalLength() { + RefPtr<Path> flat = GetOrBuildPathForMeasuring(); + return flat ? flat->ComputeLength() : 0.f; +} + +void SVGGeometryElement::FlushStyleIfNeeded() { + // Note: we still can set d property on other elements which don't have d + // attribute, but we don't look at the d property on them, so here we only + // care about the element with d attribute, i.e. SVG path element. + if (GetPathDataAttrName() != nsGkAtoms::d || + !StaticPrefs::layout_css_d_property_enabled()) { + return; + } + + RefPtr<Document> doc = GetComposedDoc(); + if (!doc) { + return; + } + + doc->FlushPendingNotifications(FlushType::Style); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGeometryElement.h b/dom/svg/SVGGeometryElement.h new file mode 100644 index 0000000000..41e4e014f4 --- /dev/null +++ b/dom/svg/SVGGeometryElement.h @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGEOMETRYELEMENT_H_ +#define DOM_SVG_SVGGEOMETRYELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/SVGAnimatedNumber.h" + +namespace mozilla { + +struct SVGMark { + enum Type { + eStart, + eMid, + eEnd, + + eTypeCount + }; + + float x, y, angle; + Type type; + SVGMark(float aX, float aY, float aAngle, Type aType) + : x(aX), y(aY), angle(aAngle), type(aType) {} +}; + +namespace dom { + +class DOMSVGAnimatedNumber; +class DOMSVGPoint; + +using SVGGeometryElementBase = mozilla::dom::SVGGraphicsElement; + +class SVGGeometryElement : public SVGGeometryElementBase { + protected: + using CapStyle = mozilla::gfx::CapStyle; + using DrawTarget = mozilla::gfx::DrawTarget; + using FillRule = mozilla::gfx::FillRule; + using Float = mozilla::gfx::Float; + using Matrix = mozilla::gfx::Matrix; + using Path = mozilla::gfx::Path; + using Point = mozilla::gfx::Point; + using PathBuilder = mozilla::gfx::PathBuilder; + using Rect = mozilla::gfx::Rect; + using StrokeOptions = mozilla::gfx::StrokeOptions; + + public: + explicit SVGGeometryElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + bool IsNodeOfType(uint32_t aFlags) const override; + + /** + * Causes this element to discard any Path object that GetOrBuildPath may + * have cached. + */ + void ClearAnyCachedPath() final { mCachedPath = nullptr; } + + virtual bool AttributeDefinesGeometry(const nsAtom* aName); + + /** + * Returns true if this element's geometry depends on the width or height of + * its coordinate context (typically the viewport established by its nearest + * <svg> ancestor). In other words, returns true if one of the attributes for + * which AttributeDefinesGeometry returns true has a percentage value. + * + * This could be moved up to a more general class so it can be used for + * non-leaf elements, but that would require care and for now there's no need. + */ + bool GeometryDependsOnCoordCtx(); + + virtual bool IsMarkable(); + virtual void GetMarkPoints(nsTArray<SVGMark>* aMarks); + + /** + * A method that can be faster than using a Moz2D Path and calling GetBounds/ + * GetStrokedBounds on it. It also helps us avoid rounding error for simple + * shapes and simple transforms where the Moz2D Path backends can fail to + * produce the clean integer bounds that content authors expect in some cases. + * + * If |aToNonScalingStrokeSpace| is non-null then |aBounds|, which is computed + * in bounds space, has the property that it's the smallest (axis-aligned) + * rectangular bound containing the image of this shape as stroked in + * non-scaling-stroke space. (When all transforms involved are rectilinear + * the bounds of the image of |aBounds| in non-scaling-stroke space will be + * tight, but if there are non-rectilinear transforms involved then that may + * be impossible and this method will return false). + * + * If |aToNonScalingStrokeSpace| is non-null then |*aToNonScalingStrokeSpace| + * must be non-singular. + */ + virtual bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) { + return false; + } + + /** + * For use with GetAsSimplePath. + */ + class SimplePath { + public: + SimplePath() + : mX(0.0), mY(0.0), mWidthOrX2(0.0), mHeightOrY2(0.0), mType(NONE) {} + bool IsPath() const { return mType != NONE; } + void SetRect(Float x, Float y, Float width, Float height) { + mX = x; + mY = y; + mWidthOrX2 = width; + mHeightOrY2 = height; + mType = RECT; + } + Rect AsRect() const { + MOZ_ASSERT(mType == RECT); + return Rect(mX, mY, mWidthOrX2, mHeightOrY2); + } + bool IsRect() const { return mType == RECT; } + void SetLine(Float x1, Float y1, Float x2, Float y2) { + mX = x1; + mY = y1; + mWidthOrX2 = x2; + mHeightOrY2 = y2; + mType = LINE; + } + Point Point1() const { + MOZ_ASSERT(mType == LINE); + return Point(mX, mY); + } + Point Point2() const { + MOZ_ASSERT(mType == LINE); + return Point(mWidthOrX2, mHeightOrY2); + } + bool IsLine() const { return mType == LINE; } + void Reset() { mType = NONE; } + + private: + enum Type { NONE, RECT, LINE }; + Float mX, mY, mWidthOrX2, mHeightOrY2; + Type mType; + }; + + /** + * For some platforms there is significant overhead to creating and painting + * a Moz2D Path object. For Rects and lines it is better to get the path data + * using this method and then use the optimized DrawTarget methods for + * filling/stroking rects and lines. + */ + virtual void GetAsSimplePath(SimplePath* aSimplePath) { + aSimplePath->Reset(); + } + + /** + * Returns a Path that can be used to paint, hit-test or calculate bounds for + * this element. May return nullptr if there is no [valid] path. The path + * that is created may be cached and returned on subsequent calls. + */ + virtual already_AddRefed<Path> GetOrBuildPath(const DrawTarget* aDrawTarget, + FillRule fillRule); + + /** + * The same as GetOrBuildPath, but bypasses the cache (neither returns any + * previously cached Path, nor caches the Path that in does return). + * this element. May return nullptr if there is no [valid] path. + */ + virtual already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) = 0; + + /** + * Get the distances from the origin of the path segments. + * For non-path elements that's just 0 and the total length of the shape. + */ + virtual bool GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray<double>* aOutput) { + aOutput->Clear(); + double distances[] = {0.0, GetTotalLength()}; + return aOutput->AppendElements(Span<double>(distances), fallible); + } + + /** + * Returns a Path that can be used to measure the length of this elements + * path, or to find the position at a given distance along it. + * + * This is currently equivalent to calling GetOrBuildPath, but it may not be + * in the future. The reason for this function to be separate from + * GetOrBuildPath is because SVGPathData::BuildPath inserts small lines into + * the path if zero length subpaths are encountered, in order to implement + * the SVG specifications requirements that zero length subpaths should + * render circles/squares if stroke-linecap is round/square, respectively. + * In principle these inserted lines could interfere with path measurement, + * so we keep callers that are looking to do measurement separate in case we + * run into problems with the inserted lines negatively affecting measuring + * for content. + */ + virtual already_AddRefed<Path> GetOrBuildPathForMeasuring(); + + /** + * Return |true| if some geometry properties (|x|, |y|, etc) are changed + * because of CSS change. + */ + bool IsGeometryChangedViaCSS(ComputedStyle const& aNewStyle, + ComputedStyle const& aOldStyle) const; + + /** + * Returns the current computed value of the CSS property 'fill-rule' for + * this element. + */ + FillRule GetFillRule(); + + enum PathLengthScaleForType { eForTextPath, eForStroking }; + + /** + * Gets the ratio of the actual element's length to the content author's + * estimated length (as provided by the element's 'pathLength' attribute). + * This is used to scale stroke dashing, and to scale offsets along a + * textPath. + */ + float GetPathLengthScale(PathLengthScaleForType aFor); + + // WebIDL + already_AddRefed<DOMSVGAnimatedNumber> PathLength(); + MOZ_CAN_RUN_SCRIPT bool IsPointInFill(const DOMPointInit& aPoint); + MOZ_CAN_RUN_SCRIPT bool IsPointInStroke(const DOMPointInit& aPoint); + MOZ_CAN_RUN_SCRIPT float GetTotalLengthForBinding(); + MOZ_CAN_RUN_SCRIPT already_AddRefed<DOMSVGPoint> GetPointAtLength( + float distance, ErrorResult& rv); + + protected: + // SVGElement method + NumberAttributesInfo GetNumberInfo() override; + + // d is a presentation attribute, so we would like to make sure style is + // up-to-date. This function flushes the style if the path attribute is d. + MOZ_CAN_RUN_SCRIPT void FlushStyleIfNeeded(); + + SVGAnimatedNumber mPathLength; + static NumberInfo sNumberInfo; + mutable RefPtr<Path> mCachedPath; + + private: + already_AddRefed<Path> GetOrBuildPathForHitTest(); + + float GetTotalLength(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGGEOMETRYELEMENT_H_ diff --git a/dom/svg/SVGGeometryProperty.cpp b/dom/svg/SVGGeometryProperty.cpp new file mode 100644 index 0000000000..7b58221b8d --- /dev/null +++ b/dom/svg/SVGGeometryProperty.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGGeometryProperty.h" +#include "SVGCircleElement.h" +#include "SVGEllipseElement.h" +#include "SVGForeignObjectElement.h" +#include "SVGImageElement.h" +#include "SVGRectElement.h" +#include "SVGUseElement.h" +#include "nsCSSValue.h" + +namespace mozilla::dom::SVGGeometryProperty { + +nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit) { + switch (aSpecifiedUnit) { + case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER: + case SVGLength_Binding::SVG_LENGTHTYPE_PX: + return nsCSSUnit::eCSSUnit_Pixel; + + case SVGLength_Binding::SVG_LENGTHTYPE_MM: + return nsCSSUnit::eCSSUnit_Millimeter; + + case SVGLength_Binding::SVG_LENGTHTYPE_CM: + return nsCSSUnit::eCSSUnit_Centimeter; + + case SVGLength_Binding::SVG_LENGTHTYPE_IN: + return nsCSSUnit::eCSSUnit_Inch; + + case SVGLength_Binding::SVG_LENGTHTYPE_PT: + return nsCSSUnit::eCSSUnit_Point; + + case SVGLength_Binding::SVG_LENGTHTYPE_PC: + return nsCSSUnit::eCSSUnit_Pica; + + case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE: + return nsCSSUnit::eCSSUnit_Percent; + + case SVGLength_Binding::SVG_LENGTHTYPE_EMS: + return nsCSSUnit::eCSSUnit_EM; + + case SVGLength_Binding::SVG_LENGTHTYPE_EXS: + return nsCSSUnit::eCSSUnit_XHeight; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); + return nsCSSUnit::eCSSUnit_Pixel; + } +} + +nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement, + uint8_t aAttrEnum) { + // This is a very trivial function only applied to a few elements, + // so we want to avoid making it virtual. + if (aElement->IsSVGElement(nsGkAtoms::rect)) { + return SVGRectElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::circle)) { + return SVGCircleElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::ellipse)) { + return SVGEllipseElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::image)) { + return SVGImageElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::foreignObject)) { + return SVGForeignObjectElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::use)) { + return SVGUseElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + return eCSSProperty_UNKNOWN; +} + +bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp) { + return aProp == eCSSProperty_r || aProp == eCSSProperty_rx || + aProp == eCSSProperty_ry || aProp == eCSSProperty_width || + aProp == eCSSProperty_height; +} + +bool ElementMapsLengthsToStyle(SVGElement const* aElement) { + return aElement->IsAnyOfSVGElements(nsGkAtoms::rect, nsGkAtoms::circle, + nsGkAtoms::ellipse, nsGkAtoms::image, + nsGkAtoms::foreignObject, nsGkAtoms::use); +} + +} // namespace mozilla::dom::SVGGeometryProperty diff --git a/dom/svg/SVGGeometryProperty.h b/dom/svg/SVGGeometryProperty.h new file mode 100644 index 0000000000..d5eb0e9d11 --- /dev/null +++ b/dom/svg/SVGGeometryProperty.h @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGEOMETRYPROPERTY_H_ +#define DOM_SVG_SVGGEOMETRYPROPERTY_H_ + +#include "mozilla/SVGImageFrame.h" +#include "mozilla/dom/SVGElement.h" +#include "ComputedStyle.h" +#include "SVGAnimatedLength.h" +#include "nsComputedDOMStyle.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include <type_traits> + +namespace mozilla::dom::SVGGeometryProperty { +namespace ResolverTypes { +struct LengthPercentNoAuto {}; +struct LengthPercentRXY {}; +struct LengthPercentWidthHeight {}; +} // namespace ResolverTypes + +namespace Tags { + +#define SVGGEOMETRYPROPERTY_GENERATETAG(tagName, resolver, direction, \ + styleStruct) \ + struct tagName { \ + using ResolverType = ResolverTypes::resolver; \ + constexpr static auto CtxDirection = SVGContentUtils::direction; \ + constexpr static auto Getter = &styleStruct::m##tagName; \ + } + +SVGGEOMETRYPROPERTY_GENERATETAG(X, LengthPercentNoAuto, X, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(Y, LengthPercentNoAuto, Y, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(Cx, LengthPercentNoAuto, X, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(Cy, LengthPercentNoAuto, Y, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(R, LengthPercentNoAuto, XY, nsStyleSVGReset); + +#undef SVGGEOMETRYPROPERTY_GENERATETAG + +struct Height; +struct Width { + using ResolverType = ResolverTypes::LengthPercentWidthHeight; + constexpr static auto CtxDirection = SVGContentUtils::X; + constexpr static auto Getter = &nsStylePosition::mWidth; + constexpr static auto SizeGetter = &gfx::Size::width; + static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) { + return aAspectRatio.Inverted(); + } + constexpr static uint32_t DefaultObjectSize = 300; + using CounterPart = Height; +}; +struct Height { + using ResolverType = ResolverTypes::LengthPercentWidthHeight; + constexpr static auto CtxDirection = SVGContentUtils::Y; + constexpr static auto Getter = &nsStylePosition::mHeight; + constexpr static auto SizeGetter = &gfx::Size::height; + static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) { + return aAspectRatio; + } + constexpr static uint32_t DefaultObjectSize = 150; + using CounterPart = Width; +}; + +struct Ry; +struct Rx { + using ResolverType = ResolverTypes::LengthPercentRXY; + constexpr static auto CtxDirection = SVGContentUtils::X; + constexpr static auto Getter = &nsStyleSVGReset::mRx; + using CounterPart = Ry; +}; +struct Ry { + using ResolverType = ResolverTypes::LengthPercentRXY; + constexpr static auto CtxDirection = SVGContentUtils::Y; + constexpr static auto Getter = &nsStyleSVGReset::mRy; + using CounterPart = Rx; +}; + +} // namespace Tags + +namespace details { +template <class T> +using AlwaysFloat = float; +using dummy = int[]; + +using CtxDirectionType = decltype(SVGContentUtils::X); + +template <CtxDirectionType CTD> +float ResolvePureLengthPercentage(SVGElement* aElement, + const LengthPercentage& aLP) { + return aLP.ResolveToCSSPixelsWith( + [&] { return CSSCoord{SVGElementMetrics(aElement).GetAxisLength(CTD)}; }); +} + +template <class Tag> +float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement, + ResolverTypes::LengthPercentNoAuto) { + auto const& value = aStyle.StyleSVGReset()->*Tag::Getter; + return ResolvePureLengthPercentage<Tag::CtxDirection>(aElement, value); +} + +template <class Tag> +float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement, + ResolverTypes::LengthPercentWidthHeight) { + static_assert( + std::is_same<Tag, Tags::Width>{} || std::is_same<Tag, Tags::Height>{}, + "Wrong tag"); + + auto const& value = aStyle.StylePosition()->*Tag::Getter; + if (value.IsLengthPercentage()) { + return ResolvePureLengthPercentage<Tag::CtxDirection>( + aElement, value.AsLengthPercentage()); + } + + if (aElement->IsSVGElement(nsGkAtoms::image)) { + // It's not clear per SVG2 spec what should be done for values other + // than |auto| (e.g. |max-content|). We treat them as nonsense, thus + // using the initial value behavior, i.e. |auto|. + // The following procedure follows the Default Sizing Algorithm as + // specified in: + // https://svgwg.org/svg2-draft/embedded.html#ImageElement + + SVGImageFrame* imgf = do_QueryFrame(aElement->GetPrimaryFrame()); + if (!imgf) { + return 0.f; + } + + using Other = typename Tag::CounterPart; + auto const& valueOther = aStyle.StylePosition()->*Other::Getter; + + gfx::Size intrinsicImageSize; + AspectRatio aspectRatio; + if (!imgf->GetIntrinsicImageDimensions(intrinsicImageSize, aspectRatio)) { + // No image container, just return 0. + return 0.f; + } + + if (valueOther.IsLengthPercentage()) { + // We are |auto|, but the other side has specifed length. + float lengthOther = ResolvePureLengthPercentage<Other::CtxDirection>( + aElement, valueOther.AsLengthPercentage()); + + if (aspectRatio) { + // Preserve aspect ratio if it's present. + return Other::AspectRatioRelative(aspectRatio) + .ApplyToFloat(lengthOther); + } + + float intrinsicLength = intrinsicImageSize.*Tag::SizeGetter; + if (intrinsicLength >= 0) { + // Use the intrinsic length if it's present. + return intrinsicLength; + } + + // No specified size, no aspect ratio, no intrinsic length, + // then use default size. + return Tag::DefaultObjectSize; + } + + // |width| and |height| are both |auto| + if (intrinsicImageSize.*Tag::SizeGetter >= 0) { + return intrinsicImageSize.*Tag::SizeGetter; + } + + if (intrinsicImageSize.*Other::SizeGetter >= 0 && aspectRatio) { + return Other::AspectRatioRelative(aspectRatio) + .ApplyTo(intrinsicImageSize.*Other::SizeGetter); + } + + if (aspectRatio) { + // Resolve as a contain constraint against the default object size. + auto defaultAspectRatioRelative = + AspectRatio{float(Other::DefaultObjectSize) / Tag::DefaultObjectSize}; + auto aspectRatioRelative = Tag::AspectRatioRelative(aspectRatio); + + if (defaultAspectRatioRelative < aspectRatioRelative) { + // Using default length in our side and the intrinsic aspect ratio, + // the other side cannot be contained. + return aspectRatioRelative.Inverted().ApplyTo(Other::DefaultObjectSize); + } + + return Tag::DefaultObjectSize; + } + + return Tag::DefaultObjectSize; + } + + // For other elements, |auto| and |max-content| etc. are treated as 0. + return 0.f; +} + +template <class Tag> +float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement, + ResolverTypes::LengthPercentRXY) { + static_assert(std::is_same<Tag, Tags::Rx>{} || std::is_same<Tag, Tags::Ry>{}, + "Wrong tag"); + + auto const& value = aStyle.StyleSVGReset()->*Tag::Getter; + if (value.IsLengthPercentage()) { + return ResolvePureLengthPercentage<Tag::CtxDirection>( + aElement, value.AsLengthPercentage()); + } + + MOZ_ASSERT(value.IsAuto()); + using Rother = typename Tag::CounterPart; + auto const& valueOther = aStyle.StyleSVGReset()->*Rother::Getter; + + if (valueOther.IsAuto()) { + // Per SVG2, |Rx|, |Ry| resolve to 0 if both are |auto| + return 0.f; + } + + // If |Rx| is auto while |Ry| not, |Rx| gets the value of |Ry|. + return ResolvePureLengthPercentage<Rother::CtxDirection>( + aElement, valueOther.AsLengthPercentage()); +} + +} // namespace details + +template <class Tag> +float ResolveWith(const ComputedStyle& aStyle, const SVGElement* aElement) { + // TODO: There are a lot of utilities lacking const-ness in dom/svg. + // We should fix that problem and remove this `const_cast`. + return details::ResolveImpl<Tag>(aStyle, const_cast<SVGElement*>(aElement), + typename Tag::ResolverType{}); +} + +template <class Func> +bool DoForComputedStyle(const SVGElement* aElement, Func aFunc) { + if (const nsIFrame* f = aElement->GetPrimaryFrame()) { + aFunc(f->Style()); + return true; + } + + if (RefPtr<const ComputedStyle> computedStyle = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + aFunc(computedStyle.get()); + return true; + } + + return false; +} + +#define SVGGEOMETRYPROPERTY_EVAL_ALL(expr) \ + (void)details::dummy { 0, (static_cast<void>(expr), 0)... } + +// To add support for new properties, or to handle special cases for +// existing properties, you can add a new tag in |Tags| and |ResolverTypes| +// namespace, then implement the behavior in |details::ResolveImpl|. +template <class... Tags> +bool ResolveAll(const SVGElement* aElement, + details::AlwaysFloat<Tags>*... aRes) { + bool res = DoForComputedStyle(aElement, [&](auto const* style) { + SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = ResolveWith<Tags>(*style, aElement)); + }); + + if (res) { + return true; + } + + SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = 0); + return false; +} + +#undef SVGGEOMETRYPROPERTY_EVAL_ALL + +nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit); +nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement, + uint8_t aAttrEnum); + +bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp); +bool ElementMapsLengthsToStyle(SVGElement const* aElement); + +} // namespace mozilla::dom::SVGGeometryProperty + +#endif // DOM_SVG_SVGGEOMETRYPROPERTY_H_ diff --git a/dom/svg/SVGGradientElement.cpp b/dom/svg/SVGGradientElement.cpp new file mode 100644 index 0000000000..cb02d3ffc4 --- /dev/null +++ b/dom/svg/SVGGradientElement.cpp @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGGradientElement.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGGradientElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGLinearGradientElementBinding.h" +#include "mozilla/dom/SVGRadialGradientElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "DOMSVGAnimatedTransformList.h" +#include "nsGkAtoms.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(LinearGradient) +NS_IMPL_NS_NEW_SVG_ELEMENT(RadialGradient) + +namespace mozilla::dom { + +using namespace SVGGradientElement_Binding; +using namespace SVGUnitTypes_Binding; + +//--------------------- Gradients------------------------ + +SVGEnumMapping SVGGradientElement::sSpreadMethodMap[] = { + {nsGkAtoms::pad, SVG_SPREADMETHOD_PAD}, + {nsGkAtoms::reflect, SVG_SPREADMETHOD_REFLECT}, + {nsGkAtoms::repeat, SVG_SPREADMETHOD_REPEAT}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGGradientElement::sEnumInfo[2] = { + {nsGkAtoms::gradientUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::spreadMethod, sSpreadMethodMap, SVG_SPREADMETHOD_PAD}}; + +SVGElement::StringInfo SVGGradientElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGGradientElement::SVGGradientElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGradientElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::EnumAttributesInfo SVGGradientElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGGradientElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGGradientElement::GradientUnits() { + return mEnumAttributes[GRADIENTUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedTransformList> +SVGGradientElement::GradientTransform() { + // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList + // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so: + return DOMSVGAnimatedTransformList::GetDOMWrapper( + GetAnimatedTransformList(DO_ALLOCATE), this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGGradientElement::SpreadMethod() { + return mEnumAttributes[SPREADMETHOD].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGGradientElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGGradientElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sColorMap, + sGradientStopMap}; + + return FindAttributeDependence(name, map) || + SVGGradientElementBase::IsAttributeMapped(name); +} + +//---------------------Linear Gradients------------------------ + +JSObject* SVGLinearGradientElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGLinearGradientElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGLinearGradientElement::sLengthInfo[4] = { + {nsGkAtoms::x1, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y1, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::x2, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y2, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGLinearGradientElement::SVGLinearGradientElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGLinearGradientElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGLinearGradientElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGLinearGradientElement::X1() { + return mLengthAttributes[ATTR_X1].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGLinearGradientElement::Y1() { + return mLengthAttributes[ATTR_Y1].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGLinearGradientElement::X2() { + return mLengthAttributes[ATTR_X2].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGLinearGradientElement::Y2() { + return mLengthAttributes[ATTR_Y2].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGAnimatedTransformList* SVGGradientElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!mGradientTransform && (aFlags & DO_ALLOCATE)) { + mGradientTransform = MakeUnique<SVGAnimatedTransformList>(); + } + return mGradientTransform.get(); +} + +SVGElement::LengthAttributesInfo SVGLinearGradientElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//-------------------------- Radial Gradients ---------------------------- + +JSObject* SVGRadialGradientElement::WrapNode( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return SVGRadialGradientElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGRadialGradientElement::sLengthInfo[6] = { + {nsGkAtoms::cx, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::cy, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::r, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::XY}, + {nsGkAtoms::fx, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::fy, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::fr, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::XY}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGRadialGradientElement::SVGRadialGradientElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGRadialGradientElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGRadialGradientElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGRadialGradientElement::Cx() { + return mLengthAttributes[ATTR_CX].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRadialGradientElement::Cy() { + return mLengthAttributes[ATTR_CY].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRadialGradientElement::R() { + return mLengthAttributes[ATTR_R].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRadialGradientElement::Fx() { + return mLengthAttributes[ATTR_FX].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRadialGradientElement::Fy() { + return mLengthAttributes[ATTR_FY].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRadialGradientElement::Fr() { + return mLengthAttributes[ATTR_FR].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::LengthAttributesInfo SVGRadialGradientElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGradientElement.h b/dom/svg/SVGGradientElement.h new file mode 100644 index 0000000000..5b382e7ed9 --- /dev/null +++ b/dom/svg/SVGGradientElement.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGRADIENTELEMENT_H_ +#define DOM_SVG_SVGGRADIENTELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedString.h" +#include "SVGAnimatedTransformList.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/UniquePtr.h" + +nsresult NS_NewSVGLinearGradientElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); +nsresult NS_NewSVGRadialGradientElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGGradientFrame; +class SVGLinearGradientFrame; +class SVGRadialGradientFrame; + +namespace dom { + +class DOMSVGAnimatedTransformList; + +//--------------------- Gradients------------------------ + +using SVGGradientElementBase = SVGElement; + +class SVGGradientElement : public SVGGradientElementBase { + friend class mozilla::SVGGradientFrame; + + protected: + explicit SVGGradientElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override = 0; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override = 0; + + // nsIContent + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + virtual SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + nsStaticAtom* GetTransformListAttrName() const override { + return nsGkAtoms::gradientTransform; + } + + // WebIDL + already_AddRefed<DOMSVGAnimatedEnumeration> GradientUnits(); + already_AddRefed<DOMSVGAnimatedTransformList> GradientTransform(); + already_AddRefed<DOMSVGAnimatedEnumeration> SpreadMethod(); + already_AddRefed<DOMSVGAnimatedString> Href(); + + protected: + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { GRADIENTUNITS, SPREADMETHOD }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static SVGEnumMapping sSpreadMethodMap[]; + static EnumInfo sEnumInfo[2]; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + // SVGGradientElement values + UniquePtr<SVGAnimatedTransformList> mGradientTransform; +}; + +//---------------------Linear Gradients------------------------ + +using SVGLinearGradientElementBase = SVGGradientElement; + +class SVGLinearGradientElement : public SVGLinearGradientElementBase { + friend class mozilla::SVGLinearGradientFrame; + friend nsresult(::NS_NewSVGLinearGradientElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGLinearGradientElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + virtual JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X1(); + already_AddRefed<DOMSVGAnimatedLength> Y1(); + already_AddRefed<DOMSVGAnimatedLength> X2(); + already_AddRefed<DOMSVGAnimatedLength> Y2(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X1, ATTR_Y1, ATTR_X2, ATTR_Y2 }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +//-------------------------- Radial Gradients ---------------------------- + +using SVGRadialGradientElementBase = SVGGradientElement; + +class SVGRadialGradientElement : public SVGRadialGradientElementBase { + friend class mozilla::SVGRadialGradientFrame; + friend nsresult(::NS_NewSVGRadialGradientElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + protected: + explicit SVGRadialGradientElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + virtual JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> Cx(); + already_AddRefed<DOMSVGAnimatedLength> Cy(); + already_AddRefed<DOMSVGAnimatedLength> R(); + already_AddRefed<DOMSVGAnimatedLength> Fx(); + already_AddRefed<DOMSVGAnimatedLength> Fy(); + already_AddRefed<DOMSVGAnimatedLength> Fr(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_CX, ATTR_CY, ATTR_R, ATTR_FX, ATTR_FY, ATTR_FR }; + SVGAnimatedLength mLengthAttributes[6]; + static LengthInfo sLengthInfo[6]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGGRADIENTELEMENT_H_ diff --git a/dom/svg/SVGGraphicsElement.cpp b/dom/svg/SVGGraphicsElement.cpp new file mode 100644 index 0000000000..1e87437011 --- /dev/null +++ b/dom/svg/SVGGraphicsElement.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGGraphicsElement.h" + +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGGraphicsElementBinding.h" +#include "mozilla/dom/SVGMatrix.h" +#include "mozilla/dom/SVGRect.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/SVGUtils.h" + +#include "nsIContentInlines.h" +#include "nsLayoutUtils.h" + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGGraphicsElement, SVGGraphicsElementBase) +NS_IMPL_RELEASE_INHERITED(SVGGraphicsElement, SVGGraphicsElementBase) + +NS_INTERFACE_MAP_BEGIN(SVGGraphicsElement) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests) +NS_INTERFACE_MAP_END_INHERITING(SVGGraphicsElementBase) + +//---------------------------------------------------------------------- +// Implementation + +SVGGraphicsElement::SVGGraphicsElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGraphicsElementBase(std::move(aNodeInfo)) {} + +SVGElement* SVGGraphicsElement::GetNearestViewportElement() { + return SVGContentUtils::GetNearestViewportElement(this); +} + +SVGElement* SVGGraphicsElement::GetFarthestViewportElement() { + return SVGContentUtils::GetOuterSVGElement(this); +} + +static already_AddRefed<SVGRect> ZeroBBox(SVGGraphicsElement& aOwner) { + return MakeAndAddRef<SVGRect>(&aOwner, gfx::Rect{0, 0, 0, 0}); +} + +already_AddRefed<SVGRect> SVGGraphicsElement::GetBBox( + const SVGBoundingBoxOptions& aOptions) { + nsIFrame* frame = GetPrimaryFrame(FlushType::Layout); + + if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + return ZeroBBox(*this); + } + ISVGDisplayableFrame* svgframe = do_QueryFrame(frame); + + if (!svgframe) { + if (!SVGUtils::IsInSVGTextSubtree(frame)) { + return ZeroBBox(*this); + } + + // For <tspan>, <textPath>, the frame is an nsInlineFrame or + // nsBlockFrame, |svgframe| will be a nullptr. + // We implement their getBBox directly here instead of in + // SVGUtils::GetBBox, because SVGUtils::GetBBox is more + // or less used for other purpose elsewhere. e.g. gradient + // code assumes GetBBox of <tspan> returns the bbox of the + // outer <text>. + // TODO: cleanup this sort of usecase of SVGUtils::GetBBox, + // then move this code SVGUtils::GetBBox. + SVGTextFrame* text = + static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType( + frame->GetParent(), LayoutFrameType::SVGText)); + + if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + return ZeroBBox(*this); + } + + gfxRect rec = text->TransformFrameRectFromTextChild( + frame->GetRectRelativeToSelf(), frame); + + // Should also add the |x|, |y| of the SVGTextFrame itself, since + // the result obtained by TransformFrameRectFromTextChild doesn't + // include them. + rec.x += float(text->GetPosition().x) / AppUnitsPerCSSPixel(); + rec.y += float(text->GetPosition().y) / AppUnitsPerCSSPixel(); + + return do_AddRef(new SVGRect(this, ToRect(rec))); + } + + if (!NS_SVGNewGetBBoxEnabled()) { + return do_AddRef(new SVGRect( + this, ToRect(SVGUtils::GetBBox( + frame, SVGUtils::eBBoxIncludeFillGeometry | + SVGUtils::eUseUserSpaceOfUseElement)))); + } + uint32_t flags = 0; + if (aOptions.mFill) { + flags |= SVGUtils::eBBoxIncludeFill; + } + if (aOptions.mStroke) { + flags |= SVGUtils::eBBoxIncludeStroke; + } + if (aOptions.mMarkers) { + flags |= SVGUtils::eBBoxIncludeMarkers; + } + if (aOptions.mClipped) { + flags |= SVGUtils::eBBoxIncludeClipped; + } + if (flags == 0) { + return do_AddRef(new SVGRect(this, gfx::Rect())); + } + if (flags == SVGUtils::eBBoxIncludeMarkers || + flags == SVGUtils::eBBoxIncludeClipped) { + flags |= SVGUtils::eBBoxIncludeFill; + } + flags |= SVGUtils::eUseUserSpaceOfUseElement; + return do_AddRef(new SVGRect(this, ToRect(SVGUtils::GetBBox(frame, flags)))); +} + +already_AddRefed<SVGMatrix> SVGGraphicsElement::GetCTM() { + Document* currentDoc = GetComposedDoc(); + if (currentDoc) { + // Flush all pending notifications so that our frames are up to date + currentDoc->FlushPendingNotifications(FlushType::Layout); + } + gfx::Matrix m = SVGContentUtils::GetCTM(this, false); + RefPtr<SVGMatrix> mat = + m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m)); + return mat.forget(); +} + +already_AddRefed<SVGMatrix> SVGGraphicsElement::GetScreenCTM() { + Document* currentDoc = GetComposedDoc(); + if (currentDoc) { + // Flush all pending notifications so that our frames are up to date + currentDoc->FlushPendingNotifications(FlushType::Layout); + } + gfx::Matrix m = SVGContentUtils::GetCTM(this, true); + RefPtr<SVGMatrix> mat = + m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m)); + return mat.forget(); +} + +bool SVGGraphicsElement::IsSVGFocusable(bool* aIsFocusable, + int32_t* aTabIndex) { + // XXXedgar, maybe we could factor out the common code for SVG, HTML and + // MathML elements, see bug 1586011. + if (!IsInComposedDoc() || IsInDesignMode()) { + // In designMode documents we only allow focusing the document. + if (aTabIndex) { + *aTabIndex = -1; + } + + *aIsFocusable = false; + + return true; + } + + int32_t tabIndex = TabIndex(); + + if (aTabIndex) { + *aTabIndex = tabIndex; + } + + // If a tabindex is specified at all, or the default tabindex is 0, we're + // focusable + *aIsFocusable = tabIndex >= 0 || GetTabIndexAttrValue().isSome(); + + return false; +} + +bool SVGGraphicsElement::IsFocusableInternal(int32_t* aTabIndex, + bool aWithMouse) { + bool isFocusable = false; + IsSVGFocusable(&isFocusable, aTabIndex); + return isFocusable; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGraphicsElement.h b/dom/svg/SVGGraphicsElement.h new file mode 100644 index 0000000000..48b99d0933 --- /dev/null +++ b/dom/svg/SVGGraphicsElement.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGRAPHICSELEMENT_H_ +#define DOM_SVG_SVGGRAPHICSELEMENT_H_ + +#include "mozilla/dom/SVGTests.h" +#include "mozilla/dom/SVGTransformableElement.h" + +namespace mozilla::dom { + +using SVGGraphicsElementBase = SVGTransformableElement; + +class SVGGraphicsElement : public SVGGraphicsElementBase, public SVGTests { + protected: + explicit SVGGraphicsElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + ~SVGGraphicsElement() = default; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + // WebIDL + SVGElement* GetNearestViewportElement(); + SVGElement* GetFarthestViewportElement(); + MOZ_CAN_RUN_SCRIPT + already_AddRefed<SVGRect> GetBBox(const SVGBoundingBoxOptions&); + already_AddRefed<SVGMatrix> GetCTM(); + already_AddRefed<SVGMatrix> GetScreenCTM(); + + bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override; + bool IsSVGGraphicsElement() const final { return true; } + + // Overrides SVGTests. + SVGElement* AsSVGElement() final { return this; } + + protected: + // returns true if focusability has been definitively determined otherwise + // false + bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex); + + template <typename T> + bool IsInLengthInfo(const nsAtom* aAttribute, const T& aLengthInfos) const { + for (auto const& e : aLengthInfos) { + if (e.mName == aAttribute) { + return true; + } + } + return false; + } +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGGRAPHICSELEMENT_H_ diff --git a/dom/svg/SVGImageElement.cpp b/dom/svg/SVGImageElement.cpp new file mode 100644 index 0000000000..f6fb143c52 --- /dev/null +++ b/dom/svg/SVGImageElement.cpp @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGImageElement.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "imgINotificationObserver.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGImageElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/UserActivation.h" +#include "nsContentUtils.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Image) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGImageElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGImageElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGImageElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +SVGElement::StringInfo SVGImageElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGImageElement, SVGImageElementBase, + imgINotificationObserver, nsIImageLoadingContent) + +//---------------------------------------------------------------------- +// Implementation + +namespace SVGT = SVGGeometryProperty::Tags; + +SVGImageElement::SVGImageElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGImageElementBase(std::move(aNodeInfo)) { + // We start out broken + AddStatesSilently(ElementState::BROKEN); +} + +SVGImageElement::~SVGImageElement() { nsImageLoadingContent::Destroy(); } + +nsCSSPropertyID SVGImageElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_X: + return eCSSProperty_x; + case ATTR_Y: + return eCSSProperty_y; + case ATTR_WIDTH: + return eCSSProperty_width; + case ATTR_HEIGHT: + return eCSSProperty_height; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGImageElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGImageElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGImageElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGImageElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGImageElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> +SVGImageElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGImageElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +void SVGImageElement::GetDecoding(nsAString& aValue) { + GetEnumAttr(nsGkAtoms::decoding, kDecodingTableDefault->tag, aValue); +} + +already_AddRefed<Promise> SVGImageElement::Decode(ErrorResult& aRv) { + return nsImageLoadingContent::QueueDecodeAsync(aRv); +} + +//---------------------------------------------------------------------- + +nsresult SVGImageElement::LoadSVGImage(bool aForce, bool aNotify) { + // resolve href attribute + nsIURI* baseURI = GetBaseURI(); + + nsAutoString href; + if (mStringAttributes[HREF].IsExplicitlySet()) { + mStringAttributes[HREF].GetAnimValue(href, this); + } else { + mStringAttributes[XLINK_HREF].GetAnimValue(href, this); + } + href.Trim(" \t\n\r"); + + if (baseURI && !href.IsEmpty()) NS_MakeAbsoluteURI(href, href, baseURI); + + // Mark channel as urgent-start before load image if the image load is + // initaiated by a user interaction. + mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); + + return LoadImage(href, aForce, aNotify, eImageLoadType_Normal); +} + +bool SVGImageElement::ShouldLoadImage() const { + return LoadingEnabled() && OwnerDoc()->ShouldLoadImages(); +} + +//---------------------------------------------------------------------- +// EventTarget methods: + +void SVGImageElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { + nsImageLoadingContent::AsyncEventRunning(aEvent); +} + +//---------------------------------------------------------------------- +// nsIContent methods: + +bool SVGImageElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::decoding) { + return aResult.ParseEnumValue(aValue, kDecodingTable, false, + kDecodingTableDefault); + } + } + + return SVGImageElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +nsresult SVGImageElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (aName == nsGkAtoms::href && (aNamespaceID == kNameSpaceID_None || + aNamespaceID == kNameSpaceID_XLink)) { + if (aValue) { + if (ShouldLoadImage()) { + LoadSVGImage(true, aNotify); + } + } else { + CancelImageRequests(aNotify); + } + } else if (aName == nsGkAtoms::decoding && + aNamespaceID == kNameSpaceID_None) { + // Request sync or async image decoding. + SetSyncDecodingHint( + aValue && static_cast<ImageDecodingType>(aValue->GetEnumValue()) == + ImageDecodingType::Sync); + } + return SVGImageElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +void SVGImageElement::MaybeLoadSVGImage() { + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + (NS_FAILED(LoadSVGImage(false, true)) || !LoadingEnabled())) { + CancelImageRequests(true); + } +} + +nsresult SVGImageElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGImageElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsImageLoadingContent::BindToTree(aContext, aParent); + + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + ShouldLoadImage()) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod("dom::SVGImageElement::MaybeLoadSVGImage", this, + &SVGImageElement::MaybeLoadSVGImage)); + } + + return rv; +} + +void SVGImageElement::UnbindFromTree(bool aNullParent) { + nsImageLoadingContent::UnbindFromTree(aNullParent); + SVGImageElementBase::UnbindFromTree(aNullParent); +} + +ElementState SVGImageElement::IntrinsicState() const { + return SVGImageElementBase::IntrinsicState() | + nsImageLoadingContent::ImageState(); +} + +void SVGImageElement::DestroyContent() { + nsImageLoadingContent::Destroy(); + SVGImageElementBase::DestroyContent(); +} + +NS_IMETHODIMP_(bool) +SVGImageElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = { + sViewportsMap, + }; + + return IsInLengthInfo(name, sLengthInfo) || + FindAttributeDependence(name, map) || + SVGImageElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +/* For the purposes of the update/invalidation logic pretend to + be a rectangle. */ +bool SVGImageElement::GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) { + Rect rect; + + DebugOnly<bool> ok = + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, + SVGT::Height>(this, &rect.x, &rect.y, + &rect.width, &rect.height); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (rect.IsEmpty()) { + // Rendering of the element disabled + rect.SetEmpty(); // Make sure width/height are zero and not negative + } + + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; +} + +already_AddRefed<Path> SVGImageElement::BuildPath(PathBuilder* aBuilder) { + // To get bound, the faster method GetGeometryBounds() should already return + // success. For render and hittest, SVGImageFrame should have its own + // implementation that doesn't need to build path for an image. + MOZ_ASSERT_UNREACHABLE( + "There is no reason to call BuildPath for SVGImageElement"); + return nullptr; +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGImageElement::HasValidDimensions() const { + float width, height; + + if (SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>(this, &width, + &height)) { + return width > 0 && height > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGImageElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGAnimatedPreserveAspectRatio* +SVGImageElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +SVGElement::StringAttributesInfo SVGImageElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGImageElement.h b/dom/svg/SVGImageElement.h new file mode 100644 index 0000000000..780ee78a89 --- /dev/null +++ b/dom/svg/SVGImageElement.h @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGIMAGEELEMENT_H_ +#define DOM_SVG_SVGIMAGEELEMENT_H_ + +#include "nsImageLoadingContent.h" +#include "mozilla/dom/SVGAnimatedLength.h" +#include "mozilla/dom/SVGAnimatedString.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGAnimatedPreserveAspectRatio.h" + +nsresult NS_NewSVGImageElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGImageFrame; + +namespace dom { +class DOMSVGAnimatedPreserveAspectRatio; + +using SVGImageElementBase = SVGGeometryElement; + +class SVGImageElement : public SVGImageElementBase, + public nsImageLoadingContent { + friend class mozilla::SVGImageFrame; + + protected: + explicit SVGImageElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + virtual ~SVGImageElement(); + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGImageElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + public: + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + + // EventTarget + void AsyncEventRunning(AsyncEventDispatcher* aEvent) override; + + // nsIContent interface + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + bool IsNodeOfType(uint32_t aFlags) const override { + // <image> is not really a SVGGeometryElement, we should + // ignore eSHAPE flag accepted by SVGGeometryElement. + return !(aFlags & ~eUSE_TARGET); + } + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + + ElementState IntrinsicState() const override; + + void DestroyContent() override; + + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + // SVGGeometryElement methods: + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + void MaybeLoadSVGImage(); + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> PreserveAspectRatio(); + already_AddRefed<DOMSVGAnimatedString> Href(); + + void SetDecoding(const nsAString& aDecoding, ErrorResult& aError) { + SetAttr(nsGkAtoms::decoding, aDecoding, aError); + } + void GetDecoding(nsAString& aValue); + + already_AddRefed<Promise> Decode(ErrorResult& aRv); + + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + protected: + nsresult LoadSVGImage(bool aForce, bool aNotify); + bool ShouldLoadImage() const; + + LengthAttributesInfo GetLengthInfo() override; + SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() override; + StringAttributesInfo GetStringInfo() override; + + // Override for nsImageLoadingContent. + nsIContent* AsContent() override { return this; } + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGIMAGEELEMENT_H_ diff --git a/dom/svg/SVGIntegerPairSMILType.cpp b/dom/svg/SVGIntegerPairSMILType.cpp new file mode 100644 index 0000000000..838522b272 --- /dev/null +++ b/dom/svg/SVGIntegerPairSMILType.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGIntegerPairSMILType.h" + +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "nsDebug.h" + +namespace mozilla { + +void SVGIntegerPairSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + aValue.mU.mIntPair[0] = 0; + aValue.mU.mIntPair[1] = 0; + aValue.mType = this; +} + +void SVGIntegerPairSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value"); + aValue.mU.mIntPair[0] = 0; + aValue.mU.mIntPair[1] = 0; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGIntegerPairSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + aDest.mU.mIntPair[0] = aSrc.mU.mIntPair[0]; + aDest.mU.mIntPair[1] = aSrc.mU.mIntPair[1]; + return NS_OK; +} + +bool SVGIntegerPairSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return aLeft.mU.mIntPair[0] == aRight.mU.mIntPair[0] && + aLeft.mU.mIntPair[1] == aRight.mU.mIntPair[1]; +} + +nsresult SVGIntegerPairSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aValueToAdd.mType == aDest.mType, "Trying to add invalid types"); + MOZ_ASSERT(aValueToAdd.mType == this, "Unexpected source type"); + + aDest.mU.mIntPair[0] += aValueToAdd.mU.mIntPair[0] * aCount; + aDest.mU.mIntPair[1] += aValueToAdd.mU.mIntPair[1] * aCount; + + return NS_OK; +} + +nsresult SVGIntegerPairSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); + + double delta[2]; + delta[0] = aTo.mU.mIntPair[0] - aFrom.mU.mIntPair[0]; + delta[1] = aTo.mU.mIntPair[1] - aFrom.mU.mIntPair[1]; + + aDistance = NS_hypot(delta[0], delta[1]); + return NS_OK; +} + +nsresult SVGIntegerPairSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + double currentVal[2]; + currentVal[0] = + aStartVal.mU.mIntPair[0] + + (aEndVal.mU.mIntPair[0] - aStartVal.mU.mIntPair[0]) * aUnitDistance; + currentVal[1] = + aStartVal.mU.mIntPair[1] + + (aEndVal.mU.mIntPair[1] - aStartVal.mU.mIntPair[1]) * aUnitDistance; + + aResult.mU.mIntPair[0] = NS_lround(currentVal[0]); + aResult.mU.mIntPair[1] = NS_lround(currentVal[1]); + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGIntegerPairSMILType.h b/dom/svg/SVGIntegerPairSMILType.h new file mode 100644 index 0000000000..c7c439c692 --- /dev/null +++ b/dom/svg/SVGIntegerPairSMILType.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGINTEGERPAIRSMILTYPE_H_ +#define DOM_SVG_SVGINTEGERPAIRSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +class SVGIntegerPairSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGIntegerPairSMILType* Singleton() { + static SVGIntegerPairSMILType sSingleton; + return &sSingleton; + } + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue&) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGIntegerPairSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGINTEGERPAIRSMILTYPE_H_ diff --git a/dom/svg/SVGLength.cpp b/dom/svg/SVGLength.cpp new file mode 100644 index 0000000000..abca0e71d4 --- /dev/null +++ b/dom/svg/SVGLength.cpp @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGLength.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsTextFormatter.h" +#include "SVGContentUtils.h" +#include <limits> +#include <algorithm> + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGLength_Binding; + +namespace mozilla { + +// Declare some helpers defined below: +static void GetUnitString(nsAString& unit, uint16_t unitType); +static uint16_t GetUnitTypeForString(const nsAString& unitStr); + +void SVGLength::GetValueAsString(nsAString& aValue) const { + nsTextFormatter::ssprintf(aValue, u"%g", (double)mValue); + + nsAutoString unitString; + GetUnitString(unitString, mUnit); + aValue.Append(unitString); +} + +bool SVGLength::SetValueFromString(const nsAString& aString) { + bool success; + auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); + + if (!success) { + return false; + } + + RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(token); + + float value; + + if (!SVGContentUtils::ParseNumber(iter, end, value)) { + return false; + } + + const nsAString& units = Substring(iter.get(), end.get()); + uint16_t unitType = GetUnitTypeForString(units); + if (!IsValidUnitType(unitType)) { + return false; + } + mValue = value; + mUnit = uint8_t(unitType); + return true; +} + +inline static bool IsAbsoluteUnit(uint8_t aUnit) { + return aUnit >= SVGLength_Binding::SVG_LENGTHTYPE_CM && + aUnit <= SVGLength_Binding::SVG_LENGTHTYPE_PC; +} + +/** + * Helper to convert between different CSS absolute units without the need for + * an element, which provides more flexibility at the DOM level (and without + * the need for an intermediary conversion to user units, which avoids + * unnecessary overhead and rounding error). + * + * Example usage: to find out how many centimeters there are per inch: + * + * GetAbsUnitsPerAbsUnit(SVGLength_Binding::SVG_LENGTHTYPE_CM, + * SVGLength_Binding::SVG_LENGTHTYPE_IN) + */ +inline static float GetAbsUnitsPerAbsUnit(uint8_t aUnits, uint8_t aPerUnit) { + MOZ_ASSERT(IsAbsoluteUnit(aUnits), "Not a CSS absolute unit"); + MOZ_ASSERT(IsAbsoluteUnit(aPerUnit), "Not a CSS absolute unit"); + + float CSSAbsoluteUnitConversionFactors[5][5] = { + // columns: cm, mm, in, pt, pc + // cm per...: + {1.0f, 0.1f, 2.54f, 0.035277777777777778f, 0.42333333333333333f}, + // mm per...: + {10.0f, 1.0f, 25.4f, 0.35277777777777778f, 4.2333333333333333f}, + // in per...: + {0.39370078740157481f, 0.039370078740157481f, 1.0f, 0.013888888888888889f, + 0.16666666666666667f}, + // pt per...: + {28.346456692913386f, 2.8346456692913386f, 72.0f, 1.0f, 12.0f}, + // pc per...: + {2.3622047244094489f, 0.23622047244094489f, 6.0f, 0.083333333333333333f, + 1.0f}}; + + // First absolute unit is SVG_LENGTHTYPE_CM = 6 + return CSSAbsoluteUnitConversionFactors[aUnits - 6][aPerUnit - 6]; +} + +float SVGLength::GetValueInSpecifiedUnit(uint8_t aUnit, + const SVGElement* aElement, + uint8_t aAxis) const { + if (aUnit == mUnit) { + return mValue; + } + if ((aUnit == SVGLength_Binding::SVG_LENGTHTYPE_NUMBER && + mUnit == SVGLength_Binding::SVG_LENGTHTYPE_PX) || + (aUnit == SVGLength_Binding::SVG_LENGTHTYPE_PX && + mUnit == SVGLength_Binding::SVG_LENGTHTYPE_NUMBER)) { + return mValue; + } + if (IsAbsoluteUnit(aUnit) && IsAbsoluteUnit(mUnit)) { + return mValue * GetAbsUnitsPerAbsUnit(aUnit, mUnit); + } + + // Otherwise we do a two step conversion via user units. This can only + // succeed if aElement is non-null (although that's not sufficient to + // guarantee success). + + float userUnitsPerCurrentUnit = GetUserUnitsPerUnit(aElement, aAxis); + float userUnitsPerNewUnit = + SVGLength(0.0f, aUnit).GetUserUnitsPerUnit(aElement, aAxis); + + NS_ASSERTION( + userUnitsPerCurrentUnit >= 0 || !IsFinite(userUnitsPerCurrentUnit), + "bad userUnitsPerCurrentUnit"); + NS_ASSERTION(userUnitsPerNewUnit >= 0 || !IsFinite(userUnitsPerNewUnit), + "bad userUnitsPerNewUnit"); + + float value = mValue * userUnitsPerCurrentUnit / userUnitsPerNewUnit; + + // userUnitsPerCurrentUnit could be infinity, or userUnitsPerNewUnit could + // be zero. + if (IsFinite(value)) { + return value; + } + return std::numeric_limits<float>::quiet_NaN(); +} + +#define INCHES_PER_MM_FLOAT float(0.0393700787) +#define INCHES_PER_CM_FLOAT float(0.393700787) + +float SVGLength::GetUserUnitsPerUnit(const SVGElement* aElement, + uint8_t aAxis) const { + switch (mUnit) { + case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER: + case SVGLength_Binding::SVG_LENGTHTYPE_PX: + return 1.0f; + case SVGLength_Binding::SVG_LENGTHTYPE_MM: + return INCHES_PER_MM_FLOAT * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_CM: + return INCHES_PER_CM_FLOAT * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_IN: + return GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_PT: + return (1.0f / POINTS_PER_INCH_FLOAT) * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_PC: + return (12.0f / POINTS_PER_INCH_FLOAT) * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE: + return GetUserUnitsPerPercent(aElement, aAxis); + case SVGLength_Binding::SVG_LENGTHTYPE_EMS: + return SVGContentUtils::GetFontSize(const_cast<SVGElement*>(aElement)); + case SVGLength_Binding::SVG_LENGTHTYPE_EXS: + return SVGContentUtils::GetFontXHeight(const_cast<SVGElement*>(aElement)); + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); + return std::numeric_limits<float>::quiet_NaN(); + } +} + +/* static */ +float SVGLength::GetUserUnitsPerPercent(const SVGElement* aElement, + uint8_t aAxis) { + if (aElement) { + dom::SVGViewportElement* viewportElement = aElement->GetCtx(); + if (viewportElement) { + return std::max(viewportElement->GetLength(aAxis) / 100.0f, 0.0f); + } + } + return std::numeric_limits<float>::quiet_NaN(); +} + +// Helpers: + +// These items must be at the same index as the SVGLength constants! +static const nsStaticAtom* const unitMap[] = { + nullptr, /* SVG_LENGTHTYPE_UNKNOWN */ + nullptr, /* SVG_LENGTHTYPE_NUMBER */ + nsGkAtoms::percentage, + nsGkAtoms::em, + nsGkAtoms::ex, + nsGkAtoms::px, + nsGkAtoms::cm, + nsGkAtoms::mm, + nsGkAtoms::in, + nsGkAtoms::pt, + nsGkAtoms::pc}; + +static void GetUnitString(nsAString& unit, uint16_t unitType) { + if (SVGLength::IsValidUnitType(unitType)) { + if (unitMap[unitType]) { + unitMap[unitType]->ToString(unit); + } + return; + } + MOZ_ASSERT_UNREACHABLE( + "Unknown unit type! Someone's using an SVGLength " + "with an invalid unit?"); +} + +static uint16_t GetUnitTypeForString(const nsAString& unitStr) { + if (unitStr.IsEmpty()) return SVGLength_Binding::SVG_LENGTHTYPE_NUMBER; + + nsAtom* unitAtom = NS_GetStaticAtom(unitStr); + + if (unitAtom) { + for (uint32_t i = 1; i < ArrayLength(unitMap); i++) { + if (unitMap[i] == unitAtom) { + return i; + } + } + } + return SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN; +} + +} // namespace mozilla diff --git a/dom/svg/SVGLength.h b/dom/svg/SVGLength.h new file mode 100644 index 0000000000..edadca56ef --- /dev/null +++ b/dom/svg/SVGLength.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLENGTH_H_ +#define DOM_SVG_SVGLENGTH_H_ + +#include "nsDebug.h" +#include "nsMathUtils.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/dom/SVGLengthBinding.h" + +namespace mozilla { + +namespace dom { +class SVGElement; +} + +/** + * This SVGLength class is currently used for SVGLength *list* attributes only. + * The class that is currently used for <length> attributes is + * SVGAnimatedLength. + * + * The member mUnit should always be valid, but the member mValue may be + * numeric_limits<float>::quiet_NaN() under one circumstances (see the comment + * in SetValueAndUnit below). Even if mValue is valid, some methods may return + * numeric_limits<float>::quiet_NaN() if they involve a unit conversion that + * fails - see comments below. + * + * The DOM wrapper class for this class is DOMSVGLength. + */ +class SVGLength { + public: + SVGLength() + : mValue(0.0f), + mUnit(dom::SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN) // caught by + // IsValid() + {} + + SVGLength(float aValue, uint8_t aUnit) : mValue(aValue), mUnit(aUnit) { + NS_ASSERTION(IsValid(), "Constructed an invalid length"); + } + + bool operator==(const SVGLength& rhs) const { + return mValue == rhs.mValue && mUnit == rhs.mUnit; + } + + void GetValueAsString(nsAString& aValue) const; + + /** + * This method returns true, unless there was a parse failure, in which + * case it returns false (and the length is left unchanged). + */ + bool SetValueFromString(const nsAString& aString); + + /** + * This will usually return a valid, finite number. There is one exception + * though - see the comment in SetValueAndUnit(). + */ + float GetValueInCurrentUnits() const { return mValue; } + + uint8_t GetUnit() const { return mUnit; } + + void SetValueInCurrentUnits(float aValue) { + mValue = aValue; + NS_ASSERTION(IsValid(), "Set invalid SVGLength"); + } + + void SetValueAndUnit(float aValue, uint8_t aUnit) { + mValue = aValue; + mUnit = aUnit; + + // IsValid() should always be true, with one exception: if + // SVGLengthListSMILType has to convert between unit types and the unit + // conversion is undefined, it will end up passing in and setting + // numeric_limits<float>::quiet_NaN(). Because of that we only check the + // unit here, and allow mValue to be invalid. The painting code has to be + // able to handle NaN anyway, since conversion to user units may fail in + // general. + + NS_ASSERTION(IsValidUnitType(mUnit), "Set invalid SVGLength"); + } + + /** + * If it's not possible to convert this length's value to user units, then + * this method will return numeric_limits<float>::quiet_NaN(). + */ + float GetValueInUserUnits(const dom::SVGElement* aElement, + uint8_t aAxis) const { + return mValue * GetUserUnitsPerUnit(aElement, aAxis); + } + + /** + * Get this length's value in the units specified. + * + * This method returns numeric_limits<float>::quiet_NaN() if it is not + * possible to convert the value to the specified unit. + */ + float GetValueInSpecifiedUnit(uint8_t aUnit, const dom::SVGElement* aElement, + uint8_t aAxis) const; + + bool IsPercentage() const { + return mUnit == dom::SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE; + } + + static bool IsValidUnitType(uint16_t unit) { + return unit > dom::SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN && + unit <= dom::SVGLength_Binding::SVG_LENGTHTYPE_PC; + } + + /** + * Returns the number of user units per current unit. + * + * This method returns numeric_limits<float>::quiet_NaN() if the conversion + * factor between the length's current unit and user units is undefined (see + * the comments for GetUserUnitsPerInch and GetUserUnitsPerPercent). + */ + float GetUserUnitsPerUnit(const dom::SVGElement* aElement, + uint8_t aAxis) const; + + private: +#ifdef DEBUG + bool IsValid() const { return IsFinite(mValue) && IsValidUnitType(mUnit); } +#endif + + /** + * The conversion factor between user units (CSS px) and CSS inches is + * constant: 96 px per inch. + */ + static float GetUserUnitsPerInch() { return 96.0; } + + /** + * The conversion factor between user units and percentage units depends on + * aElement being non-null, and on aElement having a viewport element + * ancestor with only appropriate SVG elements between aElement and that + * ancestor. If that's not the case, then the conversion factor is undefined. + * + * This function returns a non-negative value if the conversion factor is + * defined, otherwise it returns numeric_limits<float>::quiet_NaN(). + */ + static float GetUserUnitsPerPercent(const dom::SVGElement* aElement, + uint8_t aAxis); + + float mValue; + uint8_t mUnit; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGLENGTH_H_ diff --git a/dom/svg/SVGLengthList.cpp b/dom/svg/SVGLengthList.cpp new file mode 100644 index 0000000000..d3ded132c3 --- /dev/null +++ b/dom/svg/SVGLengthList.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGLengthList.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsString.h" +#include "SVGElement.h" +#include "SVGAnimatedLengthList.h" +#include "SVGContentUtils.h" +#include "SVGLength.h" + +namespace mozilla { + +nsresult SVGLengthList::CopyFrom(const SVGLengthList& rhs) { + if (!mLengths.Assign(rhs.mLengths, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGLengthList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + uint32_t last = mLengths.Length() - 1; + for (uint32_t i = 0; i < mLengths.Length(); ++i) { + nsAutoString length; + mLengths[i].GetValueAsString(length); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(length); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGLengthList::SetValueFromString(const nsAString& aValue) { + SVGLengthList temp; + + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional> + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + SVGLength length; + if (!length.SetValueFromString(tokenizer.nextToken())) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + if (!temp.AppendItem(length)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + if (tokenizer.separatorAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + return CopyFrom(temp); +} + +bool SVGLengthList::operator==(const SVGLengthList& rhs) const { + if (Length() != rhs.Length()) { + return false; + } + for (uint32_t i = 0; i < Length(); ++i) { + if (!(mLengths[i] == rhs.mLengths[i])) { + return false; + } + } + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGLengthList.h b/dom/svg/SVGLengthList.h new file mode 100644 index 0000000000..4f3b11a5fd --- /dev/null +++ b/dom/svg/SVGLengthList.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLENGTHLIST_H_ +#define DOM_SVG_SVGLENGTHLIST_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "SVGElement.h" +#include "nsTArray.h" +#include "SVGLength.h" +#include "mozilla/dom/SVGLengthBinding.h" + +namespace mozilla { + +namespace dom { +class DOMSVGLength; +class DOMSVGLengthList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGLengthList. + */ +class SVGLengthList { + friend class dom::DOMSVGLength; + friend class dom::DOMSVGLengthList; + friend class SVGAnimatedLengthList; + + public: + SVGLengthList() = default; + ~SVGLengthList() = default; + + SVGLengthList& operator=(const SVGLengthList& aOther) { + mLengths.ClearAndRetainStorage(); + // Best-effort, really. + Unused << mLengths.AppendElements(aOther.mLengths, fallible); + return *this; + } + + SVGLengthList(const SVGLengthList& aOther) { *this = aOther; } + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mLengths.IsEmpty(); } + + uint32_t Length() const { return mLengths.Length(); } + + const SVGLength& operator[](uint32_t aIndex) const { + return mLengths[aIndex]; + } + + bool operator==(const SVGLengthList& rhs) const; + + bool SetCapacity(uint32_t size) { + return mLengths.SetCapacity(size, fallible); + } + + void Compact() { mLengths.Compact(); } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedLengthList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGLengthList& rhs); + + SVGLength& operator[](uint32_t aIndex) { return mLengths[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mLengths.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mLengths.Clear(); } + + bool InsertItem(uint32_t aIndex, const SVGLength& aLength) { + if (aIndex >= mLengths.Length()) aIndex = mLengths.Length(); + return !!mLengths.InsertElementAt(aIndex, aLength, fallible); + } + + void ReplaceItem(uint32_t aIndex, const SVGLength& aLength) { + MOZ_ASSERT(aIndex < mLengths.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mLengths[aIndex] = aLength; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mLengths.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mLengths.RemoveElementAt(aIndex); + } + + bool AppendItem(SVGLength aLength) { + return !!mLengths.AppendElement(aLength, fallible); + } + + protected: + /* Rationale for using nsTArray<SVGLength> and not nsTArray<SVGLength, 1>: + * + * It might seem like we should use AutoTArray<SVGLength, 1> instead of + * nsTArray<SVGLength>. That would preallocate space for one SVGLength and + * avoid an extra memory allocation call in the common case of the 'x' + * and 'y' attributes each containing a single length (and the 'dx' and 'dy' + * attributes being empty). However, consider this: + * + * An empty nsTArray uses sizeof(Header*). An AutoTArray<class E, + * uint32_t N> on the other hand uses sizeof(Header*) + + * (2 * sizeof(uint32_t)) + (N * sizeof(E)), which for one SVGLength is + * sizeof(Header*) + 16 bytes. + * + * Now consider that for text elements we have four length list attributes + * (x, y, dx, dy), each of which can have a baseVal and an animVal list. If + * we were to go the AutoTArray<SVGLength, 1> route for each of these, we'd + * end up using at least 160 bytes for these four attributes alone, even + * though we only need storage for two SVGLengths (16 bytes) in the common + * case!! + * + * A compromise might be to template SVGLengthList to allow + * SVGAnimatedLengthList to preallocate space for an SVGLength for the + * baseVal lists only, and that would cut the space used by the four + * attributes to 96 bytes. Taking that even further and templating + * SVGAnimatedLengthList too in order to only use nsTArray for 'dx' and 'dy' + * would reduce the storage further to 64 bytes. Having different types makes + * things more complicated for code that needs to look at the lists though. + * In fact it also makes things more complicated when it comes to storing the + * lists. + * + * It may be worth considering using nsAttrValue for length lists instead of + * storing them directly on the element. + */ + FallibleTArray<SVGLength> mLengths; +}; + +/** + * This SVGLengthList subclass is for SVGLengthListSMILType which needs to know + * which element and attribute a length list belongs to so that it can convert + * between unit types if necessary. + */ +class SVGLengthListAndInfo : public SVGLengthList { + public: + SVGLengthListAndInfo() + : mElement(nullptr), mAxis(0), mCanZeroPadList(false) {} + + SVGLengthListAndInfo(dom::SVGElement* aElement, uint8_t aAxis, + bool aCanZeroPadList) + : mElement(do_GetWeakReference(static_cast<nsINode*>(aElement))), + mAxis(aAxis), + mCanZeroPadList(aCanZeroPadList) {} + + void SetInfo(dom::SVGElement* aElement, uint8_t aAxis, bool aCanZeroPadList) { + mElement = do_GetWeakReference(static_cast<nsINode*>(aElement)); + mAxis = aAxis; + mCanZeroPadList = aCanZeroPadList; + } + + dom::SVGElement* Element() const { + nsCOMPtr<nsIContent> e = do_QueryReferent(mElement); + return static_cast<dom::SVGElement*>(e.get()); + } + + /** + * Returns true if this object is an "identity" value, from the perspective + * of SMIL. In other words, returns true until the initial value set up in + * SVGLengthListSMILType::Init() has been changed with a SetInfo() call. + */ + bool IsIdentity() const { + if (!mElement) { + MOZ_ASSERT(IsEmpty(), "target element propagation failure"); + return true; + } + return false; + } + + uint8_t Axis() const { + MOZ_ASSERT(mElement, "Axis() isn't valid"); + return mAxis; + } + + /** + * The value returned by this function depends on which attribute this object + * is for. If appending a list of zeros to the attribute's list would have no + * effect on rendering (e.g. the attributes 'dx' and 'dy' on <text>), then + * this method will return true. If appending a list of zeros to the + * attribute's list could *change* rendering (e.g. the attributes 'x' and 'y' + * on <text>), then this method will return false. + * + * The reason that this method exists is because the SMIL code needs to know + * what to do when it's asked to animate between lists of different length. + * If this method returns true, then it can zero pad the short list before + * carrying out its operations. However, in the case of the 'x' and 'y' + * attributes on <text>, zero would mean "zero in the current coordinate + * system", whereas we would want to pad shorter lists with the coordinates + * at which glyphs would otherwise lie, which is almost certainly not zero! + * Animating from/to zeros in this case would produce terrible results. + * + * Currently SVGLengthListSMILType simply disallows (drops) animation between + * lists of different length if it can't zero pad a list. This is to avoid + * having some authors create content that depends on undesirable behaviour + * (which would make it difficult for us to fix the behavior in future). At + * some point it would be nice to implement a callback to allow this code to + * determine padding values for lists that can't be zero padded. See + * https://bugzilla.mozilla.org/show_bug.cgi?id=573431 + */ + bool CanZeroPadList() const { + // NS_ASSERTION(mElement, "CanZeroPadList() isn't valid"); + return mCanZeroPadList; + } + + // For the SMIL code. See comment in SVGLengthListSMILType::Add(). + void SetCanZeroPadList(bool aCanZeroPadList) { + mCanZeroPadList = aCanZeroPadList; + } + + nsresult CopyFrom(const SVGLengthListAndInfo& rhs) { + mElement = rhs.mElement; + mAxis = rhs.mAxis; + mCanZeroPadList = rhs.mCanZeroPadList; + return SVGLengthList::CopyFrom(rhs); + } + + // Instances of this special subclass do not have DOM wrappers that we need + // to worry about keeping in sync, so it's safe to expose any hidden base + // class methods required by the SMIL code, as we do below. + + /** + * Exposed so that SVGLengthList baseVals can be copied to + * SVGLengthListAndInfo objects. Note that callers should also call + * SetInfo() when using this method! + */ + nsresult CopyFrom(const SVGLengthList& rhs) { + return SVGLengthList::CopyFrom(rhs); + } + const SVGLength& operator[](uint32_t aIndex) const { + return SVGLengthList::operator[](aIndex); + } + SVGLength& operator[](uint32_t aIndex) { + return SVGLengthList::operator[](aIndex); + } + bool SetLength(uint32_t aNumberOfItems) { + return SVGLengthList::SetLength(aNumberOfItems); + } + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; + uint8_t mAxis; + bool mCanZeroPadList; +}; + +/** + * This class wraps SVGLengthList objects to allow frame consumers to process + * SVGLengthList objects as if they were simply a list of float values in user + * units. When consumers request the value at a given index, this class + * dynamically converts the corresponding SVGLength from its actual unit and + * returns its value in user units. + * + * Consumers should check that the user unit values returned are finite. Even + * if the consumer can guarantee the list's element has a valid viewport + * ancestor to resolve percentage units against, and a valid presContext and + * ComputedStyle to resolve absolute and em/ex units against, unit conversions + * could still overflow. In that case the value returned will be + * numeric_limits<float>::quiet_NaN(). + */ +class MOZ_STACK_CLASS SVGUserUnitList { + public: + SVGUserUnitList() : mList(nullptr), mElement(nullptr), mAxis(0) {} + + void Init(const SVGLengthList* aList, dom::SVGElement* aElement, + uint8_t aAxis) { + mList = aList; + mElement = aElement; + mAxis = aAxis; + } + + void Clear() { mList = nullptr; } + + bool IsEmpty() const { return !mList || mList->IsEmpty(); } + + uint32_t Length() const { return mList ? mList->Length() : 0; } + + /// This may return a non-finite value + float operator[](uint32_t aIndex) const { + return (*mList)[aIndex].GetValueInUserUnits(mElement, mAxis); + } + + bool HasPercentageValueAt(uint32_t aIndex) const { + const SVGLength& length = (*mList)[aIndex]; + return length.GetUnit() == + dom::SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE; + } + + private: + const SVGLengthList* mList; + dom::SVGElement* mElement; + uint8_t mAxis; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGLENGTHLIST_H_ diff --git a/dom/svg/SVGLengthListSMILType.cpp b/dom/svg/SVGLengthListSMILType.cpp new file mode 100644 index 0000000000..080bcce431 --- /dev/null +++ b/dom/svg/SVGLengthListSMILType.cpp @@ -0,0 +1,290 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGLengthListSMILType.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "SVGLengthList.h" +#include <math.h> +#include <algorithm> + +namespace mozilla { + +/*static*/ +SVGLengthListSMILType SVGLengthListSMILType::sSingleton; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGLengthListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + SVGLengthListAndInfo* lengthList = new SVGLengthListAndInfo(); + + // See the comment documenting Init() in our header file: + lengthList->SetCanZeroPadList(true); + + aValue.mU.mPtr = lengthList; + aValue.mType = this; +} + +void SVGLengthListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast<SVGLengthListAndInfo*>(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGLengthListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGLengthListAndInfo* src = + static_cast<const SVGLengthListAndInfo*>(aSrc.mU.mPtr); + SVGLengthListAndInfo* dest = + static_cast<SVGLengthListAndInfo*>(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGLengthListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast<const SVGLengthListAndInfo*>(aLeft.mU.mPtr) == + *static_cast<const SVGLengthListAndInfo*>(aRight.mU.mPtr); +} + +nsresult SVGLengthListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGLengthListAndInfo& dest = + *static_cast<SVGLengthListAndInfo*>(aDest.mU.mPtr); + const SVGLengthListAndInfo& valueToAdd = + *static_cast<const SVGLengthListAndInfo*>(aValueToAdd.mU.mPtr); + + // To understand this code, see the comments documenting our Init() method, + // and documenting SVGLengthListAndInfo::CanZeroPadList(). + + // Note that *this* method actually may safely zero pad a shorter list + // regardless of the value returned by CanZeroPadList() for that list, + // just so long as the shorter list is being added *to* the longer list + // and *not* vice versa! It's okay in the case of adding a shorter list to a + // longer list because during the add operation we'll end up adding the + // zeros to actual specified values. It's *not* okay in the case of adding a + // longer list to a shorter list because then we end up adding to implicit + // zeros when we'd actually need to add to whatever the underlying values + // should be, not zeros, and those values are not explicit or otherwise + // available. + + if (valueToAdd.IsIdentity()) { // Adding identity value - no-op + return NS_OK; + } + + if (dest.IsIdentity()) { // Adding *to* an identity value + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i].SetValueAndUnit(valueToAdd[i].GetValueInCurrentUnits() * aCount, + valueToAdd[i].GetUnit()); + } + dest.SetInfo( + valueToAdd.Element(), valueToAdd.Axis(), + valueToAdd.CanZeroPadList()); // propagate target element info! + return NS_OK; + } + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + + // Zero-pad our |dest| list, if necessary. + if (dest.Length() < valueToAdd.Length()) { + if (!dest.CanZeroPadList()) { + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(valueToAdd.CanZeroPadList(), + "values disagree about attribute's zero-paddibility"); + + uint32_t i = dest.Length(); + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (; i < valueToAdd.Length(); ++i) { + dest[i].SetValueAndUnit(0.0f, valueToAdd[i].GetUnit()); + } + } + + for (uint32_t i = 0; i < valueToAdd.Length(); ++i) { + float valToAdd; + if (dest[i].GetUnit() == valueToAdd[i].GetUnit()) { + valToAdd = valueToAdd[i].GetValueInCurrentUnits(); + } else { + // If units differ, we use the unit of the item in 'dest'. + // We leave it to the frame code to check that values are finite. + valToAdd = valueToAdd[i].GetValueInSpecifiedUnit( + dest[i].GetUnit(), dest.Element(), dest.Axis()); + } + dest[i].SetValueAndUnit( + dest[i].GetValueInCurrentUnits() + valToAdd * aCount, + dest[i].GetUnit()); + } + + // propagate target element info! + dest.SetInfo(valueToAdd.Element(), valueToAdd.Axis(), + dest.CanZeroPadList() && valueToAdd.CanZeroPadList()); + + return NS_OK; +} + +nsresult SVGLengthListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + const SVGLengthListAndInfo& from = + *static_cast<const SVGLengthListAndInfo*>(aFrom.mU.mPtr); + const SVGLengthListAndInfo& to = + *static_cast<const SVGLengthListAndInfo*>(aTo.mU.mPtr); + + // To understand this code, see the comments documenting our Init() method, + // and documenting SVGLengthListAndInfo::CanZeroPadList(). + + NS_ASSERTION((from.CanZeroPadList() == to.CanZeroPadList()) || + (from.CanZeroPadList() && from.IsEmpty()) || + (to.CanZeroPadList() && to.IsEmpty()), + "Only \"zero\" SMILValues from the SMIL engine should " + "return true for CanZeroPadList() when the attribute " + "being animated can't be zero padded"); + + if ((from.Length() < to.Length() && !from.CanZeroPadList()) || + (to.Length() < from.Length() && !to.CanZeroPadList())) { + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + // We return the root of the sum of the squares of the deltas between the + // user unit values of the lengths at each correspanding index. In the + // general case, paced animation is probably not useful, but this strategy at + // least does the right thing for paced animation in the face of simple + // 'values' lists such as: + // + // values="100 200 300; 101 201 301; 110 210 310" + // + // I.e. half way through the simple duration we'll get "105 205 305". + + double total = 0.0; + + uint32_t i = 0; + for (; i < from.Length() && i < to.Length(); ++i) { + double f = from[i].GetValueInUserUnits(from.Element(), from.Axis()); + double t = to[i].GetValueInUserUnits(to.Element(), to.Axis()); + double delta = t - f; + total += delta * delta; + } + + // In the case that from.Length() != to.Length(), one of the following loops + // will run. (OK since CanZeroPadList()==true for the other list.) + + for (; i < from.Length(); ++i) { + double f = from[i].GetValueInUserUnits(from.Element(), from.Axis()); + total += f * f; + } + for (; i < to.Length(); ++i) { + double t = to[i].GetValueInUserUnits(to.Element(), to.Axis()); + total += t * t; + } + + float distance = sqrt(total); + if (!IsFinite(distance)) { + return NS_ERROR_FAILURE; + } + aDistance = distance; + return NS_OK; +} + +nsresult SVGLengthListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGLengthListAndInfo& start = + *static_cast<const SVGLengthListAndInfo*>(aStartVal.mU.mPtr); + const SVGLengthListAndInfo& end = + *static_cast<const SVGLengthListAndInfo*>(aEndVal.mU.mPtr); + SVGLengthListAndInfo& result = + *static_cast<SVGLengthListAndInfo*>(aResult.mU.mPtr); + + // To understand this code, see the comments documenting our Init() method, + // and documenting SVGLengthListAndInfo::CanZeroPadList(). + + NS_ASSERTION((start.CanZeroPadList() == end.CanZeroPadList()) || + (start.CanZeroPadList() && start.IsEmpty()) || + (end.CanZeroPadList() && end.IsEmpty()), + "Only \"zero\" SMILValues from the SMIL engine should " + "return true for CanZeroPadList() when the attribute " + "being animated can't be zero padded"); + + if ((start.Length() < end.Length() && !start.CanZeroPadList()) || + (end.Length() < start.Length() && !end.CanZeroPadList())) { + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + if (!result.SetLength(std::max(start.Length(), end.Length()))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t i = 0; + for (; i < start.Length() && i < end.Length(); ++i) { + float s; + if (start[i].GetUnit() == end[i].GetUnit()) { + s = start[i].GetValueInCurrentUnits(); + } else { + // If units differ, we use the unit of the item in 'end'. + // We leave it to the frame code to check that values are finite. + s = start[i].GetValueInSpecifiedUnit(end[i].GetUnit(), end.Element(), + end.Axis()); + } + float e = end[i].GetValueInCurrentUnits(); + result[i].SetValueAndUnit(s + (e - s) * aUnitDistance, end[i].GetUnit()); + } + + // In the case that start.Length() != end.Length(), one of the following + // loops will run. (Okay, since CanZeroPadList()==true for the other list.) + + for (; i < start.Length(); ++i) { + result[i].SetValueAndUnit( + start[i].GetValueInCurrentUnits() - + start[i].GetValueInCurrentUnits() * aUnitDistance, + start[i].GetUnit()); + } + for (; i < end.Length(); ++i) { + result[i].SetValueAndUnit(end[i].GetValueInCurrentUnits() * aUnitDistance, + end[i].GetUnit()); + } + + // propagate target element info! + result.SetInfo(end.Element(), end.Axis(), + start.CanZeroPadList() && end.CanZeroPadList()); + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGLengthListSMILType.h b/dom/svg/SVGLengthListSMILType.h new file mode 100644 index 0000000000..ebfccdb85f --- /dev/null +++ b/dom/svg/SVGLengthListSMILType.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLENGTHLISTSMILTYPE_H_ +#define DOM_SVG_SVGLENGTHLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGLengthListSMILType +// +// Operations for animating an SVGLengthList. +// +class SVGLengthListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGLengthListSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + + /** + * When this method initializes the SVGLengthListAndInfo for its SMILValue + * argument, it has to blindly set its mCanZeroPadList to true despite + * the fact that some attributes can't be zero-padded. (See the explaination + * that follows.) SVGAnimatedLengthList::SMILAnimatedLengthList's + * GetBaseValue() and ValueFromString() methods then override this for the + * SMILValue objects that they create to set this flag to the appropriate + * value for the attribute in question. + * + * The reason that we default to setting the mCanZeroPadList to true is + * because the SMIL engine creates "zero" valued SMILValue objects for + * intermediary calculations, and may pass such a SMILValue (along with a + * SMILValue from an animation element - that is a SMILValue created by + * SVGAnimatedLengthList::SMILAnimatedLengthList's GetBaseValue() or + * ValueFromString() methods) into the Add(), ComputeDistance() or + * Interpolate() methods below. Even in the case of animation of list + * attributes that may *not* be padded with zeros (such as 'x' and 'y' on the + * <text> element), we need to allow zero-padding of these "zero" valued + * SMILValue's lists. One reason for this is illustrated by the following + * example: + * + * <text x="2 4">foo + * <animate by="2 2" .../> + * </text> + * + * In this example there are two SMIL animation layers to be sandwiched: the + * base layer, and the layer created for the <animate> element. The SMIL + * engine calculates the result of each layer *independently*, before + * compositing the results together. Thus for the <animate> sandwich layer + * the SMIL engine interpolates between a "zero" SMILValue that it creates + * (since there is no explicit "from") and the "2 2", before the result of + * that interpolation is added to the "2 4" from the base layer. Clearly for + * the interpolation between the "zero" SMILValue and "2 2" to work, the + * "zero" SMILValue's SVGLengthListAndInfo must be zero paddable - hence + * why this method always sets mCanZeroPadList to true. + * + * (Since the Add(), ComputeDistance() and Interpolate() methods may be + * passed two input SMILValue objects for which CanZeroPadList() returns + * opposite values, these methods must be careful what they set the flag to + * on the SMILValue that they output. If *either* of the input SMILValues + * has an SVGLengthListAndInfo for which CanZeroPadList() returns false, + * then they must set the flag to false on the output SMILValue too. If + * the methods failed to do that, then when the result SMILValue objects + * from each sandwich layer are composited together, we could end up allowing + * animation between lists of different length when we should not!) + */ + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGLengthListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGLENGTHLISTSMILTYPE_H_ diff --git a/dom/svg/SVGLineElement.cpp b/dom/svg/SVGLineElement.cpp new file mode 100644 index 0000000000..317529ea10 --- /dev/null +++ b/dom/svg/SVGLineElement.cpp @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGLineElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGLineElementBinding.h" +#include "mozilla/gfx/2D.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Line) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGLineElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGLineElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGLineElement::sLengthInfo[4] = { + {nsGkAtoms::x1, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y1, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::x2, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y2, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGLineElement::SVGLineElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGLineElementBase(std::move(aNodeInfo)) {} + +void SVGLineElement::MaybeAdjustForZeroLength(float aX1, float aY1, float& aX2, + float aY2) { + if (aX1 == aX2 && aY1 == aY2) { + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, this, nullptr, nullptr, + SVGContentUtils::eIgnoreStrokeDashing); + + if (strokeOptions.mLineCap != CapStyle::BUTT) { + float tinyLength = + strokeOptions.mLineWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR; + aX2 += tinyLength; + } + } +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGLineElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGLineElement::X1() { + return mLengthAttributes[ATTR_X1].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGLineElement::Y1() { + return mLengthAttributes[ATTR_Y1].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGLineElement::X2() { + return mLengthAttributes[ATTR_X2].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGLineElement::Y2() { + return mLengthAttributes[ATTR_Y2].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGLineElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sMarkersMap}; + + return FindAttributeDependence(name, map) || + SVGLineElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::LengthAttributesInfo SVGLineElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +void SVGLineElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) { + float x1, y1, x2, y2; + + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + float angle = std::atan2(y2 - y1, x2 - x1); + + aMarks->AppendElement(SVGMark(x1, y1, angle, SVGMark::eStart)); + aMarks->AppendElement(SVGMark(x2, y2, angle, SVGMark::eEnd)); +} + +void SVGLineElement::GetAsSimplePath(SimplePath* aSimplePath) { + float x1, y1, x2, y2; + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + MaybeAdjustForZeroLength(x1, y1, x2, y2); + aSimplePath->SetLine(x1, y1, x2, y2); +} + +already_AddRefed<Path> SVGLineElement::BuildPath(PathBuilder* aBuilder) { + float x1, y1, x2, y2; + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + MaybeAdjustForZeroLength(x1, y1, x2, y2); + aBuilder->MoveTo(Point(x1, y1)); + aBuilder->LineTo(Point(x2, y2)); + + return aBuilder->Finish(); +} + +bool SVGLineElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { + float x1, y1, x2, y2; + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + if (aStrokeOptions.mLineWidth <= 0) { + *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x1, y1)), Size()); + aBounds->ExpandToEnclose(aToBoundsSpace.TransformPoint(Point(x2, y2))); + return true; + } + + // transform from non-scaling-stroke space to the space in which we compute + // bounds + Matrix nonScalingToBounds; + if (aToNonScalingStrokeSpace) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + Matrix nonScalingToUser = aToNonScalingStrokeSpace->Inverse(); + nonScalingToBounds = nonScalingToUser * aToBoundsSpace; + } + + if (aStrokeOptions.mLineCap == CapStyle::ROUND) { + if (!aToBoundsSpace.IsRectilinear() || + (aToNonScalingStrokeSpace && + !aToNonScalingStrokeSpace->IsRectilinear())) { + // TODO: handle this case. + return false; + } + Rect bounds(Point(x1, y1), Size()); + bounds.ExpandToEnclose(Point(x2, y2)); + if (aToNonScalingStrokeSpace) { + bounds = aToNonScalingStrokeSpace->TransformBounds(bounds); + bounds.Inflate(aStrokeOptions.mLineWidth / 2.f); + *aBounds = nonScalingToBounds.TransformBounds(bounds); + } else { + bounds.Inflate(aStrokeOptions.mLineWidth / 2.f); + *aBounds = aToBoundsSpace.TransformBounds(bounds); + } + return true; + } + + // Handle butt and square linecap, normal and non-scaling stroke cases + // together: start with endpoints (x1, y1), (x2, y2) in the stroke space, + // compute the four corners of the stroked line, transform the corners to + // bounds space, and compute bounds there. + + if (aToNonScalingStrokeSpace) { + Point nonScalingSpaceP1, nonScalingSpaceP2; + nonScalingSpaceP1 = aToNonScalingStrokeSpace->TransformPoint(Point(x1, y1)); + nonScalingSpaceP2 = aToNonScalingStrokeSpace->TransformPoint(Point(x2, y2)); + x1 = nonScalingSpaceP1.x; + y1 = nonScalingSpaceP1.y; + x2 = nonScalingSpaceP2.x; + y2 = nonScalingSpaceP2.y; + } + + Float length = Float(NS_hypot(x2 - x1, y2 - y1)); + Float xDelta; + Float yDelta; + Point points[4]; + + if (aStrokeOptions.mLineCap == CapStyle::BUTT) { + if (length == 0.f) { + xDelta = yDelta = 0.f; + } else { + Float ratio = aStrokeOptions.mLineWidth / 2.f / length; + xDelta = ratio * (y2 - y1); + yDelta = ratio * (x2 - x1); + } + points[0] = Point(x1 - xDelta, y1 + yDelta); + points[1] = Point(x1 + xDelta, y1 - yDelta); + points[2] = Point(x2 + xDelta, y2 - yDelta); + points[3] = Point(x2 - xDelta, y2 + yDelta); + } else { + MOZ_ASSERT(aStrokeOptions.mLineCap == CapStyle::SQUARE); + if (length == 0.f) { + xDelta = yDelta = aStrokeOptions.mLineWidth / 2.f; + points[0] = Point(x1 - xDelta, y1 + yDelta); + points[1] = Point(x1 - xDelta, y1 - yDelta); + points[2] = Point(x1 + xDelta, y1 - yDelta); + points[3] = Point(x1 + xDelta, y1 + yDelta); + } else { + Float ratio = aStrokeOptions.mLineWidth / 2.f / length; + yDelta = ratio * (x2 - x1); + xDelta = ratio * (y2 - y1); + points[0] = Point(x1 - yDelta - xDelta, y1 - xDelta + yDelta); + points[1] = Point(x1 - yDelta + xDelta, y1 - xDelta - yDelta); + points[2] = Point(x2 + yDelta + xDelta, y2 + xDelta - yDelta); + points[3] = Point(x2 + yDelta - xDelta, y2 + xDelta + yDelta); + } + } + + const Matrix& toBoundsSpace = + aToNonScalingStrokeSpace ? nonScalingToBounds : aToBoundsSpace; + + *aBounds = Rect(toBoundsSpace.TransformPoint(points[0]), Size()); + for (uint32_t i = 1; i < 4; ++i) { + aBounds->ExpandToEnclose(toBoundsSpace.TransformPoint(points[i])); + } + + return true; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGLineElement.h b/dom/svg/SVGLineElement.h new file mode 100644 index 0000000000..042c726cc1 --- /dev/null +++ b/dom/svg/SVGLineElement.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLINEELEMENT_H_ +#define DOM_SVG_SVGLINEELEMENT_H_ + +#include "SVGAnimatedLength.h" +#include "SVGGeometryElement.h" + +nsresult NS_NewSVGLineElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGLineElementBase = SVGGeometryElement; + +class SVGLineElement final : public SVGLineElementBase { + protected: + explicit SVGLineElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGLineElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + // If the input line has length zero and linecaps aren't butt, adjust |aX2| by + // a tiny amount to a barely-nonzero-length line that all of our draw targets + // will render + void MaybeAdjustForZeroLength(float aX1, float aY1, float& aX2, float aY2); + + public: + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + // SVGGeometryElement methods: + bool IsMarkable() override { return true; } + void GetMarkPoints(nsTArray<SVGMark>* aMarks) override; + void GetAsSimplePath(SimplePath* aSimplePath) override; + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override; + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X1(); + already_AddRefed<DOMSVGAnimatedLength> Y1(); + already_AddRefed<DOMSVGAnimatedLength> X2(); + already_AddRefed<DOMSVGAnimatedLength> Y2(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X1, ATTR_Y1, ATTR_X2, ATTR_Y2 }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGLINEELEMENT_H_ diff --git a/dom/svg/SVGMPathElement.cpp b/dom/svg/SVGMPathElement.cpp new file mode 100644 index 0000000000..a2cd8be406 --- /dev/null +++ b/dom/svg/SVGMPathElement.cpp @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMPathElement.h" + +#include "nsDebug.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/SVGAnimateMotionElement.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "nsContentUtils.h" +#include "nsIReferrerInfo.h" +#include "mozilla/dom/SVGMPathElementBinding.h" +#include "nsIURI.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(MPath) + +namespace mozilla::dom { + +JSObject* SVGMPathElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGMPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGMPathElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, false}, + {nsGkAtoms::href, kNameSpaceID_XLink, false}}; + +// Cycle collection magic -- based on SVGUseElement +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGMPathElement) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGMPathElement, + SVGMPathElementBase) + tmp->UnlinkHrefTarget(false); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGMPathElement, + SVGMPathElementBase) + tmp->mPathTracker.Traverse(&cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGMPathElement, + SVGMPathElementBase, + nsIMutationObserver) + +// Constructor +SVGMPathElement::SVGMPathElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGMPathElementBase(std::move(aNodeInfo)), mPathTracker(this) {} + +SVGMPathElement::~SVGMPathElement() { UnlinkHrefTarget(false); } + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMPathElement) + +already_AddRefed<DOMSVGAnimatedString> SVGMPathElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGMPathElement::BindToTree(BindContext& aContext, nsINode& aParent) { + MOZ_ASSERT(!mPathTracker.get(), + "Shouldn't have href-target yet (or it should've been cleared)"); + nsresult rv = SVGMPathElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (IsInComposedDoc()) { + const nsAttrValue* hrefAttrValue = + HasAttr(kNameSpaceID_None, nsGkAtoms::href) + ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None) + : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (hrefAttrValue) { + UpdateHrefTarget(nsIContent::FromNode(aParent), + hrefAttrValue->GetStringValue()); + } + } + + return NS_OK; +} + +void SVGMPathElement::UnbindFromTree(bool aNullParent) { + UnlinkHrefTarget(true); + SVGMPathElementBase::UnbindFromTree(aNullParent); +} + +bool SVGMPathElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + bool returnVal = SVGMPathElementBase::ParseAttribute( + aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); + if ((aNamespaceID == kNameSpaceID_XLink || + aNamespaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href && IsInComposedDoc()) { + // Note: If we fail the IsInDoc call, it's ok -- we'll update the target + // on next BindToTree call. + + // Note: "href" takes priority over xlink:href. So if "xlink:href" is being + // set here, we only let that update our target if "href" is *unset*. + if (aNamespaceID != kNameSpaceID_XLink || + !mStringAttributes[HREF].IsExplicitlySet()) { + UpdateHrefTarget(GetParent(), aValue); + } + } + return returnVal; +} + +nsresult SVGMPathElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + if (!aValue && aName == nsGkAtoms::href) { + // href attr being removed. + if (aNamespaceID == kNameSpaceID_None) { + UnlinkHrefTarget(true); + + // After unsetting href, we may still have xlink:href, so we should + // try to add it back. + const nsAttrValue* xlinkHref = + mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (xlinkHref) { + UpdateHrefTarget(GetParent(), xlinkHref->GetStringValue()); + } + } else if (aNamespaceID == kNameSpaceID_XLink && + !HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { + UnlinkHrefTarget(true); + } // else: we unset some random-namespace href attribute, or unset + // xlink:href but still have href attribute, so keep the target linking + // to href. + } + + return SVGMPathElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGMPathElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//---------------------------------------------------------------------- +// nsIMutationObserver methods + +void SVGMPathElement::AttributeChanged(Element* aElement, int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::d) { + NotifyParentOfMpathChange(GetParent()); + } + } +} + +//---------------------------------------------------------------------- +// Public helper methods + +SVGGeometryElement* SVGMPathElement::GetReferencedPath() { + if (!HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) && + !HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { + MOZ_ASSERT(!mPathTracker.get(), + "We shouldn't have a href target " + "if we don't have an xlink:href or href attribute"); + return nullptr; + } + + nsIContent* genericTarget = mPathTracker.get(); + if (genericTarget && genericTarget->IsNodeOfType(nsINode::eSHAPE)) { + return static_cast<SVGGeometryElement*>(genericTarget); + } + return nullptr; +} + +//---------------------------------------------------------------------- +// Protected helper methods + +void SVGMPathElement::UpdateHrefTarget(nsIContent* aParent, + const nsAString& aHrefStr) { + nsCOMPtr<nsIURI> baseURI = GetBaseURI(); + if (nsContentUtils::IsLocalRefURL(aHrefStr)) { + baseURI = SVGObserverUtils::GetBaseURLForLocalRef(this, baseURI); + } + nsCOMPtr<nsIURI> targetURI; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), aHrefStr, + OwnerDoc(), baseURI); + + // Stop observing old target (if any) + if (mPathTracker.get()) { + mPathTracker.get()->RemoveMutationObserver(this); + } + + if (aParent) { + // Pass in |aParent| instead of |this| -- first argument is only used + // for a call to GetComposedDoc(), and |this| might not have a current + // document yet (if our caller is BindToTree). + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForSVGResources(OwnerDoc()); + + mPathTracker.ResetToURIFragmentID(aParent, targetURI, referrerInfo); + } else { + // if we don't have a parent, then there's no animateMotion element + // depending on our target, so there's no point tracking it right now. + mPathTracker.Unlink(); + } + + // Start observing new target (if any) + if (mPathTracker.get()) { + mPathTracker.get()->AddMutationObserver(this); + } + + NotifyParentOfMpathChange(aParent); +} + +void SVGMPathElement::UnlinkHrefTarget(bool aNotifyParent) { + // Stop observing old target (if any) + if (mPathTracker.get()) { + mPathTracker.get()->RemoveMutationObserver(this); + } + mPathTracker.Unlink(); + + if (aNotifyParent) { + NotifyParentOfMpathChange(GetParent()); + } +} + +void SVGMPathElement::NotifyParentOfMpathChange(nsIContent* aParent) { + if (aParent && aParent->IsSVGElement(nsGkAtoms::animateMotion)) { + SVGAnimateMotionElement* animateMotionParent = + static_cast<SVGAnimateMotionElement*>(aParent); + + animateMotionParent->MpathChanged(); + AnimationNeedsResample(); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMPathElement.h b/dom/svg/SVGMPathElement.h new file mode 100644 index 0000000000..f5f859a018 --- /dev/null +++ b/dom/svg/SVGMPathElement.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMPATHELEMENT_H_ +#define DOM_SVG_SVGMPATHELEMENT_H_ + +#include "mozilla/dom/IDTracker.h" +#include "mozilla/dom/SVGElement.h" +#include "nsStubMutationObserver.h" +#include "SVGAnimatedString.h" + +nsresult NS_NewSVGMPathElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { +class SVGGeometryElement; + +using SVGMPathElementBase = SVGElement; + +class SVGMPathElement final : public SVGMPathElementBase, + public nsStubMutationObserver { + protected: + friend nsresult(::NS_NewSVGMPathElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGMPathElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + ~SVGMPathElement(); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGMPathElement, SVGMPathElementBase) + + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + + // Element specializations + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) override; + + // Public helper method: If our xlink:href attribute links to a Shape + // element, this method returns a pointer to that element. Otherwise, + // this returns nullptr. + SVGGeometryElement* GetReferencedPath(); + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> Href(); + + protected: + /** + * Helper that provides a reference to the 'path' element with the ID that is + * referenced by the 'mpath' element's 'href' attribute, and that will + * invalidate the parent of the 'mpath' and update mutation observers to the + * new path element if the element that that ID identifies changes to a + * different element (or none). + */ + class PathElementTracker final : public IDTracker { + public: + explicit PathElementTracker(SVGMPathElement* aMpathElement) + : mMpathElement(aMpathElement) {} + + protected: + // We need to be notified when target changes, in order to request a sample + // (which will clear animation effects that used the old target-path + // and recompute the animation effects using the new target-path). + void ElementChanged(Element* aFrom, Element* aTo) override { + IDTracker::ElementChanged(aFrom, aTo); + if (aFrom) { + aFrom->RemoveMutationObserver(mMpathElement); + } + if (aTo) { + aTo->AddMutationObserver(mMpathElement); + } + mMpathElement->NotifyParentOfMpathChange(mMpathElement->GetParent()); + } + + // We need to override IsPersistent to get persistent tracking (beyond the + // first time the target changes) + bool IsPersistent() override { return true; } + + private: + SVGMPathElement* const mMpathElement; + }; + + StringAttributesInfo GetStringInfo() override; + + void UpdateHrefTarget(nsIContent* aParent, const nsAString& aHrefStr); + void UnlinkHrefTarget(bool aNotifyParent); + void NotifyParentOfMpathChange(nsIContent* aParent); + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + PathElementTracker mPathTracker; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGMPATHELEMENT_H_ diff --git a/dom/svg/SVGMarkerElement.cpp b/dom/svg/SVGMarkerElement.cpp new file mode 100644 index 0000000000..1e5fd13877 --- /dev/null +++ b/dom/svg/SVGMarkerElement.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMarkerElement.h" + +#include "nsGkAtoms.h" +#include "DOMSVGAngle.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "nsError.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGMarkerElementBinding.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/RefPtr.h" +#include "SVGContentUtils.h" + +using namespace mozilla::gfx; +using namespace mozilla::dom::SVGMarkerElement_Binding; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Marker) + +namespace mozilla::dom { + +using namespace SVGAngle_Binding; + +JSObject* SVGMarkerElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGMarkerElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGMarkerElement::sLengthInfo[4] = { + {nsGkAtoms::refX, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::refY, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::markerWidth, 3, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::markerHeight, 3, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +SVGEnumMapping SVGMarkerElement::sUnitsMap[] = { + {nsGkAtoms::strokeWidth, SVG_MARKERUNITS_STROKEWIDTH}, + {nsGkAtoms::userSpaceOnUse, SVG_MARKERUNITS_USERSPACEONUSE}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGMarkerElement::sEnumInfo[1] = { + {nsGkAtoms::markerUnits, sUnitsMap, SVG_MARKERUNITS_STROKEWIDTH}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGMarkerElement::SVGMarkerElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGMarkerElementBase(std::move(aNodeInfo)), mCoordCtx(nullptr) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMarkerElement) + +//---------------------------------------------------------------------- + +already_AddRefed<SVGAnimatedRect> SVGMarkerElement::ViewBox() { + return mViewBox.ToSVGAnimatedRect(this); +} + +already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> +SVGMarkerElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGMarkerElement::RefX() { + return mLengthAttributes[REFX].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGMarkerElement::RefY() { + return mLengthAttributes[REFY].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGMarkerElement::MarkerUnits() { + return mEnumAttributes[MARKERUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGMarkerElement::MarkerWidth() { + return mLengthAttributes[MARKERWIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGMarkerElement::MarkerHeight() { + return mLengthAttributes[MARKERHEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGMarkerElement::OrientType() { + return mOrient.ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedAngle> SVGMarkerElement::OrientAngle() { + return mOrient.ToDOMAnimatedAngle(this); +} + +void SVGMarkerElement::SetOrientToAuto() { + mOrient.SetBaseType(SVG_MARKER_ORIENT_AUTO, this, IgnoreErrors()); +} + +void SVGMarkerElement::SetOrientToAngle(DOMSVGAngle& angle, ErrorResult& rv) { + float f = angle.Value(); + if (!IsFinite(f)) { + rv.ThrowTypeError("Unknown or invalid type"); + return; + } + mOrient.SetBaseValue(f, angle.UnitType(), this, true); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGMarkerElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap, + sColorMap, + sFillStrokeMap, + sGraphicsMap}; + + return FindAttributeDependence(name, map) || + SVGMarkerElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +void SVGMarkerElement::SetParentCoordCtxProvider(SVGViewportElement* aContext) { + mCoordCtx = aContext; + mViewBoxToViewportTransform = nullptr; +} + +/* virtual */ +bool SVGMarkerElement::HasValidDimensions() const { + return (!mLengthAttributes[MARKERWIDTH].IsExplicitlySet() || + mLengthAttributes[MARKERWIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[MARKERHEIGHT].IsExplicitlySet() || + mLengthAttributes[MARKERHEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGMarkerElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGMarkerElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGAnimatedOrient* SVGMarkerElement::GetAnimatedOrient() { return &mOrient; } + +SVGAnimatedViewBox* SVGMarkerElement::GetAnimatedViewBox() { return &mViewBox; } + +SVGAnimatedPreserveAspectRatio* +SVGMarkerElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +//---------------------------------------------------------------------- +// public helpers + +gfx::Matrix SVGMarkerElement::GetMarkerTransform(float aStrokeWidth, + const SVGMark& aMark) { + float scale = + mEnumAttributes[MARKERUNITS].GetAnimValue() == SVG_MARKERUNITS_STROKEWIDTH + ? aStrokeWidth + : 1.0f; + + float angle; + switch (mOrient.GetAnimType()) { + case SVG_MARKER_ORIENT_AUTO: + angle = aMark.angle; + break; + case SVG_MARKER_ORIENT_AUTO_START_REVERSE: + angle = aMark.angle + (aMark.type == SVGMark::eStart ? M_PI : 0.0f); + break; + default: // SVG_MARKER_ORIENT_ANGLE + angle = mOrient.GetAnimValue() * M_PI / 180.0f; + break; + } + + return gfx::Matrix(cos(angle) * scale, sin(angle) * scale, + -sin(angle) * scale, cos(angle) * scale, aMark.x, aMark.y); +} + +SVGViewBox SVGMarkerElement::GetViewBox() { + if (mViewBox.HasRect()) { + return mViewBox.GetAnimValue(); + } + return SVGViewBox(0, 0, + mLengthAttributes[MARKERWIDTH].GetAnimValue(mCoordCtx), + mLengthAttributes[MARKERHEIGHT].GetAnimValue(mCoordCtx)); +} + +gfx::Matrix SVGMarkerElement::GetViewBoxTransform() { + if (!mViewBoxToViewportTransform) { + float viewportWidth = + mLengthAttributes[MARKERWIDTH].GetAnimValue(mCoordCtx); + float viewportHeight = + mLengthAttributes[MARKERHEIGHT].GetAnimValue(mCoordCtx); + + SVGViewBox viewbox = GetViewBox(); + + MOZ_ASSERT(viewbox.width > 0.0f && viewbox.height > 0.0f, + "Rendering should be disabled"); + + gfx::Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform( + viewportWidth, viewportHeight, viewbox.x, viewbox.y, viewbox.width, + viewbox.height, mPreserveAspectRatio); + + float refX = mLengthAttributes[REFX].GetAnimValue(mCoordCtx); + float refY = mLengthAttributes[REFY].GetAnimValue(mCoordCtx); + + gfx::Point ref = viewBoxTM.TransformPoint(gfx::Point(refX, refY)); + + Matrix TM = viewBoxTM; + TM.PostTranslate(-ref.x, -ref.y); + + mViewBoxToViewportTransform = MakeUnique<gfx::Matrix>(TM); + } + + return *mViewBoxToViewportTransform; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMarkerElement.h b/dom/svg/SVGMarkerElement.h new file mode 100644 index 0000000000..8ee2e1d39d --- /dev/null +++ b/dom/svg/SVGMarkerElement.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMARKERELEMENT_H_ +#define DOM_SVG_SVGMARKERELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedOrient.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedViewBox.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGMarkerElementBinding.h" +#include "mozilla/UniquePtr.h" + +nsresult NS_NewSVGMarkerElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { + +struct SVGMark; +class SVGMarkerFrame; + +namespace dom { + +class DOMSVGAnimatedAngle; +class DOMSVGAnimatedEnumeration; + +using SVGMarkerElementBase = SVGElement; + +class SVGMarkerElement : public SVGMarkerElementBase { + friend class mozilla::SVGMarkerFrame; + + protected: + friend nsresult(::NS_NewSVGMarkerElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGMarkerElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // public helpers + gfx::Matrix GetMarkerTransform(float aStrokeWidth, const SVGMark& aMark); + SVGViewBox GetViewBox(); + gfx::Matrix GetViewBoxTransform(); + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<SVGAnimatedRect> ViewBox(); + already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> PreserveAspectRatio(); + already_AddRefed<DOMSVGAnimatedLength> RefX(); + already_AddRefed<DOMSVGAnimatedLength> RefY(); + already_AddRefed<DOMSVGAnimatedEnumeration> MarkerUnits(); + already_AddRefed<DOMSVGAnimatedLength> MarkerWidth(); + already_AddRefed<DOMSVGAnimatedLength> MarkerHeight(); + already_AddRefed<DOMSVGAnimatedEnumeration> OrientType(); + already_AddRefed<DOMSVGAnimatedAngle> OrientAngle(); + void SetOrientToAuto(); + void SetOrientToAngle(DOMSVGAngle& angle, ErrorResult& rv); + + protected: + void SetParentCoordCtxProvider(SVGViewportElement* aContext); + + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + SVGAnimatedOrient* GetAnimatedOrient() override; + virtual SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() + override; + SVGAnimatedViewBox* GetAnimatedViewBox() override; + + enum { REFX, REFY, MARKERWIDTH, MARKERHEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { MARKERUNITS }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sUnitsMap[]; + static EnumInfo sEnumInfo[1]; + + SVGAnimatedOrient mOrient; + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + + SVGViewportElement* mCoordCtx; + UniquePtr<gfx::Matrix> mViewBoxToViewportTransform; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGMARKERELEMENT_H_ diff --git a/dom/svg/SVGMaskElement.cpp b/dom/svg/SVGMaskElement.cpp new file mode 100644 index 0000000000..0e78539f5e --- /dev/null +++ b/dom/svg/SVGMaskElement.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMaskElement.h" + +#include "nsGkAtoms.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGMaskElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Mask) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGMaskElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGMaskElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//--------------------- Masks ------------------------ + +SVGElement::LengthInfo SVGMaskElement::sLengthInfo[4] = { + {nsGkAtoms::x, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +SVGElement::EnumInfo SVGMaskElement::sEnumInfo[2] = { + {nsGkAtoms::maskUnits, sSVGUnitTypesMap, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::maskContentUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_USERSPACEONUSE}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGMaskElement::SVGMaskElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGMaskElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode method + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMaskElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGMaskElement::MaskUnits() { + return mEnumAttributes[MASKUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGMaskElement::MaskContentUnits() { + return mEnumAttributes[MASKCONTENTUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGMaskElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGMaskElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGMaskElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGMaskElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGMaskElement::HasValidDimensions() const { + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGMaskElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGMaskElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGMaskElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sColorMap, + sFEFloodMap, + sFillStrokeMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sGraphicsMap, + sMarkersMap, + sMaskMap, + sTextContentElementsMap, + sViewportsMap}; + + return FindAttributeDependence(name, map) || + SVGMaskElementBase::IsAttributeMapped(name); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMaskElement.h b/dom/svg/SVGMaskElement.h new file mode 100644 index 0000000000..f70af8ec1c --- /dev/null +++ b/dom/svg/SVGMaskElement.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMASKELEMENT_H_ +#define DOM_SVG_SVGMASKELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "mozilla/dom/SVGElement.h" + +nsresult NS_NewSVGMaskElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGMaskFrame; + +namespace dom { + +//--------------------- Masks ------------------------ + +using SVGMaskElementBase = SVGElement; + +class SVGMaskElement final : public SVGMaskElementBase { + friend class mozilla::SVGMaskFrame; + + protected: + friend nsresult(::NS_NewSVGMaskElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGMaskElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedEnumeration> MaskUnits(); + already_AddRefed<DOMSVGAnimatedEnumeration> MaskContentUnits(); + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { MASKUNITS, MASKCONTENTUNITS }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static EnumInfo sEnumInfo[2]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGMASKELEMENT_H_ diff --git a/dom/svg/SVGMatrix.cpp b/dom/svg/SVGMatrix.cpp new file mode 100644 index 0000000000..6e8647520d --- /dev/null +++ b/dom/svg/SVGMatrix.cpp @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMatrix.h" +#include "nsError.h" +#include <math.h> +#include "mozilla/dom/SVGMatrixBinding.h" +#include "mozilla/FloatingPoint.h" + +const double radPerDegree = 2.0 * M_PI / 360.0; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SVGMatrix, mTransform) + +DOMSVGTransform* SVGMatrix::GetParentObject() const { return mTransform; } + +JSObject* SVGMatrix::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGMatrix_Binding::Wrap(aCx, this, aGivenProto); +} + +void SVGMatrix::SetA(float aA, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._11 = aA; + SetMatrix(mx); +} + +void SVGMatrix::SetB(float aB, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._12 = aB; + SetMatrix(mx); +} + +void SVGMatrix::SetC(float aC, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._21 = aC; + SetMatrix(mx); +} + +void SVGMatrix::SetD(float aD, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._22 = aD; + SetMatrix(mx); +} + +void SVGMatrix::SetE(float aE, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._31 = aE; + SetMatrix(mx); +} + +void SVGMatrix::SetF(float aF, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._32 = aF; + SetMatrix(mx); +} + +already_AddRefed<SVGMatrix> SVGMatrix::Multiply(SVGMatrix& aMatrix) { + RefPtr<SVGMatrix> matrix = new SVGMatrix(aMatrix.GetMatrix() * GetMatrix()); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::Inverse(ErrorResult& rv) { + gfxMatrix mat = GetMatrix(); + if (!mat.Invert()) { + rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + RefPtr<SVGMatrix> matrix = new SVGMatrix(mat); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::Translate(float x, float y) { + RefPtr<SVGMatrix> matrix = + new SVGMatrix(gfxMatrix(GetMatrix()).PreTranslate(gfxPoint(x, y))); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::Scale(float scaleFactor) { + return ScaleNonUniform(scaleFactor, scaleFactor); +} + +already_AddRefed<SVGMatrix> SVGMatrix::ScaleNonUniform(float scaleFactorX, + float scaleFactorY) { + RefPtr<SVGMatrix> matrix = new SVGMatrix( + gfxMatrix(GetMatrix()).PreScale(scaleFactorX, scaleFactorY)); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::Rotate(float angle) { + RefPtr<SVGMatrix> matrix = + new SVGMatrix(gfxMatrix(GetMatrix()).PreRotate(angle * radPerDegree)); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::RotateFromVector(float x, float y, + ErrorResult& rv) { + if (x == 0.0 || y == 0.0) { + rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return nullptr; + } + + RefPtr<SVGMatrix> matrix = + new SVGMatrix(gfxMatrix(GetMatrix()).PreRotate(atan2(y, x))); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::FlipX() { + const gfxMatrix& mx = GetMatrix(); + RefPtr<SVGMatrix> matrix = new SVGMatrix( + gfxMatrix(-mx._11, -mx._12, mx._21, mx._22, mx._31, mx._32)); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::FlipY() { + const gfxMatrix& mx = GetMatrix(); + RefPtr<SVGMatrix> matrix = new SVGMatrix( + gfxMatrix(mx._11, mx._12, -mx._21, -mx._22, mx._31, mx._32)); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::SkewX(float angle, ErrorResult& rv) { + double ta = tan(angle * radPerDegree); + if (!IsFinite(ta)) { + rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return nullptr; + } + + const gfxMatrix& mx = GetMatrix(); + gfxMatrix skewMx(mx._11, mx._12, mx._21 + mx._11 * ta, mx._22 + mx._12 * ta, + mx._31, mx._32); + RefPtr<SVGMatrix> matrix = new SVGMatrix(skewMx); + return matrix.forget(); +} + +already_AddRefed<SVGMatrix> SVGMatrix::SkewY(float angle, ErrorResult& rv) { + double ta = tan(angle * radPerDegree); + if (!IsFinite(ta)) { + rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return nullptr; + } + + const gfxMatrix& mx = GetMatrix(); + gfxMatrix skewMx(mx._11 + mx._21 * ta, mx._12 + mx._22 * ta, mx._21, mx._22, + mx._31, mx._32); + + RefPtr<SVGMatrix> matrix = new SVGMatrix(skewMx); + return matrix.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMatrix.h b/dom/svg/SVGMatrix.h new file mode 100644 index 0000000000..2a5018c988 --- /dev/null +++ b/dom/svg/SVGMatrix.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Notes on transforms in Mozilla and the SVG code. + * + * It's important to note that the matrix convention used in the SVG standard + * is the opposite convention to the one used in the Mozilla code or, more + * specifically, the convention used in Thebes code (code using gfxMatrix). + * Whereas the SVG standard uses the column vector convention, Thebes code uses + * the row vector convention. Thus, whereas in the SVG standard you have + * [M1][M2][M3]|p|, in Thebes you have |p|'[M3]'[M2]'[M1]'. In other words, the + * following are equivalent: + * + * / a1 c1 tx1 \ / a2 c2 tx2 \ / a3 c3 tx3 \ / x \ + * SVG: | b1 d1 ty1 | | b2 d2 ty2 | | b3 d3 ty3 | | y | + * \ 0 0 1 / \ 0 0 1 / \ 0 0 1 / \ 1 / + * + * / a3 b3 0 \ / a2 b2 0 \ / a1 b1 0 \ + * Thebes: [ x y 1 ] | c3 d3 0 | | c2 d2 0 | | c1 d1 0 | + * \ tx3 ty3 1 / \ tx2 ty2 1 / \ tx1 ty1 1 / + * + * Because the Thebes representation of a transform is the transpose of the SVG + * representation, our transform order must be reversed when representing SVG + * transforms using gfxMatrix in the SVG code. Since the SVG implementation + * stores and obtains matrices in SVG order, to do this we must pre-multiply + * gfxMatrix objects that represent SVG transforms instead of post-multiplying + * them as we would for matrices using SVG's column vector convention. + * Pre-multiplying may look wrong if you're only familiar with the SVG + * convention, but in that case hopefully the above explanation clears things + * up. + */ + +#ifndef DOM_SVG_SVGMATRIX_H_ +#define DOM_SVG_SVGMATRIX_H_ + +#include "DOMSVGTransform.h" +#include "gfxMatrix.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" + +namespace mozilla::dom { + +/** + * DOM wrapper for an SVG matrix. + */ +class SVGMatrix final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SVGMatrix) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(SVGMatrix) + + /** + * Ctor for SVGMatrix objects that belong to a DOMSVGTransform. + */ + explicit SVGMatrix(DOMSVGTransform& aTransform) : mTransform(&aTransform) {} + + /** + * Ctors for SVGMatrix objects created independently of a DOMSVGTransform. + */ + // Default ctor for gfxMatrix will produce identity mx + SVGMatrix() = default; + + explicit SVGMatrix(const gfxMatrix& aMatrix) : mMatrix(aMatrix) {} + + // WebIDL + DOMSVGTransform* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + float A() const { return static_cast<float>(GetMatrix()._11); } + void SetA(float aA, ErrorResult& rv); + float B() const { return static_cast<float>(GetMatrix()._12); } + void SetB(float aB, ErrorResult& rv); + float C() const { return static_cast<float>(GetMatrix()._21); } + void SetC(float aC, ErrorResult& rv); + float D() const { return static_cast<float>(GetMatrix()._22); } + void SetD(float aD, ErrorResult& rv); + float E() const { return static_cast<float>(GetMatrix()._31); } + void SetE(float aE, ErrorResult& rv); + float F() const { return static_cast<float>(GetMatrix()._32); } + void SetF(float aF, ErrorResult& rv); + already_AddRefed<SVGMatrix> Multiply(SVGMatrix& aMatrix); + already_AddRefed<SVGMatrix> Inverse(ErrorResult& aRv); + already_AddRefed<SVGMatrix> Translate(float x, float y); + already_AddRefed<SVGMatrix> Scale(float scaleFactor); + already_AddRefed<SVGMatrix> ScaleNonUniform(float scaleFactorX, + float scaleFactorY); + already_AddRefed<SVGMatrix> Rotate(float angle); + already_AddRefed<SVGMatrix> RotateFromVector(float x, float y, + ErrorResult& aRv); + already_AddRefed<SVGMatrix> FlipX(); + already_AddRefed<SVGMatrix> FlipY(); + already_AddRefed<SVGMatrix> SkewX(float angle, ErrorResult& rv); + already_AddRefed<SVGMatrix> SkewY(float angle, ErrorResult& rv); + + private: + ~SVGMatrix() = default; + + const gfxMatrix& GetMatrix() const { + return mTransform ? mTransform->Matrixgfx() : mMatrix; + } + + void SetMatrix(const gfxMatrix& aMatrix) { + if (mTransform) { + mTransform->SetMatrix(aMatrix); + } else { + mMatrix = aMatrix; + } + } + + bool IsAnimVal() const { + return mTransform ? mTransform->IsAnimVal() : false; + } + + RefPtr<DOMSVGTransform> mTransform; + + // Typically we operate on the matrix data accessed via mTransform but for + // matrices that exist independently of an DOMSVGTransform we use mMatrix + // below. + gfxMatrix mMatrix; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGMATRIX_H_ diff --git a/dom/svg/SVGMetadataElement.cpp b/dom/svg/SVGMetadataElement.cpp new file mode 100644 index 0000000000..e2774f264a --- /dev/null +++ b/dom/svg/SVGMetadataElement.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMetadataElement.h" +#include "mozilla/dom/SVGMetadataElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Metadata) + +namespace mozilla::dom { + +JSObject* SVGMetadataElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGMetadataElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGMetadataElement::SVGMetadataElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGMetadataElementBase(std::move(aNodeInfo)) {} + +nsresult SVGMetadataElement::Init() { return NS_OK; } + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMetadataElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMetadataElement.h b/dom/svg/SVGMetadataElement.h new file mode 100644 index 0000000000..3c1669ac6a --- /dev/null +++ b/dom/svg/SVGMetadataElement.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMETADATAELEMENT_H_ +#define DOM_SVG_SVGMETADATAELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" + +nsresult NS_NewSVGMetadataElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGMetadataElementBase = SVGElement; + +class SVGMetadataElement final : public SVGMetadataElementBase { + protected: + friend nsresult(::NS_NewSVGMetadataElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGMetadataElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + nsresult Init(); + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGMETADATAELEMENT_H_ diff --git a/dom/svg/SVGMotionSMILAnimationFunction.cpp b/dom/svg/SVGMotionSMILAnimationFunction.cpp new file mode 100644 index 0000000000..65638baadc --- /dev/null +++ b/dom/svg/SVGMotionSMILAnimationFunction.cpp @@ -0,0 +1,418 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGMotionSMILAnimationFunction.h" + +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/dom/SVGPathElement.h" +#include "mozilla/dom/SVGMPathElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/SMILParserUtils.h" +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "SVGAnimatedOrient.h" +#include "SVGMotionSMILPathUtils.h" +#include "SVGMotionSMILType.h" +#include "SVGPathDataParser.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGAngle_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction() + : mRotateType(eRotateType_Explicit), + mRotateAngle(0.0f), + mPathSourceType(ePathSourceType_None), + mIsPathStale(true) // Try to initialize path on first GetValues call +{} + +void SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath( + nsAtom* aAttribute) { + bool isAffected; + if (aAttribute == nsGkAtoms::path) { + isAffected = (mPathSourceType <= ePathSourceType_PathAttr); + } else if (aAttribute == nsGkAtoms::values) { + isAffected = (mPathSourceType <= ePathSourceType_ValuesAttr); + } else if (aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to) { + isAffected = (mPathSourceType <= ePathSourceType_ToAttr); + } else if (aAttribute == nsGkAtoms::by) { + isAffected = (mPathSourceType <= ePathSourceType_ByAttr); + } else { + MOZ_ASSERT_UNREACHABLE( + "Should only call this method for path-describing " + "attrs"); + isAffected = false; + } + + if (isAffected) { + mIsPathStale = true; + mHasChanged = true; + } +} + +bool SVGMotionSMILAnimationFunction::SetAttr(nsAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult, + nsresult* aParseResult) { + // Handle motion-specific attrs + if (aAttribute == nsGkAtoms::keyPoints) { + nsresult rv = SetKeyPoints(aValue, aResult); + if (aParseResult) { + *aParseResult = rv; + } + } else if (aAttribute == nsGkAtoms::rotate) { + nsresult rv = SetRotate(aValue, aResult); + if (aParseResult) { + *aParseResult = rv; + } + } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by || + aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to || + aAttribute == nsGkAtoms::values) { + aResult.SetTo(aValue); + MarkStaleIfAttributeAffectsPath(aAttribute); + if (aParseResult) { + *aParseResult = NS_OK; + } + } else { + // Defer to superclass method + return SMILAnimationFunction::SetAttr(aAttribute, aValue, aResult, + aParseResult); + } + + return true; +} + +bool SVGMotionSMILAnimationFunction::UnsetAttr(nsAtom* aAttribute) { + if (aAttribute == nsGkAtoms::keyPoints) { + UnsetKeyPoints(); + } else if (aAttribute == nsGkAtoms::rotate) { + UnsetRotate(); + } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by || + aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to || + aAttribute == nsGkAtoms::values) { + MarkStaleIfAttributeAffectsPath(aAttribute); + } else { + // Defer to superclass method + return SMILAnimationFunction::UnsetAttr(aAttribute); + } + + return true; +} + +SMILAnimationFunction::SMILCalcMode +SVGMotionSMILAnimationFunction::GetCalcMode() const { + const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode); + if (!value) { + return CALC_PACED; // animateMotion defaults to calcMode="paced" + } + + return SMILCalcMode(value->GetEnumValue()); +} + +//---------------------------------------------------------------------- +// Helpers for GetValues + +/* + * Returns the first <mpath> child of the given element + */ + +static SVGMPathElement* GetFirstMPathChild(nsIContent* aElem) { + for (nsIContent* child = aElem->GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsSVGElement(nsGkAtoms::mpath)) { + return static_cast<SVGMPathElement*>(child); + } + } + + return nullptr; +} + +void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromBasicAttrs( + const nsIContent* aContextElem) { + MOZ_ASSERT(!HasAttr(nsGkAtoms::path), + "Should be using |path| attr if we have it"); + MOZ_ASSERT(!mPath, "regenerating when we already have path"); + MOZ_ASSERT(mPathVertices.IsEmpty(), + "regenerating when we already have vertices"); + + if (!aContextElem->IsSVGElement()) { + NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node"); + return; + } + + SVGMotionSMILPathUtils::PathGenerator pathGenerator( + static_cast<const SVGElement*>(aContextElem)); + + bool success = false; + if (HasAttr(nsGkAtoms::values)) { + // Generate path based on our values array + mPathSourceType = ePathSourceType_ValuesAttr; + const nsAString& valuesStr = GetAttr(nsGkAtoms::values)->GetStringValue(); + SVGMotionSMILPathUtils::MotionValueParser parser(&pathGenerator, + &mPathVertices); + success = SMILParserUtils::ParseValuesGeneric(valuesStr, parser); + } else if (HasAttr(nsGkAtoms::to) || HasAttr(nsGkAtoms::by)) { + // Apply 'from' value (or a dummy 0,0 'from' value) + if (HasAttr(nsGkAtoms::from)) { + const nsAString& fromStr = GetAttr(nsGkAtoms::from)->GetStringValue(); + success = pathGenerator.MoveToAbsolute(fromStr); + if (!mPathVertices.AppendElement(0.0, fallible)) { + success = false; + } + } else { + // Create dummy 'from' value at 0,0, if we're doing by-animation. + // (NOTE: We don't add the dummy 0-point to our list for *to-animation*, + // because the SMILAnimationFunction logic for to-animation doesn't + // expect a dummy value. It only expects one value: the final 'to' value.) + pathGenerator.MoveToOrigin(); + success = true; + if (!HasAttr(nsGkAtoms::to)) { + if (!mPathVertices.AppendElement(0.0, fallible)) { + success = false; + } + } + } + + // Apply 'to' or 'by' value + if (success) { + double dist; + if (HasAttr(nsGkAtoms::to)) { + mPathSourceType = ePathSourceType_ToAttr; + const nsAString& toStr = GetAttr(nsGkAtoms::to)->GetStringValue(); + success = pathGenerator.LineToAbsolute(toStr, dist); + } else { // HasAttr(nsGkAtoms::by) + mPathSourceType = ePathSourceType_ByAttr; + const nsAString& byStr = GetAttr(nsGkAtoms::by)->GetStringValue(); + success = pathGenerator.LineToRelative(byStr, dist); + } + if (success) { + if (!mPathVertices.AppendElement(dist, fallible)) { + success = false; + } + } + } + } + if (success) { + mPath = pathGenerator.GetResultingPath(); + } else { + // Parse failure. Leave path as null, and clear path-related member data. + mPathVertices.Clear(); + } +} + +void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromMpathElem( + SVGMPathElement* aMpathElem) { + mPathSourceType = ePathSourceType_Mpath; + + // Use the shape that's the target of our chosen <mpath> child. + SVGGeometryElement* shapeElem = aMpathElem->GetReferencedPath(); + if (shapeElem && shapeElem->HasValidDimensions()) { + bool ok = shapeElem->GetDistancesFromOriginToEndsOfVisibleSegments( + &mPathVertices); + if (ok && mPathVertices.Length()) { + mPath = shapeElem->GetOrBuildPathForMeasuring(); + } + } +} + +void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() { + const nsAString& pathSpec = GetAttr(nsGkAtoms::path)->GetStringValue(); + mPathSourceType = ePathSourceType_PathAttr; + + // Generate Path from |path| attr + SVGPathData path; + SVGPathDataParser pathParser(pathSpec, &path); + + // We ignore any failure returned from Parse() since the SVG spec says to + // accept all segments up to the first invalid token. Instead we must + // explicitly check that the parse produces at least one path segment (if + // the path data doesn't begin with a valid "M", then it's invalid). + pathParser.Parse(); + if (!path.Length()) { + return; + } + + mPath = path.BuildPathForMeasuring(); + bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices); + if (!ok || !mPathVertices.Length()) { + mPath = nullptr; + mPathVertices.Clear(); + } +} + +// Helper to regenerate our path representation & its list of vertices +void SVGMotionSMILAnimationFunction::RebuildPathAndVertices( + const nsIContent* aTargetElement) { + MOZ_ASSERT(mIsPathStale, "rebuilding path when it isn't stale"); + + // Clear stale data + mPath = nullptr; + mPathVertices.Clear(); + mPathSourceType = ePathSourceType_None; + + // Do we have a mpath child? if so, it trumps everything. Otherwise, we look + // through our list of path-defining attributes, in order of priority. + SVGMPathElement* firstMpathChild = GetFirstMPathChild(mAnimationElement); + + if (firstMpathChild) { + RebuildPathAndVerticesFromMpathElem(firstMpathChild); + mValueNeedsReparsingEverySample = false; + } else if (HasAttr(nsGkAtoms::path)) { + RebuildPathAndVerticesFromPathAttr(); + mValueNeedsReparsingEverySample = false; + } else { + // Get path & vertices from basic SMIL attrs: from/by/to/values + + RebuildPathAndVerticesFromBasicAttrs(aTargetElement); + mValueNeedsReparsingEverySample = true; + } + mIsPathStale = false; +} + +bool SVGMotionSMILAnimationFunction::GenerateValuesForPathAndPoints( + Path* aPath, bool aIsKeyPoints, FallibleTArray<double>& aPointDistances, + SMILValueArray& aResult) { + MOZ_ASSERT(aResult.IsEmpty(), "outparam is non-empty"); + + // If we're using "keyPoints" as our list of input distances, then we need + // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale. + double distanceMultiplier = aIsKeyPoints ? aPath->ComputeLength() : 1.0; + const uint32_t numPoints = aPointDistances.Length(); + for (uint32_t i = 0; i < numPoints; ++i) { + double curDist = aPointDistances[i] * distanceMultiplier; + if (!aResult.AppendElement(SVGMotionSMILType::ConstructSMILValue( + aPath, curDist, mRotateType, mRotateAngle), + fallible)) { + return false; + } + } + return true; +} + +nsresult SVGMotionSMILAnimationFunction::GetValues(const SMILAttr& aSMILAttr, + SMILValueArray& aResult) { + if (mIsPathStale) { + RebuildPathAndVertices(aSMILAttr.GetTargetNode()); + } + MOZ_ASSERT(!mIsPathStale, "Forgot to clear 'is path stale' state"); + + if (!mPath) { + // This could be due to e.g. a parse error. + MOZ_ASSERT(mPathVertices.IsEmpty(), "have vertices but no path"); + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(!mPathVertices.IsEmpty(), "have a path but no vertices"); + + // Now: Make the actual list of SMILValues (using keyPoints, if set) + bool isUsingKeyPoints = !mKeyPoints.IsEmpty(); + bool success = GenerateValuesForPathAndPoints( + mPath, isUsingKeyPoints, isUsingKeyPoints ? mKeyPoints : mPathVertices, + aResult); + if (!success) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +void SVGMotionSMILAnimationFunction::CheckValueListDependentAttrs( + uint32_t aNumValues) { + // Call superclass method. + SMILAnimationFunction::CheckValueListDependentAttrs(aNumValues); + + // Added behavior: Do checks specific to keyPoints. + CheckKeyPoints(); +} + +bool SVGMotionSMILAnimationFunction::IsToAnimation() const { + // Rely on inherited method, but not if we have an <mpath> child or a |path| + // attribute, because they'll override any 'to' attr we might have. + // NOTE: We can't rely on mPathSourceType, because it might not have been + // set to a useful value yet (or it might be stale). + return !GetFirstMPathChild(mAnimationElement) && !HasAttr(nsGkAtoms::path) && + SMILAnimationFunction::IsToAnimation(); +} + +void SVGMotionSMILAnimationFunction::CheckKeyPoints() { + if (!HasAttr(nsGkAtoms::keyPoints)) return; + + // attribute is ignored for calcMode="paced" (even if it's got errors) + if (GetCalcMode() == CALC_PACED) { + SetKeyPointsErrorFlag(false); + } + + if (mKeyPoints.Length() != mKeyTimes.Length()) { + // there must be exactly as many keyPoints as keyTimes + SetKeyPointsErrorFlag(true); + return; + } + + // Nothing else to check -- we can catch all keyPoints errors elsewhere. + // - Formatting & range issues will be caught in SetKeyPoints, and will + // result in an empty mKeyPoints array, which will drop us into the error + // case above. +} + +nsresult SVGMotionSMILAnimationFunction::SetKeyPoints( + const nsAString& aKeyPoints, nsAttrValue& aResult) { + mKeyPoints.Clear(); + aResult.SetTo(aKeyPoints); + + mHasChanged = true; + + if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints, false, + mKeyPoints)) { + mKeyPoints.Clear(); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void SVGMotionSMILAnimationFunction::UnsetKeyPoints() { + mKeyPoints.Clear(); + SetKeyPointsErrorFlag(false); + mHasChanged = true; +} + +nsresult SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate, + nsAttrValue& aResult) { + mHasChanged = true; + + aResult.SetTo(aRotate); + if (aRotate.EqualsLiteral("auto")) { + mRotateType = eRotateType_Auto; + } else if (aRotate.EqualsLiteral("auto-reverse")) { + mRotateType = eRotateType_AutoReverse; + } else { + mRotateType = eRotateType_Explicit; + + uint16_t angleUnit; + if (!SVGAnimatedOrient::GetValueFromString(aRotate, mRotateAngle, + &angleUnit)) { + mRotateAngle = 0.0f; // set default rotate angle + // XXX report to console? + return NS_ERROR_DOM_SYNTAX_ERR; + } + + // Convert to radian units, if we're not already in radians. + if (angleUnit != SVG_ANGLETYPE_RAD) { + mRotateAngle *= SVGAnimatedOrient::GetDegreesPerUnit(angleUnit) / + SVGAnimatedOrient::GetDegreesPerUnit(SVG_ANGLETYPE_RAD); + } + } + return NS_OK; +} + +void SVGMotionSMILAnimationFunction::UnsetRotate() { + mRotateAngle = 0.0f; // default value + mRotateType = eRotateType_Explicit; + mHasChanged = true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILAnimationFunction.h b/dom/svg/SVGMotionSMILAnimationFunction.h new file mode 100644 index 0000000000..fd49092d02 --- /dev/null +++ b/dom/svg/SVGMotionSMILAnimationFunction.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMOTIONSMILANIMATIONFUNCTION_H_ +#define DOM_SVG_SVGMOTIONSMILANIMATIONFUNCTION_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SMILAnimationFunction.h" +#include "SVGMotionSMILType.h" +#include "nsTArray.h" + +class nsAttrValue; +class nsAtom; +class nsIContent; + +namespace mozilla { + +class SMILAttr; +class SMILValue; + +namespace dom { +class SVGMPathElement; +} // namespace dom + +//---------------------------------------------------------------------- +// SVGMotionSMILAnimationFunction +// +// Subclass of SMILAnimationFunction to support a few extra features offered +// by the <animateMotion> element. +// +class SVGMotionSMILAnimationFunction final : public SMILAnimationFunction { + using Path = mozilla::gfx::Path; + + public: + SVGMotionSMILAnimationFunction(); + bool SetAttr(nsAtom* aAttribute, const nsAString& aValue, + nsAttrValue& aResult, nsresult* aParseResult = nullptr) override; + bool UnsetAttr(nsAtom* aAttribute) override; + + // Method to allow our owner-element to signal us when our <mpath> + // has changed or been added/removed. When that happens, we need to + // mark ourselves as changed so we'll get recomposed, and mark our path data + // as stale so it'll get regenerated (regardless of mPathSourceType, since + // <mpath> trumps all the other sources of path data) + void MpathChanged() { mIsPathStale = mHasChanged = true; } + + protected: + enum PathSourceType { + // NOTE: Ordering matters here. Higher-priority path-descriptors should + // have higher enumerated values + ePathSourceType_None, // uninitialized or not applicable + ePathSourceType_ByAttr, // by or from-by animation + ePathSourceType_ToAttr, // to or from-to animation + ePathSourceType_ValuesAttr, + ePathSourceType_PathAttr, + ePathSourceType_Mpath + }; + + SMILCalcMode GetCalcMode() const override; + virtual nsresult GetValues(const SMILAttr& aSMILAttr, + SMILValueArray& aResult) override; + void CheckValueListDependentAttrs(uint32_t aNumValues) override; + + bool IsToAnimation() const override; + + void CheckKeyPoints(); + nsresult SetKeyPoints(const nsAString& aKeyPoints, nsAttrValue& aResult); + void UnsetKeyPoints(); + nsresult SetRotate(const nsAString& aRotate, nsAttrValue& aResult); + void UnsetRotate(); + + // Helpers for GetValues + void MarkStaleIfAttributeAffectsPath(nsAtom* aAttribute); + void RebuildPathAndVertices(const nsIContent* aTargetElement); + void RebuildPathAndVerticesFromMpathElem(dom::SVGMPathElement* aMpathElem); + void RebuildPathAndVerticesFromPathAttr(); + void RebuildPathAndVerticesFromBasicAttrs(const nsIContent* aContextElem); + bool GenerateValuesForPathAndPoints(Path* aPath, bool aIsKeyPoints, + FallibleTArray<double>& aPointDistances, + SMILValueArray& aResult); + + // Members + // ------- + FallibleTArray<double> mKeyPoints; // parsed from "keyPoints" attribute. + + RotateType mRotateType; // auto, auto-reverse, or explicit. + float mRotateAngle; // the angle value, if explicit. + + PathSourceType mPathSourceType; // source of our Path. + RefPtr<Path> mPath; // representation of motion path. + FallibleTArray<double> mPathVertices; // distances of vertices along path. + + bool mIsPathStale; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILANIMATIONFUNCTION_H_ diff --git a/dom/svg/SVGMotionSMILAttr.cpp b/dom/svg/SVGMotionSMILAttr.cpp new file mode 100644 index 0000000000..c269e876af --- /dev/null +++ b/dom/svg/SVGMotionSMILAttr.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* representation of a dummy attribute targeted by <animateMotion> element */ + +#include "SVGMotionSMILAttr.h" + +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/SMILValue.h" +#include "SVGMotionSMILType.h" +#include "nsDebug.h" +#include "gfx2DGlue.h" + +namespace mozilla { + +nsresult SVGMotionSMILAttr::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + MOZ_ASSERT_UNREACHABLE( + "Shouldn't using SMILAttr::ValueFromString for " + "parsing animateMotion's SMIL values."); + return NS_ERROR_FAILURE; +} + +SMILValue SVGMotionSMILAttr::GetBaseValue() const { + return SMILValue(&SVGMotionSMILType::sSingleton); +} + +void SVGMotionSMILAttr::ClearAnimValue() { + mSVGElement->SetAnimateMotionTransform(nullptr); +} + +nsresult SVGMotionSMILAttr::SetAnimValue(const SMILValue& aValue) { + gfx::Matrix matrix = SVGMotionSMILType::CreateMatrix(aValue); + mSVGElement->SetAnimateMotionTransform(&matrix); + return NS_OK; +} + +const nsIContent* SVGMotionSMILAttr::GetTargetNode() const { + return mSVGElement; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILAttr.h b/dom/svg/SVGMotionSMILAttr.h new file mode 100644 index 0000000000..953aeed5b0 --- /dev/null +++ b/dom/svg/SVGMotionSMILAttr.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* representation of a dummy attribute targeted by <animateMotion> element */ + +#ifndef DOM_SVG_SVGMOTIONSMILATTR_H_ +#define DOM_SVG_SVGMOTIONSMILATTR_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" + +class nsIContent; + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +/** + * SVGMotionSMILAttr: Implements the SMILAttr interface for SMIL animations + * from <animateMotion>. + * + * NOTE: Even though there's technically no "motion" attribute, we behave in + * many ways as if there were, for simplicity. + */ +class SVGMotionSMILAttr : public SMILAttr { + public: + explicit SVGMotionSMILAttr(dom::SVGElement* aSVGElement) + : mSVGElement(aSVGElement) {} + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + nsresult SetAnimValue(const SMILValue& aValue) override; + void ClearAnimValue() override; + const nsIContent* GetTargetNode() const override; + + protected: + // Raw pointers are OK here because this SVGMotionSMILAttr is both + // created & destroyed during a SMIL sample-step, during which time the DOM + // isn't modified. + dom::SVGElement* mSVGElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILATTR_H_ diff --git a/dom/svg/SVGMotionSMILPathUtils.cpp b/dom/svg/SVGMotionSMILPathUtils.cpp new file mode 100644 index 0000000000..628b3622c1 --- /dev/null +++ b/dom/svg/SVGMotionSMILPathUtils.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGMotionSMILPathUtils.h" + +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" // for NS_ENSURE_FINITE2 +#include "SVGContentUtils.h" +#include "SVGLength.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +//---------------------------------------------------------------------- +// PathGenerator methods + +// For the dummy 'from' value used in pure by-animation & to-animation +void SVGMotionSMILPathUtils::PathGenerator::MoveToOrigin() { + MOZ_ASSERT(!mHaveReceivedCommands, + "Not expecting requests for mid-path MoveTo commands"); + mHaveReceivedCommands = true; + mPathBuilder->MoveTo(Point(0, 0)); +} + +// For 'from' and the first entry in 'values'. +bool SVGMotionSMILPathUtils::PathGenerator::MoveToAbsolute( + const nsAString& aCoordPairStr) { + MOZ_ASSERT(!mHaveReceivedCommands, + "Not expecting requests for mid-path MoveTo commands"); + mHaveReceivedCommands = true; + + float xVal, yVal; + if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) { + return false; + } + mPathBuilder->MoveTo(Point(xVal, yVal)); + return true; +} + +// For 'to' and every entry in 'values' except the first. +bool SVGMotionSMILPathUtils::PathGenerator::LineToAbsolute( + const nsAString& aCoordPairStr, double& aSegmentDistance) { + mHaveReceivedCommands = true; + + float xVal, yVal; + if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) { + return false; + } + Point initialPoint = mPathBuilder->CurrentPoint(); + + mPathBuilder->LineTo(Point(xVal, yVal)); + aSegmentDistance = NS_hypot(initialPoint.x - xVal, initialPoint.y - yVal); + return true; +} + +// For 'by'. +bool SVGMotionSMILPathUtils::PathGenerator::LineToRelative( + const nsAString& aCoordPairStr, double& aSegmentDistance) { + mHaveReceivedCommands = true; + + float xVal, yVal; + if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) { + return false; + } + mPathBuilder->LineTo(mPathBuilder->CurrentPoint() + Point(xVal, yVal)); + aSegmentDistance = NS_hypot(xVal, yVal); + return true; +} + +already_AddRefed<Path> +SVGMotionSMILPathUtils::PathGenerator::GetResultingPath() { + return mPathBuilder->Finish(); +} + +//---------------------------------------------------------------------- +// Helper / protected methods + +bool SVGMotionSMILPathUtils::PathGenerator::ParseCoordinatePair( + const nsAString& aCoordPairStr, float& aXVal, float& aYVal) { + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional> + tokenizer(aCoordPairStr, ','); + + SVGLength x, y; + + if (!tokenizer.hasMoreTokens() || + !x.SetValueFromString(tokenizer.nextToken())) { + return false; + } + + if (!tokenizer.hasMoreTokens() || + !y.SetValueFromString(tokenizer.nextToken())) { + return false; + } + + if (tokenizer.separatorAfterCurrentToken() || // Trailing comma. + tokenizer.hasMoreTokens()) { // More text remains + return false; + } + + float xRes = x.GetValueInUserUnits(mSVGElement, SVGContentUtils::X); + float yRes = y.GetValueInUserUnits(mSVGElement, SVGContentUtils::Y); + + NS_ENSURE_FINITE2(xRes, yRes, false); + + aXVal = xRes; + aYVal = yRes; + return true; +} + +//---------------------------------------------------------------------- +// MotionValueParser methods +bool SVGMotionSMILPathUtils::MotionValueParser::Parse( + const nsAString& aValueStr) { + bool success; + if (!mPathGenerator->HaveReceivedCommands()) { + // Interpret first value in "values" attribute as the path's initial MoveTo + success = mPathGenerator->MoveToAbsolute(aValueStr); + if (success) { + success = !!mPointDistances->AppendElement(0.0, fallible); + } + } else { + double dist; + success = mPathGenerator->LineToAbsolute(aValueStr, dist); + if (success) { + mDistanceSoFar += dist; + success = !!mPointDistances->AppendElement(mDistanceSoFar, fallible); + } + } + return success; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILPathUtils.h b/dom/svg/SVGMotionSMILPathUtils.h new file mode 100644 index 0000000000..02b3389303 --- /dev/null +++ b/dom/svg/SVGMotionSMILPathUtils.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Helper class to help with generating anonymous path elements for + <animateMotion> elements to use. */ + +#ifndef DOM_SVG_SVGMOTIONSMILPATHUTILS_H_ +#define DOM_SVG_SVGMOTIONSMILPATHUTILS_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SMILParserUtils.h" +#include "mozilla/gfx/2D.h" +#include "gfxPlatform.h" +#include "nsDebug.h" +#include "nsStringFwd.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { +class SVGElement; +} + +class SVGMotionSMILPathUtils { + using DrawTarget = mozilla::gfx::DrawTarget; + using Path = mozilla::gfx::Path; + using PathBuilder = mozilla::gfx::PathBuilder; + + public: + // Class to assist in generating a Path, based on + // coordinates in the <animateMotion> from/by/to/values attributes. + class PathGenerator { + public: + explicit PathGenerator(const dom::SVGElement* aSVGElement) + : mSVGElement(aSVGElement), mHaveReceivedCommands(false) { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + NS_ASSERTION( + gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget( + drawTarget), + "Should support Moz2D content drawing"); + + mPathBuilder = drawTarget->CreatePathBuilder(); + } + + // Methods for adding various path commands to output path. + // Note: aCoordPairStr is expected to be a whitespace and/or + // comma-separated x,y coordinate-pair -- see description of + // "the specified values for from, by, to, and values" at + // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement + void MoveToOrigin(); + bool MoveToAbsolute(const nsAString& aCoordPairStr); + bool LineToAbsolute(const nsAString& aCoordPairStr, + double& aSegmentDistance); + bool LineToRelative(const nsAString& aCoordPairStr, + double& aSegmentDistance); + + // Accessor to let clients check if we've received any commands yet. + inline bool HaveReceivedCommands() { return mHaveReceivedCommands; } + // Accessor to get the finalized path + already_AddRefed<Path> GetResultingPath(); + + protected: + // Helper methods + bool ParseCoordinatePair(const nsAString& aCoordPairStr, float& aXVal, + float& aYVal); + + // Member data + const dom::SVGElement* mSVGElement; // context for converting to user units + RefPtr<PathBuilder> mPathBuilder; + bool mHaveReceivedCommands; + }; + + // Class to assist in passing each subcomponent of a |values| attribute to + // a PathGenerator, for generating a corresponding Path. + class MOZ_STACK_CLASS MotionValueParser + : public SMILParserUtils::GenericValueParser { + public: + MotionValueParser(PathGenerator* aPathGenerator, + FallibleTArray<double>* aPointDistances) + : mPathGenerator(aPathGenerator), + mPointDistances(aPointDistances), + mDistanceSoFar(0.0) { + MOZ_ASSERT(mPointDistances->IsEmpty(), + "expecting point distances array to start empty"); + } + + // SMILParserUtils::GenericValueParser interface + bool Parse(const nsAString& aValueStr) override; + + protected: + PathGenerator* mPathGenerator; + FallibleTArray<double>* mPointDistances; + double mDistanceSoFar; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILPATHUTILS_H_ diff --git a/dom/svg/SVGMotionSMILType.cpp b/dom/svg/SVGMotionSMILType.cpp new file mode 100644 index 0000000000..15521ee54d --- /dev/null +++ b/dom/svg/SVGMotionSMILType.cpp @@ -0,0 +1,459 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* implementation of nsISMILType for use by <animateMotion> element */ + +#include "SVGMotionSMILType.h" + +#include "mozilla/SMILValue.h" +#include "mozilla/gfx/Point.h" +#include "gfx2DGlue.h" +#include "nsDebug.h" +#include "nsMathUtils.h" +#include "nsISupportsUtils.h" +#include "nsTArray.h" +#include <math.h> + +using namespace mozilla::gfx; + +namespace mozilla { + +/*static*/ +SVGMotionSMILType SVGMotionSMILType::sSingleton; + +// Helper enum, for distinguishing between types of MotionSegment structs +enum SegmentType { eSegmentType_Translation, eSegmentType_PathPoint }; + +// Helper Structs: containers for params to define our MotionSegment +// (either simple translation or point-on-a-path) +struct TranslationParams { // Simple translation + float mX; + float mY; +}; +struct PathPointParams { // Point along a path + // Refcounted: need to AddRef/Release. This can't be an nsRefPtr because + // this struct is used inside a union so it can't have a default constructor. + Path* MOZ_OWNING_REF mPath; + float mDistToPoint; // Distance from path start to the point on the path that + // we're interested in. +}; + +/** + * Helper Struct: MotionSegment + * + * Instances of this class represent the points that we move between during + * <animateMotion>. Each SMILValue will get a nsTArray of these (generally + * with at most 1 entry in the array, except for in SandwichAdd). (This + * matches our behavior in SVGTransformListSMILType.) + * + * NOTE: In general, MotionSegments are represented as points on a path + * (eSegmentType_PathPoint), so that we can easily interpolate and compute + * distance *along their path*. However, Add() outputs MotionSegments as + * simple translations (eSegmentType_Translation), because adding two points + * from a path (e.g. when accumulating a repeated animation) will generally + * take you to an arbitrary point *off* of the path. + */ +struct MotionSegment { + // Default constructor just locks us into being a Translation, and leaves + // other fields uninitialized (since client is presumably about to set them) + MotionSegment() + : mRotateType(eRotateType_Auto), + mRotateAngle(0.0), + mSegmentType(eSegmentType_Translation), + mU{} {} + + // Constructor for a translation + MotionSegment(float aX, float aY, float aRotateAngle) + : mRotateType(eRotateType_Explicit), + mRotateAngle(aRotateAngle), + mSegmentType(eSegmentType_Translation) { + mU.mTranslationParams.mX = aX; + mU.mTranslationParams.mY = aY; + } + + // Constructor for a point on a path (NOTE: AddRef's) + MotionSegment(Path* aPath, float aDistToPoint, RotateType aRotateType, + float aRotateAngle) + : mRotateType(aRotateType), + mRotateAngle(aRotateAngle), + mSegmentType(eSegmentType_PathPoint) { + mU.mPathPointParams.mPath = aPath; + mU.mPathPointParams.mDistToPoint = aDistToPoint; + + NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path + } + + // Copy constructor (NOTE: AddRef's if we're eSegmentType_PathPoint) + MotionSegment(const MotionSegment& aOther) + : mRotateType(aOther.mRotateType), + mRotateAngle(aOther.mRotateAngle), + mSegmentType(aOther.mSegmentType) { + if (mSegmentType == eSegmentType_Translation) { + mU.mTranslationParams = aOther.mU.mTranslationParams; + } else { // mSegmentType == eSegmentType_PathPoint + mU.mPathPointParams = aOther.mU.mPathPointParams; + NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path + } + } + + // Destructor (releases any reference we were holding onto) + ~MotionSegment() { + if (mSegmentType == eSegmentType_PathPoint) { + NS_RELEASE(mU.mPathPointParams.mPath); + } + } + + // Comparison operators + bool operator==(const MotionSegment& aOther) const { + // Compare basic params + if (mSegmentType != aOther.mSegmentType || + mRotateType != aOther.mRotateType || + (mRotateType == eRotateType_Explicit && // Technically, angle mismatch + mRotateAngle != aOther.mRotateAngle)) { // only matters for Explicit. + return false; + } + + // Compare translation params, if we're a translation. + if (mSegmentType == eSegmentType_Translation) { + return mU.mTranslationParams.mX == aOther.mU.mTranslationParams.mX && + mU.mTranslationParams.mY == aOther.mU.mTranslationParams.mY; + } + + // Else, compare path-point params, if we're a path point. + return (mU.mPathPointParams.mPath == aOther.mU.mPathPointParams.mPath) && + (mU.mPathPointParams.mDistToPoint == + aOther.mU.mPathPointParams.mDistToPoint); + } + + bool operator!=(const MotionSegment& aOther) const { + return !(*this == aOther); + } + + // Member Data + // ----------- + RotateType mRotateType; // Explicit angle vs. auto vs. auto-reverse. + float mRotateAngle; // Only used if mRotateType == eRotateType_Explicit. + const SegmentType mSegmentType; // This determines how we interpret + // mU. (const for safety/sanity) + + union { // Union to let us hold the params for either segment-type. + TranslationParams mTranslationParams; + PathPointParams mPathPointParams; + } mU; +}; + +using MotionSegmentArray = FallibleTArray<MotionSegment>; + +// Helper methods to cast SMILValue.mU.mPtr to the right pointer-type +static MotionSegmentArray& ExtractMotionSegmentArray(SMILValue& aValue) { + return *static_cast<MotionSegmentArray*>(aValue.mU.mPtr); +} + +static const MotionSegmentArray& ExtractMotionSegmentArray( + const SMILValue& aValue) { + return *static_cast<const MotionSegmentArray*>(aValue.mU.mPtr); +} + +// nsISMILType Methods +// ------------------- + +void SVGMotionSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL type"); + + aValue.mType = this; + aValue.mU.mPtr = new MotionSegmentArray(1); +} + +void SVGMotionSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL type"); + + MotionSegmentArray* arr = static_cast<MotionSegmentArray*>(aValue.mU.mPtr); + delete arr; + + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGMotionSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aSrc); + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + if (!dstArr.Assign(srcArr, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +bool SVGMotionSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL type"); + + const MotionSegmentArray& leftArr = ExtractMotionSegmentArray(aLeft); + const MotionSegmentArray& rightArr = ExtractMotionSegmentArray(aRight); + + // If array-lengths don't match, we're trivially non-equal. + if (leftArr.Length() != rightArr.Length()) { + return false; + } + + // Array-lengths match -- check each array-entry for equality. + uint32_t length = leftArr.Length(); // == rightArr->Length(), if we get here + for (uint32_t i = 0; i < length; ++i) { + if (leftArr[i] != rightArr[i]) { + return false; + } + } + + return true; // If we get here, we found no differences. +} + +// Helper method for Add & CreateMatrix +inline static void GetAngleAndPointAtDistance( + Path* aPath, float aDistance, RotateType aRotateType, + float& aRotateAngle, // in & out-param. + Point& aPoint) // out-param. +{ + if (aRotateType == eRotateType_Explicit) { + // Leave aRotateAngle as-is. + aPoint = aPath->ComputePointAtLength(aDistance); + } else { + Point tangent; // Unit vector tangent to the point we find. + aPoint = aPath->ComputePointAtLength(aDistance, &tangent); + float tangentAngle = atan2(tangent.y, tangent.x); + if (aRotateType == eRotateType_Auto) { + aRotateAngle = tangentAngle; + } else { + MOZ_ASSERT(aRotateType == eRotateType_AutoReverse); + aRotateAngle = M_PI + tangentAngle; + } + } +} + +nsresult SVGMotionSMILType::Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd); + + // We're doing a simple add here (as opposed to a sandwich add below). We + // only do this when we're accumulating a repeat result. + // NOTE: In other nsISMILTypes, we use this method with a barely-initialized + // |aDest| value to assist with "by" animation. (In this case, + // "barely-initialized" would mean dstArr.Length() would be empty.) However, + // we don't do this for <animateMotion>, because we instead use our "by" + // value to construct an equivalent "path" attribute, and we use *that* for + // our actual animation. + MOZ_ASSERT(srcArr.Length() == 1, "Invalid source segment arr to add"); + MOZ_ASSERT(dstArr.Length() == 1, "Invalid dest segment arr to add to"); + const MotionSegment& srcSeg = srcArr[0]; + const MotionSegment& dstSeg = dstArr[0]; + MOZ_ASSERT(srcSeg.mSegmentType == eSegmentType_PathPoint, + "expecting to be adding points from a motion path"); + MOZ_ASSERT(dstSeg.mSegmentType == eSegmentType_PathPoint, + "expecting to be adding points from a motion path"); + + const PathPointParams& srcParams = srcSeg.mU.mPathPointParams; + const PathPointParams& dstParams = dstSeg.mU.mPathPointParams; + + MOZ_ASSERT(srcSeg.mRotateType == dstSeg.mRotateType && + srcSeg.mRotateAngle == dstSeg.mRotateAngle, + "unexpected angle mismatch"); + MOZ_ASSERT(srcParams.mPath == dstParams.mPath, "unexpected path mismatch"); + Path* path = srcParams.mPath; + + // Use destination to get our rotate angle. + float rotateAngle = dstSeg.mRotateAngle; + Point dstPt; + GetAngleAndPointAtDistance(path, dstParams.mDistToPoint, dstSeg.mRotateType, + rotateAngle, dstPt); + + Point srcPt = path->ComputePointAtLength(srcParams.mDistToPoint); + + float newX = dstPt.x + srcPt.x * aCount; + float newY = dstPt.y + srcPt.y * aCount; + + // Replace destination's current value -- a point-on-a-path -- with the + // translation that results from our addition. + dstArr.ReplaceElementAt(0, MotionSegment(newX, newY, rotateAngle)); + return NS_OK; +} + +nsresult SVGMotionSMILType::SandwichAdd(SMILValue& aDest, + const SMILValue& aValueToAdd) const { + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd); + + // We're only expecting to be adding 1 segment on to the list + MOZ_ASSERT(srcArr.Length() == 1, + "Trying to do sandwich add of more than one value"); + + if (!dstArr.AppendElement(srcArr[0], fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult SVGMotionSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + const MotionSegmentArray& fromArr = ExtractMotionSegmentArray(aFrom); + const MotionSegmentArray& toArr = ExtractMotionSegmentArray(aTo); + + // ComputeDistance is only used for calculating distances between single + // values in a values array. So we should only have one entry in each array. + MOZ_ASSERT(fromArr.Length() == 1, "Wrong number of elements in from value"); + MOZ_ASSERT(toArr.Length() == 1, "Wrong number of elements in to value"); + + const MotionSegment& from = fromArr[0]; + const MotionSegment& to = toArr[0]; + + MOZ_ASSERT(from.mSegmentType == to.mSegmentType, + "Mismatched MotionSegment types"); + if (from.mSegmentType == eSegmentType_PathPoint) { + const PathPointParams& fromParams = from.mU.mPathPointParams; + const PathPointParams& toParams = to.mU.mPathPointParams; + MOZ_ASSERT(fromParams.mPath == toParams.mPath, + "Interpolation endpoints should be from same path"); + aDistance = std::fabs(toParams.mDistToPoint - fromParams.mDistToPoint); + } else { + const TranslationParams& fromParams = from.mU.mTranslationParams; + const TranslationParams& toParams = to.mU.mTranslationParams; + float dX = toParams.mX - fromParams.mX; + float dY = toParams.mY - fromParams.mY; + aDistance = NS_hypot(dX, dY); + } + + return NS_OK; +} + +// Helper method for Interpolate() +static inline float InterpolateFloat(const float& aStartFlt, + const float& aEndFlt, + const double& aUnitDistance) { + return aStartFlt + aUnitDistance * (aEndFlt - aStartFlt); +} + +nsresult SVGMotionSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0, + "unit distance value out of bounds"); + + const MotionSegmentArray& startArr = ExtractMotionSegmentArray(aStartVal); + const MotionSegmentArray& endArr = ExtractMotionSegmentArray(aEndVal); + MotionSegmentArray& resultArr = ExtractMotionSegmentArray(aResult); + + MOZ_ASSERT(endArr.Length() == 1, + "Invalid end-point for animateMotion interpolation"); + MOZ_ASSERT(resultArr.IsEmpty(), + "Expecting result to be just-initialized w/ empty array"); + + const MotionSegment& endSeg = endArr[0]; + MOZ_ASSERT(endSeg.mSegmentType == eSegmentType_PathPoint, + "Expecting to be interpolating along a path"); + + const PathPointParams& endParams = endSeg.mU.mPathPointParams; + // NOTE: Ususally, path & angle should match between start & end (since + // presumably start & end came from the same <animateMotion> element), + // unless start is empty. (as it would be for pure 'to' animation) + // Notable exception: when a to-animation layers on top of lower priority + // animation(s) -- see comment below. + Path* path = endParams.mPath; + RotateType rotateType = endSeg.mRotateType; + float rotateAngle = endSeg.mRotateAngle; + + float startDist; + if (startArr.IsEmpty() || + startArr[0].mU.mPathPointParams.mPath != endParams.mPath) { + // When a to-animation follows other lower priority animation(s), + // the endParams will contain a different path from the animation(s) + // that it layers on top of. + // Per SMIL spec, we should interpolate from the value at startArr. + // However, neither Chrome nor Safari implements to-animation that way. + // For simplicity, we use the same behavior as other browsers: override + // previous animations and start at the initial underlying value. + startDist = 0.0f; + } else { + MOZ_ASSERT(startArr.Length() <= 1, + "Invalid start-point for animateMotion interpolation"); + const MotionSegment& startSeg = startArr[0]; + MOZ_ASSERT(startSeg.mSegmentType == eSegmentType_PathPoint, + "Expecting to be interpolating along a path"); + const PathPointParams& startParams = startSeg.mU.mPathPointParams; + MOZ_ASSERT(startSeg.mRotateType == endSeg.mRotateType && + startSeg.mRotateAngle == endSeg.mRotateAngle, + "unexpected angle mismatch"); + startDist = startParams.mDistToPoint; + } + + // Get the interpolated distance along our path. + float resultDist = + InterpolateFloat(startDist, endParams.mDistToPoint, aUnitDistance); + + // Construct the intermediate result segment, and put it in our outparam. + // AppendElement has guaranteed success here, since Init() allocates 1 slot. + MOZ_ALWAYS_TRUE(resultArr.AppendElement( + MotionSegment(path, resultDist, rotateType, rotateAngle), fallible)); + return NS_OK; +} + +/* static */ gfx::Matrix SVGMotionSMILType::CreateMatrix( + const SMILValue& aSMILVal) { + const MotionSegmentArray& arr = ExtractMotionSegmentArray(aSMILVal); + + gfx::Matrix matrix; + uint32_t length = arr.Length(); + for (uint32_t i = 0; i < length; i++) { + Point point; // initialized below + float rotateAngle = arr[i].mRotateAngle; // might get updated below + if (arr[i].mSegmentType == eSegmentType_Translation) { + point.x = arr[i].mU.mTranslationParams.mX; + point.y = arr[i].mU.mTranslationParams.mY; + MOZ_ASSERT(arr[i].mRotateType == eRotateType_Explicit, + "'auto'/'auto-reverse' should have been converted to " + "explicit angles when we generated this translation"); + } else { + GetAngleAndPointAtDistance(arr[i].mU.mPathPointParams.mPath, + arr[i].mU.mPathPointParams.mDistToPoint, + arr[i].mRotateType, rotateAngle, point); + } + matrix.PreTranslate(point.x, point.y); + matrix.PreRotate(rotateAngle); + } + return matrix; +} + +/* static */ +SMILValue SVGMotionSMILType::ConstructSMILValue(Path* aPath, float aDist, + RotateType aRotateType, + float aRotateAngle) { + SMILValue smilVal(&SVGMotionSMILType::sSingleton); + MotionSegmentArray& arr = ExtractMotionSegmentArray(smilVal); + + // AppendElement has guaranteed success here, since Init() allocates 1 slot. + MOZ_ALWAYS_TRUE(arr.AppendElement( + MotionSegment(aPath, aDist, aRotateType, aRotateAngle), fallible)); + return smilVal; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILType.h b/dom/svg/SVGMotionSMILType.h new file mode 100644 index 0000000000..8b62641035 --- /dev/null +++ b/dom/svg/SVGMotionSMILType.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* implementation of SMILType for use by <animateMotion> element */ + +#ifndef DOM_SVG_SVGMOTIONSMILTYPE_H_ +#define DOM_SVG_SVGMOTIONSMILTYPE_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +/** + * MotionRotateType: Enum to indicate the type of our "rotate" attribute. + */ +enum RotateType { + eRotateType_Explicit, // for e.g. rotate="45"/"45deg"/"0.785rad" + eRotateType_Auto, // for rotate="auto" + eRotateType_AutoReverse // for rotate="auto-reverse" +}; + +/** + * SVGMotionSMILType: Implements the SMILType interface for SMIL animations + * from <animateMotion>. + * + * NOTE: Even though there's technically no "motion" attribute, we behave in + * many ways as if there were, for simplicity. + */ +class SVGMotionSMILType : public SMILType { + using Path = mozilla::gfx::Path; + + public: + // Singleton for SMILValue objects to hold onto. + static SVGMotionSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult SandwichAdd(SMILValue& aDest, + const SMILValue& aValueToAdd) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + public: + // Used to generate a transform matrix from an <animateMotion> SMILValue. + static gfx::Matrix CreateMatrix(const SMILValue& aSMILVal); + + // Used to generate a SMILValue for the point at the given distance along + // the given path. + static SMILValue ConstructSMILValue(Path* aPath, float aDist, + RotateType aRotateType, + float aRotateAngle); + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGMotionSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILTYPE_H_ diff --git a/dom/svg/SVGNumberList.cpp b/dom/svg/SVGNumberList.cpp new file mode 100644 index 0000000000..d41859d947 --- /dev/null +++ b/dom/svg/SVGNumberList.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGNumberList.h" + +#include "mozilla/ArrayUtils.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsString.h" +#include "nsTextFormatter.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +nsresult SVGNumberList::CopyFrom(const SVGNumberList& rhs) { + if (!mNumbers.Assign(rhs.mNumbers, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGNumberList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + char16_t buf[24]; + uint32_t last = mNumbers.Length() - 1; + for (uint32_t i = 0; i < mNumbers.Length(); ++i) { + // Would like to use aValue.AppendPrintf("%f", mNumbers[i]), but it's not + // possible to always avoid trailing zeros. + nsTextFormatter::snprintf(buf, ArrayLength(buf), u"%g", + double(mNumbers[i])); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(buf); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGNumberList::SetValueFromString(const nsAString& aValue) { + SVGNumberList temp; + + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional> + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + float num; + if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), num)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + if (!temp.AppendItem(num)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + if (tokenizer.separatorAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + return CopyFrom(temp); +} + +} // namespace mozilla diff --git a/dom/svg/SVGNumberList.h b/dom/svg/SVGNumberList.h new file mode 100644 index 0000000000..e863a99576 --- /dev/null +++ b/dom/svg/SVGNumberList.h @@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGNUMBERLIST_H_ +#define DOM_SVG_SVGNUMBERLIST_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "SVGElement.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { +class DOMSVGNumber; +class DOMSVGNumberList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGNumberList. + */ +class SVGNumberList { + friend class dom::DOMSVGNumber; + friend class dom::DOMSVGNumberList; + friend class SVGAnimatedNumberList; + + public: + SVGNumberList() = default; + ~SVGNumberList() = default; + + SVGNumberList& operator=(const SVGNumberList& aOther) { + mNumbers.ClearAndRetainStorage(); + // Best-effort, really. + Unused << mNumbers.AppendElements(aOther.mNumbers, fallible); + return *this; + } + + SVGNumberList(const SVGNumberList& aOther) { *this = aOther; } + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mNumbers.IsEmpty(); } + + uint32_t Length() const { return mNumbers.Length(); } + + const float& operator[](uint32_t aIndex) const { return mNumbers[aIndex]; } + + bool operator==(const SVGNumberList& rhs) const { + return mNumbers == rhs.mNumbers; + } + + bool SetCapacity(uint32_t size) { + return mNumbers.SetCapacity(size, fallible); + } + + void Compact() { mNumbers.Compact(); } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedNumberList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGNumberList& rhs); + + float& operator[](uint32_t aIndex) { return mNumbers[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mNumbers.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mNumbers.Clear(); } + + bool InsertItem(uint32_t aIndex, const float& aNumber) { + if (aIndex >= mNumbers.Length()) { + aIndex = mNumbers.Length(); + } + return !!mNumbers.InsertElementAt(aIndex, aNumber, fallible); + } + + void ReplaceItem(uint32_t aIndex, const float& aNumber) { + MOZ_ASSERT(aIndex < mNumbers.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mNumbers[aIndex] = aNumber; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mNumbers.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mNumbers.RemoveElementAt(aIndex); + } + + bool AppendItem(float aNumber) { + return !!mNumbers.AppendElement(aNumber, fallible); + } + + protected: + /* See SVGLengthList for the rationale for using FallibleTArray<float> instead + * of FallibleTArray<float, 1>. + */ + FallibleTArray<float> mNumbers; +}; + +/** + * This SVGNumberList subclass is used by the SMIL code when a number list + * is to be stored in a SMILValue instance. Since SMILValue objects may + * be cached, it is necessary for us to hold a strong reference to our element + * so that it doesn't disappear out from under us if, say, the element is + * removed from the DOM tree. + */ +class SVGNumberListAndInfo : public SVGNumberList { + public: + SVGNumberListAndInfo() : mElement(nullptr) {} + + explicit SVGNumberListAndInfo(dom::SVGElement* aElement) + : mElement(do_GetWeakReference(static_cast<nsINode*>(aElement))) {} + + void SetInfo(dom::SVGElement* aElement) { + mElement = do_GetWeakReference(static_cast<nsINode*>(aElement)); + } + + dom::SVGElement* Element() const { + nsCOMPtr<nsIContent> e = do_QueryReferent(mElement); + return static_cast<dom::SVGElement*>(e.get()); + } + + nsresult CopyFrom(const SVGNumberListAndInfo& rhs) { + mElement = rhs.mElement; + return SVGNumberList::CopyFrom(rhs); + } + + // Instances of this special subclass do not have DOM wrappers that we need + // to worry about keeping in sync, so it's safe to expose any hidden base + // class methods required by the SMIL code, as we do below. + + /** + * Exposed so that SVGNumberList baseVals can be copied to + * SVGNumberListAndInfo objects. Note that callers should also call + * SetInfo() when using this method! + */ + nsresult CopyFrom(const SVGNumberList& rhs) { + return SVGNumberList::CopyFrom(rhs); + } + const float& operator[](uint32_t aIndex) const { + return SVGNumberList::operator[](aIndex); + } + float& operator[](uint32_t aIndex) { + return SVGNumberList::operator[](aIndex); + } + bool SetLength(uint32_t aNumberOfItems) { + return SVGNumberList::SetLength(aNumberOfItems); + } + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGNUMBERLIST_H_ diff --git a/dom/svg/SVGNumberListSMILType.cpp b/dom/svg/SVGNumberListSMILType.cpp new file mode 100644 index 0000000000..ccc0d023a5 --- /dev/null +++ b/dom/svg/SVGNumberListSMILType.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGNumberListSMILType.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "SVGNumberList.h" +#include <math.h> + +/* The "identity" number list for a given number list attribute (the effective + * number list that is used if an attribute value is not specified) varies + * widely for different number list attributes, and can depend on the value of + * other attributes on the same element: + * + * http://www.w3.org/TR/SVG11/filters.html#feColorMatrixValuesAttribute + * + http://www.w3.org/TR/SVG11/filters.html#feComponentTransferTableValuesAttribute + * + http://www.w3.org/TR/SVG11/filters.html#feConvolveMatrixElementKernelMatrixAttribute + * http://www.w3.org/TR/SVG11/text.html#TextElementRotateAttribute + * + * Note that we don't need to worry about that variation here, however. The way + * that the SMIL engine creates and composites sandwich layers together allows + * us to treat "identity" SMILValue objects as a number list of zeros. Such + * identity SMILValues are identified by the fact that their + # SVGNumberListAndInfo has not been given an element yet. + */ + +namespace mozilla { + +/*static*/ +SVGNumberListSMILType SVGNumberListSMILType::sSingleton; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGNumberListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + SVGNumberListAndInfo* numberList = new SVGNumberListAndInfo(); + + aValue.mU.mPtr = numberList; + aValue.mType = this; +} + +void SVGNumberListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast<SVGNumberListAndInfo*>(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGNumberListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGNumberListAndInfo* src = + static_cast<const SVGNumberListAndInfo*>(aSrc.mU.mPtr); + SVGNumberListAndInfo* dest = + static_cast<SVGNumberListAndInfo*>(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGNumberListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast<const SVGNumberListAndInfo*>(aLeft.mU.mPtr) == + *static_cast<const SVGNumberListAndInfo*>(aRight.mU.mPtr); +} + +nsresult SVGNumberListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGNumberListAndInfo& dest = + *static_cast<SVGNumberListAndInfo*>(aDest.mU.mPtr); + const SVGNumberListAndInfo& valueToAdd = + *static_cast<const SVGNumberListAndInfo*>(aValueToAdd.mU.mPtr); + + MOZ_ASSERT(dest.Element() || valueToAdd.Element(), + "Target element propagation failure"); + + if (!valueToAdd.Element()) { + MOZ_ASSERT(valueToAdd.Length() == 0, + "Not identity value - target element propagation failure"); + return NS_OK; + } + if (!dest.Element()) { + MOZ_ASSERT(dest.Length() == 0, + "Not identity value - target element propagation failure"); + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] = aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; + } + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + if (dest.Length() != valueToAdd.Length()) { + // For now we only support animation between lists with the same number of + // items. SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] += aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; +} + +nsresult SVGNumberListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + const SVGNumberListAndInfo& from = + *static_cast<const SVGNumberListAndInfo*>(aFrom.mU.mPtr); + const SVGNumberListAndInfo& to = + *static_cast<const SVGNumberListAndInfo*>(aTo.mU.mPtr); + + if (from.Length() != to.Length()) { + // Lists in the 'values' attribute must have the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + // We return the root of the sum of the squares of the delta between the + // numbers at each correspanding index. + + double total = 0.0; + + for (uint32_t i = 0; i < to.Length(); ++i) { + double delta = to[i] - from[i]; + total += delta * delta; + } + double distance = sqrt(total); + if (!IsFinite(distance)) { + return NS_ERROR_FAILURE; + } + aDistance = distance; + + return NS_OK; +} + +nsresult SVGNumberListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGNumberListAndInfo& start = + *static_cast<const SVGNumberListAndInfo*>(aStartVal.mU.mPtr); + const SVGNumberListAndInfo& end = + *static_cast<const SVGNumberListAndInfo*>(aEndVal.mU.mPtr); + SVGNumberListAndInfo& result = + *static_cast<SVGNumberListAndInfo*>(aResult.mU.mPtr); + + MOZ_ASSERT(end.Element(), "Can't propagate target element"); + MOZ_ASSERT(start.Element() == end.Element() || !start.Element(), + "Different target elements"); + + if (start.Element() && // 'start' is not an "identity" value + start.Length() != end.Length()) { + // For now we only support animation between lists of the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + if (!result.SetLength(end.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + result.SetInfo(end.Element()); // propagate target element info! + + if (start.Length() != end.Length()) { + MOZ_ASSERT(start.Length() == 0, "Not an identity value"); + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = aUnitDistance * end[i]; + } + return NS_OK; + } + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = start[i] + (end[i] - start[i]) * aUnitDistance; + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGNumberListSMILType.h b/dom/svg/SVGNumberListSMILType.h new file mode 100644 index 0000000000..1ba803fc10 --- /dev/null +++ b/dom/svg/SVGNumberListSMILType.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGNUMBERLISTSMILTYPE_H_ +#define DOM_SVG_SVGNUMBERLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGNumberListSMILType +// +// Operations for animating an SVGNumberList. +// +class SVGNumberListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGNumberListSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGNumberListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGNUMBERLISTSMILTYPE_H_ diff --git a/dom/svg/SVGNumberPairSMILType.cpp b/dom/svg/SVGNumberPairSMILType.cpp new file mode 100644 index 0000000000..f0c6a61862 --- /dev/null +++ b/dom/svg/SVGNumberPairSMILType.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGNumberPairSMILType.h" + +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "nsDebug.h" + +namespace mozilla { + +/*static*/ +SVGNumberPairSMILType SVGNumberPairSMILType::sSingleton; + +void SVGNumberPairSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + aValue.mU.mNumberPair[0] = 0; + aValue.mU.mNumberPair[1] = 0; + aValue.mType = this; +} + +void SVGNumberPairSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value"); + aValue.mU.mNumberPair[0] = 0; + aValue.mU.mNumberPair[1] = 0; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGNumberPairSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + aDest.mU.mNumberPair[0] = aSrc.mU.mNumberPair[0]; + aDest.mU.mNumberPair[1] = aSrc.mU.mNumberPair[1]; + return NS_OK; +} + +bool SVGNumberPairSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return aLeft.mU.mNumberPair[0] == aRight.mU.mNumberPair[0] && + aLeft.mU.mNumberPair[1] == aRight.mU.mNumberPair[1]; +} + +nsresult SVGNumberPairSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aValueToAdd.mType == aDest.mType, "Trying to add invalid types"); + MOZ_ASSERT(aValueToAdd.mType == this, "Unexpected source type"); + + aDest.mU.mNumberPair[0] += aValueToAdd.mU.mNumberPair[0] * aCount; + aDest.mU.mNumberPair[1] += aValueToAdd.mU.mNumberPair[1] * aCount; + + return NS_OK; +} + +nsresult SVGNumberPairSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); + + double delta[2]; + delta[0] = aTo.mU.mNumberPair[0] - aFrom.mU.mNumberPair[0]; + delta[1] = aTo.mU.mNumberPair[1] - aFrom.mU.mNumberPair[1]; + + aDistance = NS_hypot(delta[0], delta[1]); + return NS_OK; +} + +nsresult SVGNumberPairSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + aResult.mU.mNumberPair[0] = + float(aStartVal.mU.mNumberPair[0] + + (aEndVal.mU.mNumberPair[0] - aStartVal.mU.mNumberPair[0]) * + aUnitDistance); + aResult.mU.mNumberPair[1] = + float(aStartVal.mU.mNumberPair[1] + + (aEndVal.mU.mNumberPair[1] - aStartVal.mU.mNumberPair[1]) * + aUnitDistance); + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGNumberPairSMILType.h b/dom/svg/SVGNumberPairSMILType.h new file mode 100644 index 0000000000..7e9cce5f73 --- /dev/null +++ b/dom/svg/SVGNumberPairSMILType.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGNUMBERPAIRSMILTYPE_H_ +#define DOM_SVG_SVGNUMBERPAIRSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +class SVGNumberPairSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGNumberPairSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue&) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGNumberPairSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGNUMBERPAIRSMILTYPE_H_ diff --git a/dom/svg/SVGOrientSMILType.cpp b/dom/svg/SVGOrientSMILType.cpp new file mode 100644 index 0000000000..1050c7a763 --- /dev/null +++ b/dom/svg/SVGOrientSMILType.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGOrientSMILType.h" + +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGMarkerElement.h" +#include "nsDebug.h" +#include "SVGAnimatedOrient.h" +#include <math.h> + +namespace mozilla { + +using namespace dom::SVGAngle_Binding; +using namespace dom::SVGMarkerElement_Binding; + +/*static*/ +SVGOrientSMILType SVGOrientSMILType::sSingleton; + +void SVGOrientSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + aValue.mU.mOrient.mAngle = 0.0f; + aValue.mU.mOrient.mUnit = SVG_ANGLETYPE_UNSPECIFIED; + aValue.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_ANGLE; + aValue.mType = this; +} + +void SVGOrientSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value."); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGOrientSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types."); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value."); + + aDest.mU.mOrient.mAngle = aSrc.mU.mOrient.mAngle; + aDest.mU.mOrient.mUnit = aSrc.mU.mOrient.mUnit; + aDest.mU.mOrient.mOrientType = aSrc.mU.mOrient.mOrientType; + return NS_OK; +} + +bool SVGOrientSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return aLeft.mU.mOrient.mAngle == aRight.mU.mOrient.mAngle && + aLeft.mU.mOrient.mUnit == aRight.mU.mOrient.mUnit && + aLeft.mU.mOrient.mOrientType == aRight.mU.mOrient.mOrientType; +} + +nsresult SVGOrientSMILType::Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aValueToAdd.mType == aDest.mType, "Trying to add invalid types"); + MOZ_ASSERT(aValueToAdd.mType == this, "Unexpected source type"); + + if (aDest.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE || + aValueToAdd.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE) { + // TODO: it would be nice to be able to add to auto angles + return NS_ERROR_FAILURE; + } + + // We may be dealing with two different angle units, so we normalize to + // degrees for the add: + float currentAngle = + aDest.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aDest.mU.mOrient.mUnit); + float angleToAdd = + aValueToAdd.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aValueToAdd.mU.mOrient.mUnit) * + aCount; + + // And then we give the resulting animated value the same units as the value + // that we're animating to/by (i.e. the same as aValueToAdd): + aDest.mU.mOrient.mAngle = + (currentAngle + angleToAdd) / + SVGAnimatedOrient::GetDegreesPerUnit(aValueToAdd.mU.mOrient.mUnit); + aDest.mU.mOrient.mUnit = aValueToAdd.mU.mOrient.mUnit; + + return NS_OK; +} + +nsresult SVGOrientSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); + + if (aFrom.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE || + aTo.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE) { + // TODO: it would be nice to be able to compute distance with auto angles + return NS_ERROR_FAILURE; + } + + // Normalize both to degrees in case they're different angle units: + double from = aFrom.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aFrom.mU.mOrient.mUnit); + double to = aTo.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aTo.mU.mOrient.mUnit); + + aDistance = fabs(to - from); + + return NS_OK; +} + +nsresult SVGOrientSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation."); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type."); + + if (aStartVal.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE || + aEndVal.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE) { + // TODO: it would be nice to be able to handle auto angles too. + return NS_ERROR_FAILURE; + } + + float start = + aStartVal.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aStartVal.mU.mOrient.mUnit); + float end = aEndVal.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aEndVal.mU.mOrient.mUnit); + float result = (start + (end - start) * aUnitDistance); + + // Again, we use the unit of the to/by value for the result: + aResult.mU.mOrient.mAngle = + result / SVGAnimatedOrient::GetDegreesPerUnit(aEndVal.mU.mOrient.mUnit); + aResult.mU.mOrient.mUnit = aEndVal.mU.mOrient.mUnit; + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGOrientSMILType.h b/dom/svg/SVGOrientSMILType.h new file mode 100644 index 0000000000..01ca03251a --- /dev/null +++ b/dom/svg/SVGOrientSMILType.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGORIENTSMILTYPE_H_ +#define DOM_SVG_SVGORIENTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +/** + * This SMILType class is a special case for the 'orient' attribute on SVG's + * 'marker' element. + * + * orient = "auto | auto-start-reverse | <angle>" + * + * Unusually, this attribute doesn't have just a single corresponding DOM + * property, but rather is split into two properties: 'orientType' (of type + * DOMSVGAnimatedEnumeration) and 'orientAngle' (of type DOMSVGAnimatedAngle). + * If 'orientType.animVal' is SVG_MARKER_ORIENT_ANGLE, then + * 'orientAngle.animVal' contains the angle that is being used. The lacuna + * value is 0. + */ + +namespace mozilla { + +class SMILValue; + +class SVGOrientSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGOrientSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue&) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGOrientSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGORIENTSMILTYPE_H_ diff --git a/dom/svg/SVGPathData.cpp b/dom/svg/SVGPathData.cpp new file mode 100644 index 0000000000..a256323d08 --- /dev/null +++ b/dom/svg/SVGPathData.cpp @@ -0,0 +1,1359 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathData.h" + +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/RefPtr.h" +#include "nsError.h" +#include "nsString.h" +#include "SVGPathDataParser.h" +#include <stdarg.h> +#include "nsStyleConsts.h" +#include "SVGContentUtils.h" +#include "SVGGeometryElement.h" +#include "SVGPathSegUtils.h" +#include <algorithm> + +using namespace mozilla::dom::SVGPathSeg_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +static inline bool IsMoveto(uint16_t aSegType) { + return aSegType == PATHSEG_MOVETO_ABS || aSegType == PATHSEG_MOVETO_REL; +} + +static inline bool IsMoveto(StylePathCommand::Tag aSegType) { + return aSegType == StylePathCommand::Tag::MoveTo; +} + +static inline bool IsValidType(uint16_t aSegType) { + return SVGPathSegUtils::IsValidType(aSegType); +} + +static inline bool IsValidType(StylePathCommand::Tag aSegType) { + return aSegType != StylePathCommand::Tag::Unknown; +} + +static inline bool IsClosePath(uint16_t aSegType) { + return aSegType == PATHSEG_CLOSEPATH; +} + +static inline bool IsClosePath(StylePathCommand::Tag aSegType) { + return aSegType == StylePathCommand::Tag::ClosePath; +} + +static inline Point ToGfxPoint(const StyleCoordPair& aPair) { + return {aPair._0, aPair._1}; +} + +static inline bool IsCubicType(StylePathCommand::Tag aType) { + return aType == StylePathCommand::Tag::CurveTo || + aType == StylePathCommand::Tag::SmoothCurveTo; +} + +static inline bool IsQuadraticType(StylePathCommand::Tag aType) { + return aType == StylePathCommand::Tag::QuadBezierCurveTo || + aType == StylePathCommand::Tag::SmoothQuadBezierCurveTo; +} + +nsresult SVGPathData::CopyFrom(const SVGPathData& rhs) { + if (!mData.Assign(rhs.mData, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGPathData::GetValueAsString(nsAString& aValue) const { + // we need this function in DidChangePathSegList + aValue.Truncate(); + if (!Length()) { + return; + } + uint32_t i = 0; + for (;;) { + nsAutoString segAsString; + SVGPathSegUtils::GetValueAsString(&mData[i], segAsString); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(segAsString); + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + if (i >= mData.Length()) { + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + return; + } + aValue.Append(' '); + } +} + +nsresult SVGPathData::SetValueFromString(const nsAString& aValue) { + // We don't use a temp variable since the spec says to parse everything up to + // the first error. We still return any error though so that callers know if + // there's a problem. + + SVGPathDataParser pathParser(aValue, this); + return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR; +} + +nsresult SVGPathData::AppendSeg(uint32_t aType, ...) { + uint32_t oldLength = mData.Length(); + uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType); + if (!mData.SetLength(newLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mData[oldLength] = SVGPathSegUtils::EncodeType(aType); + va_list args; + va_start(args, aType); + for (uint32_t i = oldLength + 1; i < newLength; ++i) { + // NOTE! 'float' is promoted to 'double' when passed through '...'! + mData[i] = float(va_arg(args, double)); + } + va_end(args); + return NS_OK; +} + +float SVGPathData::GetPathLength() const { + SVGPathTraversalState state; + + uint32_t i = 0; + while (i < mData.Length()) { + SVGPathSegUtils::TraversePathSegment(&mData[i], state); + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + return state.length; +} + +#ifdef DEBUG +uint32_t SVGPathData::CountItems() const { + uint32_t i = 0, count = 0; + + while (i < mData.Length()) { + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + count++; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + return count; +} +#endif + +bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray<double>* aOutput) const { + SVGPathTraversalState state; + + aOutput->Clear(); + + uint32_t i = 0; + while (i < mData.Length()) { + uint32_t segType = SVGPathSegUtils::DecodeType(mData[i]); + SVGPathSegUtils::TraversePathSegment(&mData[i], state); + + // With degenerately large point coordinates, TraversePathSegment can fail + // and end up producing NaNs. + if (!std::isfinite(state.length)) { + return false; + } + + // We skip all moveto commands except an initial moveto. See the text 'A + // "move to" command does not count as an additional point when dividing up + // the duration...': + // + // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement + // + // This is important in the non-default case of calcMode="linear". In + // this case an equal amount of time is spent on each path segment, + // except on moveto segments which are jumped over immediately. + + if (i == 0 || !IsMoveto(segType)) { + if (!aOutput->AppendElement(state.length, fallible)) { + return false; + } + } + i += 1 + SVGPathSegUtils::ArgCountForType(segType); + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt?"); + + return true; +} + +/* static */ +bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( + Span<const StylePathCommand> aPath, FallibleTArray<double>* aOutput) { + SVGPathTraversalState state; + + aOutput->Clear(); + + bool firstMoveToIsChecked = false; + for (const auto& cmd : aPath) { + SVGPathSegUtils::TraversePathSegment(cmd, state); + if (!std::isfinite(state.length)) { + return false; + } + + // We skip all moveto commands except for the initial moveto. + if (!cmd.IsMoveTo() || !firstMoveToIsChecked) { + if (!aOutput->AppendElement(state.length, fallible)) { + return false; + } + } + + if (cmd.IsMoveTo() && !firstMoveToIsChecked) { + firstMoveToIsChecked = true; + } + } + + return true; +} + +uint32_t SVGPathData::GetPathSegAtLength(float aDistance) const { + // TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or + // 'aDistance' > the length of the path, or the seg list is empty. + // Return -1? Throwing would better help authors avoid tricky bugs (DOM + // could do that if we return -1). + + uint32_t i = 0, segIndex = 0; + SVGPathTraversalState state; + + while (i < mData.Length()) { + SVGPathSegUtils::TraversePathSegment(&mData[i], state); + if (state.length >= aDistance) { + return segIndex; + } + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + segIndex++; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + return std::max(1U, segIndex) - + 1; // -1 because while loop takes us 1 too far +} + +/* static */ +uint32_t SVGPathData::GetPathSegAtLength(Span<const StylePathCommand> aPath, + float aDistance) { + uint32_t segIndex = 0; + SVGPathTraversalState state; + + for (const auto& cmd : aPath) { + SVGPathSegUtils::TraversePathSegment(cmd, state); + if (state.length >= aDistance) { + return segIndex; + } + segIndex++; + } + + return std::max(1U, segIndex) - 1; +} + +/** + * The SVG spec says we have to paint stroke caps for zero length subpaths: + * + * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes + * + * Cairo only does this for |stroke-linecap: round| and not for + * |stroke-linecap: square| (since that's what Adobe Acrobat has always done). + * Most likely the other backends that DrawTarget uses have the same behavior. + * + * To help us conform to the SVG spec we have this helper function to draw an + * approximation of square caps for zero length subpaths. It does this by + * inserting a subpath containing a single user space axis aligned straight + * line that is as small as it can be while minimizing the risk of it being + * thrown away by the DrawTarget's backend for being too small to affect + * rendering. The idea is that we'll then get stroke caps drawn for this axis + * aligned line, creating an axis aligned rectangle that approximates the + * square that would ideally be drawn. + * + * Since we don't have any information about transforms from user space to + * device space, we choose the length of the small line that we insert by + * making it a small percentage of the stroke width of the path. This should + * hopefully allow us to make the line as long as possible (to avoid rounding + * issues in the backend resulting in the backend seeing it as having zero + * length) while still avoiding the small rectangle being noticeably different + * from a square. + * + * Note that this function inserts a subpath into the current gfx path that + * will be present during both fill and stroke operations. + */ +static void ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB, + const Point& aPoint, + Float aStrokeWidth) { + // Note that caps are proportional to stroke width, so if stroke width is + // zero it's actually fine for |tinyLength| below to end up being zero. + // However, it would be a waste to inserting a LineTo in that case, so better + // not to. + MOZ_ASSERT(aStrokeWidth > 0.0f, + "Make the caller check for this, or check it here"); + + // The fraction of the stroke width that we choose for the length of the + // line is rather arbitrary, other than being chosen to meet the requirements + // described in the comment above. + + Float tinyLength = aStrokeWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR; + + aPB->LineTo(aPoint + Point(tinyLength, 0)); + aPB->MoveTo(aPoint); +} + +#define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \ + do { \ + if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \ + subpathContainsNonMoveTo && IsValidType(prevSegType) && \ + (!IsMoveto(prevSegType) || IsClosePath(segType))) { \ + ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \ + aStrokeWidth); \ + } \ + } while (0) + +already_AddRefed<Path> SVGPathData::BuildPath(PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, + Float aStrokeWidth) const { + if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) { + return nullptr; // paths without an initial moveto are invalid + } + + bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt; + bool subpathHasLength = false; // visual length + bool subpathContainsNonMoveTo = false; + + uint32_t segType = PATHSEG_UNKNOWN; + uint32_t prevSegType = PATHSEG_UNKNOWN; + Point pathStart(0.0, 0.0); // start point of [sub]path + Point segStart(0.0, 0.0); + Point segEnd; + Point cp1, cp2; // previous bezier's control points + Point tcp1, tcp2; // temporaries + + // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, + // then cp2 is its second control point. If the previous segment was a + // quadratic curve, then cp1 is its (only) control point. + + uint32_t i = 0; + while (i < mData.Length()) { + segType = SVGPathSegUtils::DecodeType(mData[i++]); + uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType); + + switch (segType) { + case PATHSEG_CLOSEPATH: + // set this early to allow drawing of square caps for "M{x},{y} Z": + subpathContainsNonMoveTo = true; + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + segEnd = pathStart; + aBuilder->Close(); + break; + + case PATHSEG_MOVETO_ABS: + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + pathStart = segEnd = Point(mData[i], mData[i + 1]); + aBuilder->MoveTo(segEnd); + subpathHasLength = false; + break; + + case PATHSEG_MOVETO_REL: + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + pathStart = segEnd = segStart + Point(mData[i], mData[i + 1]); + aBuilder->MoveTo(segEnd); + subpathHasLength = false; + break; + + case PATHSEG_LINETO_ABS: + segEnd = Point(mData[i], mData[i + 1]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_REL: + segEnd = segStart + Point(mData[i], mData[i + 1]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_ABS: + cp1 = Point(mData[i], mData[i + 1]); + cp2 = Point(mData[i + 2], mData[i + 3]); + segEnd = Point(mData[i + 4], mData[i + 5]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_REL: + cp1 = segStart + Point(mData[i], mData[i + 1]); + cp2 = segStart + Point(mData[i + 2], mData[i + 3]); + segEnd = segStart + Point(mData[i + 4], mData[i + 5]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_ABS: + cp1 = Point(mData[i], mData[i + 1]); + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = Point(mData[i + 2], mData[i + 3]); // set before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_REL: + cp1 = segStart + Point(mData[i], mData[i + 1]); + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = segStart + + Point(mData[i + 2], mData[i + 3]); // set before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + case PATHSEG_ARC_ABS: + case PATHSEG_ARC_REL: { + Point radii(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 5], mData[i + 6]); + if (segType == PATHSEG_ARC_REL) { + segEnd += segStart; + } + if (segEnd != segStart) { + subpathHasLength = true; + if (radii.x == 0.0f || radii.y == 0.0f) { + aBuilder->LineTo(segEnd); + } else { + SVGArcConverter converter(segStart, segEnd, radii, mData[i + 2], + mData[i + 3] != 0, mData[i + 4] != 0); + while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { + aBuilder->BezierTo(cp1, cp2, segEnd); + } + } + } + break; + } + + case PATHSEG_LINETO_HORIZONTAL_ABS: + segEnd = Point(mData[i], segStart.y); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_HORIZONTAL_REL: + segEnd = segStart + Point(mData[i], 0.0f); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_VERTICAL_ABS: + segEnd = Point(segStart.x, mData[i]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_VERTICAL_REL: + segEnd = segStart + Point(0.0f, mData[i]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 + : segStart; + cp2 = Point(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 2], mData[i + 3]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: + cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 + : segStart; + cp2 = segStart + Point(mData[i], mData[i + 1]); + segEnd = segStart + Point(mData[i + 2], mData[i + 3]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 + : segStart; + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = Point(mData[i], mData[i + 1]); // set before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: + cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 + : segStart; + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = segStart + + Point(mData[i], mData[i + 1]); // changed before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + default: + MOZ_ASSERT_UNREACHABLE("Bad path segment type"); + return nullptr; // according to spec we'd use everything up to the bad + // seg anyway + } + + subpathContainsNonMoveTo = !IsMoveto(segType); + i += argCount; + prevSegType = segType; + segStart = segEnd; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + MOZ_ASSERT(prevSegType == segType, + "prevSegType should be left at the final segType"); + + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + + return aBuilder->Finish(); +} + +already_AddRefed<Path> SVGPathData::BuildPathForMeasuring() const { + // Since the path that we return will not be used for painting it doesn't + // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want + // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as + // aStrokeLineCap to avoid the insertion of extra little lines (by + // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we + // pass as aStrokeWidth doesn't matter (since it's only used to determine the + // length of those extra little lines). + + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<PathBuilder> builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + return BuildPath(builder, StyleStrokeLinecap::Butt, 0); +} + +/* static */ +already_AddRefed<Path> SVGPathData::BuildPathForMeasuring( + Span<const StylePathCommand> aPath) { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<PathBuilder> builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + return BuildPath(aPath, builder, StyleStrokeLinecap::Butt, 0); +} + +// We could simplify this function because this is only used by CSS motion path +// and clip-path, which don't render the SVG Path. i.e. The returned path is +// used as a reference. +/* static */ +already_AddRefed<Path> SVGPathData::BuildPath( + Span<const StylePathCommand> aPath, PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth, float aZoomFactor) { + if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) { + return nullptr; // paths without an initial moveto are invalid + } + + bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt; + bool subpathHasLength = false; // visual length + bool subpathContainsNonMoveTo = false; + + StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown; + StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown; + Point pathStart(0.0, 0.0); // start point of [sub]path + Point segStart(0.0, 0.0); + Point segEnd; + Point cp1, cp2; // previous bezier's control points + Point tcp1, tcp2; // temporaries + + auto scale = [aZoomFactor](const Point& p) { + return Point(p.x * aZoomFactor, p.y * aZoomFactor); + }; + + // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, + // then cp2 is its second control point. If the previous segment was a + // quadratic curve, then cp1 is its (only) control point. + + for (const StylePathCommand& cmd : aPath) { + segType = cmd.tag; + switch (segType) { + case StylePathCommand::Tag::ClosePath: + // set this early to allow drawing of square caps for "M{x},{y} Z": + subpathContainsNonMoveTo = true; + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + segEnd = pathStart; + aBuilder->Close(); + break; + case StylePathCommand::Tag::MoveTo: { + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + const Point& p = ToGfxPoint(cmd.move_to.point); + pathStart = segEnd = + cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + aBuilder->MoveTo(scale(segEnd)); + subpathHasLength = false; + break; + } + case StylePathCommand::Tag::LineTo: { + const Point& p = ToGfxPoint(cmd.line_to.point); + segEnd = + cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(scale(segEnd)); + } + break; + } + case StylePathCommand::Tag::CurveTo: + cp1 = ToGfxPoint(cmd.curve_to.control1); + cp2 = ToGfxPoint(cmd.curve_to.control2); + segEnd = ToGfxPoint(cmd.curve_to.point); + + if (cmd.curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + cp2 += segStart; + segEnd += segStart; + } + + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); + } + break; + + case StylePathCommand::Tag::QuadBezierCurveTo: + cp1 = ToGfxPoint(cmd.quad_bezier_curve_to.control1); + segEnd = ToGfxPoint(cmd.quad_bezier_curve_to.point); + + if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + segEnd += segStart; // set before setting tcp2! + } + + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + tcp2 = cp1 + (segEnd - cp1) / 3; + + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); + } + break; + + case StylePathCommand::Tag::EllipticalArc: { + const auto& arc = cmd.elliptical_arc; + Point radii(arc.rx, arc.ry); + segEnd = ToGfxPoint(arc.point); + if (arc.absolute == StyleIsAbsolute::No) { + segEnd += segStart; + } + if (segEnd != segStart) { + subpathHasLength = true; + if (radii.x == 0.0f || radii.y == 0.0f) { + aBuilder->LineTo(scale(segEnd)); + } else { + SVGArcConverter converter(segStart, segEnd, radii, arc.angle, + arc.large_arc_flag._0, arc.sweep_flag._0); + while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); + } + } + } + break; + } + case StylePathCommand::Tag::HorizontalLineTo: + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(cmd.horizontal_line_to.x, segStart.y); + } else { + segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f); + } + + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(scale(segEnd)); + } + break; + + case StylePathCommand::Tag::VerticalLineTo: + if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(segStart.x, cmd.vertical_line_to.y); + } else { + segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y); + } + + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(scale(segEnd)); + } + break; + + case StylePathCommand::Tag::SmoothCurveTo: + cp1 = IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart; + cp2 = ToGfxPoint(cmd.smooth_curve_to.control2); + segEnd = ToGfxPoint(cmd.smooth_curve_to.point); + + if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) { + cp2 += segStart; + segEnd += segStart; + } + + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); + } + break; + + case StylePathCommand::Tag::SmoothQuadBezierCurveTo: { + cp1 = IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart; + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + + const Point& p = ToGfxPoint(cmd.smooth_quad_bezier_curve_to.point); + // set before setting tcp2! + segEnd = + cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes + ? p + : segStart + p; + tcp2 = cp1 + (segEnd - cp1) / 3; + + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); + } + break; + } + case StylePathCommand::Tag::Unknown: + MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type"); + return nullptr; + } + + subpathContainsNonMoveTo = !IsMoveto(segType); + prevSegType = segType; + segStart = segEnd; + } + + MOZ_ASSERT(prevSegType == segType, + "prevSegType should be left at the final segType"); + + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + + return aBuilder->Finish(); +} + +static double AngleOfVector(const Point& aVector) { + // C99 says about atan2 "A domain error may occur if both arguments are + // zero" and "On a domain error, the function returns an implementation- + // defined value". In the case of atan2 the implementation-defined value + // seems to commonly be zero, but it could just as easily be a NaN value. + // We specifically want zero in this case, hence the check: + + return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0; +} + +static float AngleOfVector(const Point& cp1, const Point& cp2) { + return static_cast<float>(AngleOfVector(cp1 - cp2)); +} + +// This implements F.6.5 and F.6.6 of +// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes +static std::tuple<float, float, float, float> +/* rx, ry, segStartAngle, segEndAngle */ +ComputeSegAnglesAndCorrectRadii(const Point& aSegStart, const Point& aSegEnd, + const float aAngle, const bool aLargeArcFlag, + const bool aSweepFlag, const float aRx, + const float aRy) { + float rx = fabs(aRx); // F.6.6.1 + float ry = fabs(aRy); + + // F.6.5.1: + const float angle = static_cast<float>(aAngle * M_PI / 180.0); + double x1p = cos(angle) * (aSegStart.x - aSegEnd.x) / 2.0 + + sin(angle) * (aSegStart.y - aSegEnd.y) / 2.0; + double y1p = -sin(angle) * (aSegStart.x - aSegEnd.x) / 2.0 + + cos(angle) * (aSegStart.y - aSegEnd.y) / 2.0; + + // This is the root in F.6.5.2 and the numerator under that root: + double root; + double numerator = + rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p; + + if (numerator >= 0.0) { + root = sqrt(numerator / (rx * rx * y1p * y1p + ry * ry * x1p * x1p)); + if (aLargeArcFlag == aSweepFlag) root = -root; + } else { + // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result + // of F.6.6.2 (lamedh) being greater than one. What we have here is + // ellipse radii that are too small for the ellipse to reach between + // segStart and segEnd. We scale the radii up uniformly so that the + // ellipse is just big enough to fit (i.e. to the point where there is + // exactly one solution). + + double lamedh = + 1.0 - numerator / (rx * rx * ry * ry); // equiv to eqn F.6.6.2 + double s = sqrt(lamedh); + rx = static_cast<float>((double)rx * s); // F.6.6.3 + ry = static_cast<float>((double)ry * s); + root = 0.0; + } + + double cxp = root * rx * y1p / ry; // F.6.5.2 + double cyp = -root * ry * x1p / rx; + + double theta = + AngleOfVector(Point(static_cast<float>((x1p - cxp) / rx), + static_cast<float>((y1p - cyp) / ry))); // F.6.5.5 + double delta = + AngleOfVector(Point(static_cast<float>((-x1p - cxp) / rx), + static_cast<float>((-y1p - cyp) / ry))) - // F.6.5.6 + theta; + if (!aSweepFlag && delta > 0) { + delta -= 2.0 * M_PI; + } else if (aSweepFlag && delta < 0) { + delta += 2.0 * M_PI; + } + + double tx1, ty1, tx2, ty2; + tx1 = -cos(angle) * rx * sin(theta) - sin(angle) * ry * cos(theta); + ty1 = -sin(angle) * rx * sin(theta) + cos(angle) * ry * cos(theta); + tx2 = -cos(angle) * rx * sin(theta + delta) - + sin(angle) * ry * cos(theta + delta); + ty2 = -sin(angle) * rx * sin(theta + delta) + + cos(angle) * ry * cos(theta + delta); + + if (delta < 0.0f) { + tx1 = -tx1; + ty1 = -ty1; + tx2 = -tx2; + ty2 = -ty2; + } + + return {rx, ry, static_cast<float>(atan2(ty1, tx1)), + static_cast<float>(atan2(ty2, tx2))}; +} + +void SVGPathData::GetMarkerPositioningData(nsTArray<SVGMark>* aMarks) const { + // This code should assume that ANY type of segment can appear at ANY index. + // It should also assume that segments such as M and Z can appear in weird + // places, and repeat multiple times consecutively. + + // info on current [sub]path (reset every M command): + Point pathStart(0.0, 0.0); + float pathStartAngle = 0.0f; + uint32_t pathStartIndex = 0; + + // info on previous segment: + uint16_t prevSegType = PATHSEG_UNKNOWN; + Point prevSegEnd(0.0, 0.0); + float prevSegEndAngle = 0.0f; + Point prevCP; // if prev seg was a bezier, this was its last control point + + uint32_t i = 0; + while (i < mData.Length()) { + // info on current segment: + uint16_t segType = + SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args + Point& segStart = prevSegEnd; + Point segEnd; + float segStartAngle, segEndAngle; + + switch (segType) // to find segStartAngle, segEnd and segEndAngle + { + case PATHSEG_CLOSEPATH: + segEnd = pathStart; + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case PATHSEG_MOVETO_ABS: + case PATHSEG_MOVETO_REL: + if (segType == PATHSEG_MOVETO_ABS) { + segEnd = Point(mData[i], mData[i + 1]); + } else { + segEnd = segStart + Point(mData[i], mData[i + 1]); + } + pathStart = segEnd; + pathStartIndex = aMarks->Length(); + // If authors are going to specify multiple consecutive moveto commands + // with markers, me might as well make the angle do something useful: + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + i += 2; + break; + + case PATHSEG_LINETO_ABS: + case PATHSEG_LINETO_REL: + if (segType == PATHSEG_LINETO_ABS) { + segEnd = Point(mData[i], mData[i + 1]); + } else { + segEnd = segStart + Point(mData[i], mData[i + 1]); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + i += 2; + break; + + case PATHSEG_CURVETO_CUBIC_ABS: + case PATHSEG_CURVETO_CUBIC_REL: { + Point cp1, cp2; // control points + if (segType == PATHSEG_CURVETO_CUBIC_ABS) { + cp1 = Point(mData[i], mData[i + 1]); + cp2 = Point(mData[i + 2], mData[i + 3]); + segEnd = Point(mData[i + 4], mData[i + 5]); + } else { + cp1 = segStart + Point(mData[i], mData[i + 1]); + cp2 = segStart + Point(mData[i + 2], mData[i + 3]); + segEnd = segStart + Point(mData[i + 4], mData[i + 5]); + } + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + i += 6; + break; + } + + case PATHSEG_CURVETO_QUADRATIC_ABS: + case PATHSEG_CURVETO_QUADRATIC_REL: { + Point cp1; // control point + if (segType == PATHSEG_CURVETO_QUADRATIC_ABS) { + cp1 = Point(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 2], mData[i + 3]); + } else { + cp1 = segStart + Point(mData[i], mData[i + 1]); + segEnd = segStart + Point(mData[i + 2], mData[i + 3]); + } + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + i += 4; + break; + } + + case PATHSEG_ARC_ABS: + case PATHSEG_ARC_REL: { + float rx = mData[i]; + float ry = mData[i + 1]; + float angle = mData[i + 2]; + bool largeArcFlag = mData[i + 3] != 0.0f; + bool sweepFlag = mData[i + 4] != 0.0f; + if (segType == PATHSEG_ARC_ABS) { + segEnd = Point(mData[i + 5], mData[i + 6]); + } else { + segEnd = segStart + Point(mData[i + 5], mData[i + 6]); + } + + // See section F.6 of SVG 1.1 for details on what we're doing here: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + + if (segStart == segEnd) { + // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical, + // then this is equivalent to omitting the elliptical arc segment + // entirely." We take that very literally here, not adding a mark, and + // not even setting any of the 'prev' variables so that it's as if + // this arc had never existed; note the difference this will make e.g. + // if the arc is proceeded by a bezier curve and followed by a + // "smooth" bezier curve of the same degree! + i += 7; + continue; + } + + // Below we have funny interleaving of F.6.6 (Correction of out-of-range + // radii) and F.6.5 (Conversion from endpoint to center + // parameterization) which is designed to avoid some unnecessary + // calculations. + + if (rx == 0.0 || ry == 0.0) { + // F.6.6 step 1 - straight line or coincidental points + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + i += 7; + break; + } + + std::tie(rx, ry, segStartAngle, segEndAngle) = + ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle, + largeArcFlag, sweepFlag, rx, ry); + i += 7; + break; + } + + case PATHSEG_LINETO_HORIZONTAL_ABS: + case PATHSEG_LINETO_HORIZONTAL_REL: + if (segType == PATHSEG_LINETO_HORIZONTAL_ABS) { + segEnd = Point(mData[i++], segStart.y); + } else { + segEnd = segStart + Point(mData[i++], 0.0f); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case PATHSEG_LINETO_VERTICAL_ABS: + case PATHSEG_LINETO_VERTICAL_REL: + if (segType == PATHSEG_LINETO_VERTICAL_ABS) { + segEnd = Point(segStart.x, mData[i++]); + } else { + segEnd = segStart + Point(0.0f, mData[i++]); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: { + Point cp1 = SVGPathSegUtils::IsCubicType(prevSegType) + ? segStart * 2 - prevCP + : segStart; + Point cp2; + if (segType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) { + cp2 = Point(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 2], mData[i + 3]); + } else { + cp2 = segStart + Point(mData[i], mData[i + 1]); + segEnd = segStart + Point(mData[i + 2], mData[i + 3]); + } + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + i += 4; + break; + } + + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: { + Point cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) + ? segStart * 2 - prevCP + : segStart; + if (segType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) { + segEnd = Point(mData[i], mData[i + 1]); + } else { + segEnd = segStart + Point(mData[i], mData[i + 1]); + } + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + i += 2; + break; + } + + default: + // Leave any existing marks in aMarks so we have a visual indication of + // when things went wrong. + MOZ_ASSERT(false, "Unknown segment type - path corruption?"); + return; + } + + // Set the angle of the mark at the start of this segment: + if (aMarks->Length()) { + SVGMark& mark = aMarks->LastElement(); + if (!IsMoveto(segType) && IsMoveto(prevSegType)) { + // start of new subpath + pathStartAngle = mark.angle = segStartAngle; + } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) { + // end of a subpath + if (prevSegType != PATHSEG_CLOSEPATH) mark.angle = prevSegEndAngle; + } else { + if (!(segType == PATHSEG_CLOSEPATH && prevSegType == PATHSEG_CLOSEPATH)) + mark.angle = + SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle); + } + } + + // Add the mark at the end of this segment, and set its position: + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x), + static_cast<float>(segEnd.y), 0.0f, + SVGMark::eMid)); + + if (segType == PATHSEG_CLOSEPATH && prevSegType != PATHSEG_CLOSEPATH) { + aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle = + SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle); + } + + prevSegType = segType; + prevSegEnd = segEnd; + prevSegEndAngle = segEndAngle; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + if (aMarks->Length()) { + if (prevSegType != PATHSEG_CLOSEPATH) { + aMarks->LastElement().angle = prevSegEndAngle; + } + aMarks->LastElement().type = SVGMark::eEnd; + aMarks->ElementAt(0).type = SVGMark::eStart; + } +} + +// Basically, this is identical to the above function, but replace |mData| with +// |aPath|. We probably can factor out some identical calculation, but I believe +// the above one will be removed because we will use any kind of array of +// StylePathCommand for SVG d attribute in the future. +/* static */ +void SVGPathData::GetMarkerPositioningData(Span<const StylePathCommand> aPath, + nsTArray<SVGMark>* aMarks) { + if (aPath.IsEmpty()) { + return; + } + + // info on current [sub]path (reset every M command): + Point pathStart(0.0, 0.0); + float pathStartAngle = 0.0f; + uint32_t pathStartIndex = 0; + + // info on previous segment: + StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown; + Point prevSegEnd(0.0, 0.0); + float prevSegEndAngle = 0.0f; + Point prevCP; // if prev seg was a bezier, this was its last control point + + StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown; + for (const StylePathCommand& cmd : aPath) { + segType = cmd.tag; + Point& segStart = prevSegEnd; + Point segEnd; + float segStartAngle, segEndAngle; + + switch (segType) // to find segStartAngle, segEnd and segEndAngle + { + case StylePathCommand::Tag::ClosePath: + segEnd = pathStart; + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case StylePathCommand::Tag::MoveTo: { + const Point& p = ToGfxPoint(cmd.move_to.point); + pathStart = segEnd = + cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + pathStartIndex = aMarks->Length(); + // If authors are going to specify multiple consecutive moveto commands + // with markers, me might as well make the angle do something useful: + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::LineTo: { + const Point& p = ToGfxPoint(cmd.line_to.point); + segEnd = + cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::CurveTo: { + Point cp1 = ToGfxPoint(cmd.curve_to.control1); + Point cp2 = ToGfxPoint(cmd.curve_to.control2); + segEnd = ToGfxPoint(cmd.curve_to.point); + + if (cmd.curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + cp2 += segStart; + segEnd += segStart; + } + + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + break; + } + case StylePathCommand::Tag::QuadBezierCurveTo: { + Point cp1 = ToGfxPoint(cmd.quad_bezier_curve_to.control1); + segEnd = ToGfxPoint(cmd.quad_bezier_curve_to.point); + + if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + segEnd += segStart; // set before setting tcp2! + } + + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + break; + } + case StylePathCommand::Tag::EllipticalArc: { + const auto& arc = cmd.elliptical_arc; + float rx = arc.rx; + float ry = arc.ry; + float angle = arc.angle; + bool largeArcFlag = arc.large_arc_flag._0; + bool sweepFlag = arc.sweep_flag._0; + Point radii(arc.rx, arc.ry); + segEnd = ToGfxPoint(arc.point); + if (arc.absolute == StyleIsAbsolute::No) { + segEnd += segStart; + } + + // See section F.6 of SVG 1.1 for details on what we're doing here: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + + if (segStart == segEnd) { + // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical, + // then this is equivalent to omitting the elliptical arc segment + // entirely." We take that very literally here, not adding a mark, and + // not even setting any of the 'prev' variables so that it's as if + // this arc had never existed; note the difference this will make e.g. + // if the arc is proceeded by a bezier curve and followed by a + // "smooth" bezier curve of the same degree! + continue; + } + + // Below we have funny interleaving of F.6.6 (Correction of out-of-range + // radii) and F.6.5 (Conversion from endpoint to center + // parameterization) which is designed to avoid some unnecessary + // calculations. + + if (rx == 0.0 || ry == 0.0) { + // F.6.6 step 1 - straight line or coincidental points + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + + std::tie(rx, ry, segStartAngle, segEndAngle) = + ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle, + largeArcFlag, sweepFlag, rx, ry); + break; + } + case StylePathCommand::Tag::HorizontalLineTo: { + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(cmd.horizontal_line_to.x, segStart.y); + } else { + segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::VerticalLineTo: { + if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(segStart.x, cmd.vertical_line_to.y); + } else { + segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::SmoothCurveTo: { + Point cp1 = IsCubicType(prevSegType) ? segStart * 2 - prevCP : segStart; + Point cp2 = ToGfxPoint(cmd.smooth_curve_to.control2); + segEnd = ToGfxPoint(cmd.smooth_curve_to.point); + + if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) { + cp2 += segStart; + segEnd += segStart; + } + + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + break; + } + case StylePathCommand::Tag::SmoothQuadBezierCurveTo: { + Point cp1 = + IsQuadraticType(prevSegType) ? segStart * 2 - prevCP : segStart; + segEnd = + cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes + ? ToGfxPoint(cmd.smooth_quad_bezier_curve_to.point) + : segStart + ToGfxPoint(cmd.smooth_quad_bezier_curve_to.point); + + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + break; + } + case StylePathCommand::Tag::Unknown: + // Leave any existing marks in aMarks so we have a visual indication of + // when things went wrong. + MOZ_ASSERT_UNREACHABLE("Unknown segment type - path corruption?"); + return; + } + + // Set the angle of the mark at the start of this segment: + if (aMarks->Length()) { + SVGMark& mark = aMarks->LastElement(); + if (!IsMoveto(segType) && IsMoveto(prevSegType)) { + // start of new subpath + pathStartAngle = mark.angle = segStartAngle; + } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) { + // end of a subpath + if (prevSegType != StylePathCommand::Tag::ClosePath) { + mark.angle = prevSegEndAngle; + } + } else if (!(segType == StylePathCommand::Tag::ClosePath && + prevSegType == StylePathCommand::Tag::ClosePath)) { + mark.angle = + SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle); + } + } + + // Add the mark at the end of this segment, and set its position: + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x), + static_cast<float>(segEnd.y), 0.0f, + SVGMark::eMid)); + + if (segType == StylePathCommand::Tag::ClosePath && + prevSegType != StylePathCommand::Tag::ClosePath) { + aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle = + SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle); + } + + prevSegType = segType; + prevSegEnd = segEnd; + prevSegEndAngle = segEndAngle; + } + + if (aMarks->Length()) { + if (prevSegType != StylePathCommand::Tag::ClosePath) { + aMarks->LastElement().angle = prevSegEndAngle; + } + aMarks->LastElement().type = SVGMark::eEnd; + aMarks->ElementAt(0).type = SVGMark::eStart; + } +} + +size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mData.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathData.h b/dom/svg/SVGPathData.h new file mode 100644 index 0000000000..b3ae34a10f --- /dev/null +++ b/dom/svg/SVGPathData.h @@ -0,0 +1,311 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHDATA_H_ +#define DOM_SVG_SVGPATHDATA_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "nsTArray.h" + +#include <string.h> + +namespace mozilla { + +struct StylePathCommand; +struct SVGMark; +enum class StyleStrokeLinecap : uint8_t; + +class SVGPathDataParser; // IWYU pragma: keep + +namespace dom { +class DOMSVGPathSeg; +class DOMSVGPathSegList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGPathSegList. + * + * This class is not called |class SVGPathSegList| for one very good reason; + * this class does not provide a list of "SVGPathSeg" items, it provides an + * array of floats into which path segments are encoded. See the paragraphs + * that follow for why. Note that the Length() method returns the number of + * floats in our array, not the number of encoded segments, and the index + * operator indexes floats in the array, not segments. If this class were + * called SVGPathSegList the names of these methods would be very misleading. + * + * The reason this class is designed in this way is because there are many + * different types of path segment, each taking a different numbers of + * arguments. We want to store the segments in an nsTArray to avoid individual + * allocations for each item, but the different size of segments means we can't + * have one single segment type for the nsTArray (not without using a space + * wasteful union or something similar). Since the internal code does not need + * to index into the list (the DOM wrapper does, but it handles that itself) + * the obvious solution is to have the items in this class take up variable + * width and have the internal code iterate over these lists rather than index + * into them. + * + * Implementing indexing to segments with O(1) performance would require us to + * allocate and maintain a separate segment index table (keeping that table in + * sync when items are inserted or removed from the list). So long as the + * internal code doesn't require indexing to segments, we can avoid that + * overhead and additional complexity. + * + * Segment encoding: the first float in the encoding of a segment contains the + * segment's type. The segment's type is encoded to/decoded from this float + * using the static methods SVGPathSegUtils::EncodeType(uint32_t)/ + * SVGPathSegUtils::DecodeType(float). If the path segment type in question + * takes any arguments then these follow the first float, and are in the same + * order as they are given in a <path> element's 'd' attribute (NOT in the + * order of the createSVGPathSegXxx() methods' arguments from the SVG DOM + * interface SVGPathElement, which are different...grr). Consumers can use + * SVGPathSegUtils::ArgCountForType(type) to determine how many arguments + * there are (if any), and thus where the current encoded segment ends, and + * where the next segment (if any) begins. + */ +class SVGPathData { + friend class SVGAnimatedPathSegList; + friend class dom::DOMSVGPathSeg; + friend class dom::DOMSVGPathSegList; + friend class SVGPathDataParser; + // SVGPathDataParser will not keep wrappers in sync, so consumers + // are responsible for that! + + using DrawTarget = gfx::DrawTarget; + using Path = gfx::Path; + using PathBuilder = gfx::PathBuilder; + using FillRule = gfx::FillRule; + using Float = gfx::Float; + using CapStyle = gfx::CapStyle; + + public: + using const_iterator = const float*; + + SVGPathData() = default; + ~SVGPathData() = default; + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mData.IsEmpty(); } + +#ifdef DEBUG + /** + * This method iterates over the encoded segment data and counts the number + * of segments we currently have. + */ + uint32_t CountItems() const; +#endif + + /** + * Returns the number of *floats* in the encoding array, and NOT the number + * of segments encoded in this object. (For that, see CountItems() above.) + */ + uint32_t Length() const { return mData.Length(); } + + const nsTArray<float>& RawData() const { return mData; } + + const float& operator[](uint32_t aIndex) const { return mData[aIndex]; } + + // Used by SMILCompositor to check if the cached base val is out of date + bool operator==(const SVGPathData& rhs) const { + // We use memcmp so that we don't need to worry that the data encoded in + // the first float may have the same bit pattern as a NaN. + return mData.Length() == rhs.mData.Length() && + memcmp(mData.Elements(), rhs.mData.Elements(), + mData.Length() * sizeof(float)) == 0; + } + + bool SetCapacity(uint32_t aSize) { + return mData.SetCapacity(aSize, fallible); + } + + void Compact() { mData.Compact(); } + + float GetPathLength() const; + + uint32_t GetPathSegAtLength(float aDistance) const; + + static uint32_t GetPathSegAtLength(Span<const StylePathCommand> aPath, + float aDistance); + + void GetMarkerPositioningData(nsTArray<SVGMark>* aMarks) const; + + static void GetMarkerPositioningData(Span<const StylePathCommand> aPath, + nsTArray<SVGMark>* aMarks); + + /** + * Returns true, except on OOM, in which case returns false. + */ + bool GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray<double>* aOutput) const; + + /** + * This is identical to the above one but accepts StylePathCommand. + */ + static bool GetDistancesFromOriginToEndsOfVisibleSegments( + Span<const StylePathCommand> aPath, FallibleTArray<double>* aOutput); + + /** + * This returns a path without the extra little line segments that + * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps. + * See the comment for that function for more info on that. + */ + already_AddRefed<Path> BuildPathForMeasuring() const; + + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, + Float aStrokeWidth) const; + + static already_AddRefed<Path> BuildPathForMeasuring( + Span<const StylePathCommand> aPath); + + /** + * This function tries to build the path from an array of StylePathCommand, + * which is generated by cbindgen from Rust (see ServoStyleConsts.h). + * Basically, this is a variant of the above BuildPath() functions. + */ + static already_AddRefed<Path> BuildPath(Span<const StylePathCommand> aPath, + PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, + Float aStrokeWidth, + float aZoomFactor = 1.0); + + const_iterator begin() const { return mData.Elements(); } + const_iterator end() const { return mData.Elements() + mData.Length(); } + + // memory reporting methods + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedPathSegList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + using iterator = float*; + + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGPathData& rhs); + + float& operator[](uint32_t aIndex) { return mData[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aLength) { + return mData.SetLength(aLength, fallible); + } + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mData.Clear(); } + + // Our DOM wrappers have direct access to our mData, so they directly + // manipulate it rather than us implementing: + // + // * InsertItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs); + // * ReplaceItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs); + // * RemoveItem(uint32_t aDataIndex); + // * bool AppendItem(uint32_t aType, const float *aArgs); + + nsresult AppendSeg(uint32_t aType, ...); // variable number of float args + + iterator begin() { return mData.Elements(); } + iterator end() { return mData.Elements() + mData.Length(); } + + FallibleTArray<float> mData; +}; + +/** + * This SVGPathData subclass is for SVGPathSegListSMILType which needs to + * have write access to the lists it works with. + * + * Instances of this class do not have DOM wrappers that need to be kept in + * sync, so we can safely expose any protected base class methods required by + * the SMIL code. + */ +class SVGPathDataAndInfo final : public SVGPathData { + public: + explicit SVGPathDataAndInfo(dom::SVGElement* aElement = nullptr) + : mElement(do_GetWeakReference(static_cast<nsINode*>(aElement))) {} + + void SetElement(dom::SVGElement* aElement) { + mElement = do_GetWeakReference(static_cast<nsINode*>(aElement)); + } + + dom::SVGElement* Element() const { + nsCOMPtr<nsIContent> e = do_QueryReferent(mElement); + return static_cast<dom::SVGElement*>(e.get()); + } + + nsresult CopyFrom(const SVGPathDataAndInfo& rhs) { + mElement = rhs.mElement; + return SVGPathData::CopyFrom(rhs); + } + + /** + * Returns true if this object is an "identity" value, from the perspective + * of SMIL. In other words, returns true until the initial value set up in + * SVGPathSegListSMILType::Init() has been changed with a SetElement() call. + */ + bool IsIdentity() const { + if (!mElement) { + MOZ_ASSERT(IsEmpty(), "target element propagation failure"); + return true; + } + return false; + } + + /** + * Exposed so that SVGPathData baseVals can be copied to + * SVGPathDataAndInfo objects. Note that callers should also call + * SetElement() when using this method! + */ + using SVGPathData::CopyFrom; + + // Exposed since SVGPathData objects can be modified. + using SVGPathData::iterator; + using SVGPathData::operator[]; + using SVGPathData::begin; + using SVGPathData::end; + using SVGPathData::SetLength; + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHDATA_H_ diff --git a/dom/svg/SVGPathDataParser.cpp b/dom/svg/SVGPathDataParser.cpp new file mode 100644 index 0000000000..b08b98c98f --- /dev/null +++ b/dom/svg/SVGPathDataParser.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathDataParser.h" + +#include "mozilla/gfx/Point.h" +#include "SVGDataParser.h" +#include "SVGContentUtils.h" +#include "SVGPathData.h" +#include "SVGPathSegUtils.h" + +using namespace mozilla::dom::SVGPathSeg_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +static inline char16_t ToUpper(char16_t aCh) { + return aCh >= 'a' && aCh <= 'z' ? aCh - 'a' + 'A' : aCh; +} + +bool SVGPathDataParser::Parse() { + mPathSegList->Clear(); + return ParsePath(); +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseCoordPair(float& aX, float& aY) { + return SVGContentUtils::ParseNumber(mIter, mEnd, aX) && SkipCommaWsp() && + SVGContentUtils::ParseNumber(mIter, mEnd, aY); +} + +bool SVGPathDataParser::ParseFlag(bool& aFlag) { + if (mIter == mEnd || (*mIter != '0' && *mIter != '1')) { + return false; + } + aFlag = (*mIter == '1'); + + ++mIter; + return true; +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParsePath() { + while (SkipWsp()) { + if (!ParseSubPath()) { + return false; + } + } + + return true; +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseSubPath() { + return ParseMoveto() && ParseSubPathElements(); +} + +bool SVGPathDataParser::ParseSubPathElements() { + while (SkipWsp() && !IsStartOfSubPath()) { + char16_t commandType = ToUpper(*mIter); + + // Upper case commands have absolute co-ordinates, + // lower case commands have relative co-ordinates. + bool absCoords = commandType == *mIter; + + ++mIter; + SkipWsp(); + + if (!ParseSubPathElement(commandType, absCoords)) { + return false; + } + } + return true; +} + +bool SVGPathDataParser::ParseSubPathElement(char16_t aCommandType, + bool aAbsCoords) { + switch (aCommandType) { + case 'Z': + return ParseClosePath(); + case 'L': + return ParseLineto(aAbsCoords); + case 'H': + return ParseHorizontalLineto(aAbsCoords); + case 'V': + return ParseVerticalLineto(aAbsCoords); + case 'C': + return ParseCurveto(aAbsCoords); + case 'S': + return ParseSmoothCurveto(aAbsCoords); + case 'Q': + return ParseQuadBezierCurveto(aAbsCoords); + case 'T': + return ParseSmoothQuadBezierCurveto(aAbsCoords); + case 'A': + return ParseEllipticalArc(aAbsCoords); + } + return false; +} + +bool SVGPathDataParser::IsStartOfSubPath() const { + return *mIter == 'm' || *mIter == 'M'; +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseMoveto() { + if (!IsStartOfSubPath()) { + return false; + } + + bool absCoords = (*mIter == 'M'); + + ++mIter; + SkipWsp(); + + float x, y; + if (!ParseCoordPair(x, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + absCoords ? PATHSEG_MOVETO_ABS : PATHSEG_MOVETO_REL, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + + SkipCommaWsp(); + + // Per SVG 1.1 Section 8.3.2 + // If a moveto is followed by multiple pairs of coordinates, + // the subsequent pairs are treated as implicit lineto commands + return ParseLineto(absCoords); +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseClosePath() { + return NS_SUCCEEDED(mPathSegList->AppendSeg(PATHSEG_CLOSEPATH)); +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseLineto(bool aAbsCoords) { + while (true) { + float x, y; + if (!ParseCoordPair(x, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_LINETO_ABS : PATHSEG_LINETO_REL, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseHorizontalLineto(bool aAbsCoords) { + while (true) { + float x; + if (!SVGContentUtils::ParseNumber(mIter, mEnd, x)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords + ? PATHSEG_LINETO_HORIZONTAL_ABS + : PATHSEG_LINETO_HORIZONTAL_REL, + x))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseVerticalLineto(bool aAbsCoords) { + while (true) { + float y; + if (!SVGContentUtils::ParseNumber(mIter, mEnd, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords + ? PATHSEG_LINETO_VERTICAL_ABS + : PATHSEG_LINETO_VERTICAL_REL, + y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseCurveto(bool aAbsCoords) { + while (true) { + float x1, y1, x2, y2, x, y; + + if (!(ParseCoordPair(x1, y1) && SkipCommaWsp() && ParseCoordPair(x2, y2) && + SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_CURVETO_CUBIC_ABS : PATHSEG_CURVETO_CUBIC_REL, + x1, y1, x2, y2, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseSmoothCurveto(bool aAbsCoords) { + while (true) { + float x2, y2, x, y; + if (!(ParseCoordPair(x2, y2) && SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS + : PATHSEG_CURVETO_CUBIC_SMOOTH_REL, + x2, y2, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseQuadBezierCurveto(bool aAbsCoords) { + while (true) { + float x1, y1, x, y; + if (!(ParseCoordPair(x1, y1) && SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords + ? PATHSEG_CURVETO_QUADRATIC_ABS + : PATHSEG_CURVETO_QUADRATIC_REL, + x1, y1, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // Start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseSmoothQuadBezierCurveto(bool aAbsCoords) { + while (true) { + float x, y; + if (!ParseCoordPair(x, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS + : PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseEllipticalArc(bool aAbsCoords) { + while (true) { + float r1, r2, angle, x, y; + bool largeArcFlag, sweepFlag; + + if (!(SVGContentUtils::ParseNumber(mIter, mEnd, r1) && SkipCommaWsp() && + SVGContentUtils::ParseNumber(mIter, mEnd, r2) && SkipCommaWsp() && + SVGContentUtils::ParseNumber(mIter, mEnd, angle) && SkipCommaWsp() && + ParseFlag(largeArcFlag) && SkipCommaWsp() && ParseFlag(sweepFlag) && + SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + // We can only pass floats after 'type', and per the SVG spec for arc, + // non-zero args are treated at 'true'. + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_ARC_ABS : PATHSEG_ARC_REL, r1, r2, angle, + largeArcFlag ? 1.0f : 0.0f, sweepFlag ? 1.0f : 0.0f, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//----------------------------------------------------------------------- + +static double CalcVectorAngle(double ux, double uy, double vx, double vy) { + double ta = atan2(uy, ux); + double tb = atan2(vy, vx); + if (tb >= ta) return tb - ta; + return 2 * M_PI - (ta - tb); +} + +SVGArcConverter::SVGArcConverter(const Point& from, const Point& to, + const Point& radii, double angle, + bool largeArcFlag, bool sweepFlag) { + MOZ_ASSERT(radii.x != 0.0f && radii.y != 0.0f, "Bad radii"); + + const double radPerDeg = M_PI / 180.0; + mSegIndex = 0; + + if (from == to) { + mNumSegs = 0; + return; + } + + // Convert to center parameterization as shown in + // http://www.w3.org/TR/SVG/implnote.html + mRx = fabs(radii.x); + mRy = fabs(radii.y); + + mSinPhi = sin(angle * radPerDeg); + mCosPhi = cos(angle * radPerDeg); + + double x1dash = + mCosPhi * (from.x - to.x) / 2.0 + mSinPhi * (from.y - to.y) / 2.0; + double y1dash = + -mSinPhi * (from.x - to.x) / 2.0 + mCosPhi * (from.y - to.y) / 2.0; + + double root; + double numerator = mRx * mRx * mRy * mRy - mRx * mRx * y1dash * y1dash - + mRy * mRy * x1dash * x1dash; + + if (numerator < 0.0) { + // If mRx , mRy and are such that there is no solution (basically, + // the ellipse is not big enough to reach from 'from' to 'to' + // then the ellipse is scaled up uniformly until there is + // exactly one solution (until the ellipse is just big enough). + + // -> find factor s, such that numerator' with mRx'=s*mRx and + // mRy'=s*mRy becomes 0 : + double s = sqrt(1.0 - numerator / (mRx * mRx * mRy * mRy)); + + mRx *= s; + mRy *= s; + root = 0.0; + + } else { + root = (largeArcFlag == sweepFlag ? -1.0 : 1.0) * + sqrt(numerator / + (mRx * mRx * y1dash * y1dash + mRy * mRy * x1dash * x1dash)); + } + + double cxdash = root * mRx * y1dash / mRy; + double cydash = -root * mRy * x1dash / mRx; + + mC.x = mCosPhi * cxdash - mSinPhi * cydash + (from.x + to.x) / 2.0; + mC.y = mSinPhi * cxdash + mCosPhi * cydash + (from.y + to.y) / 2.0; + mTheta = CalcVectorAngle(1.0, 0.0, (x1dash - cxdash) / mRx, + (y1dash - cydash) / mRy); + double dtheta = + CalcVectorAngle((x1dash - cxdash) / mRx, (y1dash - cydash) / mRy, + (-x1dash - cxdash) / mRx, (-y1dash - cydash) / mRy); + if (!sweepFlag && dtheta > 0) + dtheta -= 2.0 * M_PI; + else if (sweepFlag && dtheta < 0) + dtheta += 2.0 * M_PI; + + // Convert into cubic bezier segments <= 90deg + mNumSegs = static_cast<int>(ceil(fabs(dtheta / (M_PI / 2.0)))); + mDelta = dtheta / mNumSegs; + mT = 8.0 / 3.0 * sin(mDelta / 4.0) * sin(mDelta / 4.0) / sin(mDelta / 2.0); + + mFrom = from; +} + +bool SVGArcConverter::GetNextSegment(Point* cp1, Point* cp2, Point* to) { + if (mSegIndex == mNumSegs) { + return false; + } + + double cosTheta1 = cos(mTheta); + double sinTheta1 = sin(mTheta); + double theta2 = mTheta + mDelta; + double cosTheta2 = cos(theta2); + double sinTheta2 = sin(theta2); + + // a) calculate endpoint of the segment: + to->x = mCosPhi * mRx * cosTheta2 - mSinPhi * mRy * sinTheta2 + mC.x; + to->y = mSinPhi * mRx * cosTheta2 + mCosPhi * mRy * sinTheta2 + mC.y; + + // b) calculate gradients at start/end points of segment: + cp1->x = + mFrom.x + mT * (-mCosPhi * mRx * sinTheta1 - mSinPhi * mRy * cosTheta1); + cp1->y = + mFrom.y + mT * (-mSinPhi * mRx * sinTheta1 + mCosPhi * mRy * cosTheta1); + + cp2->x = to->x + mT * (mCosPhi * mRx * sinTheta2 + mSinPhi * mRy * cosTheta2); + cp2->y = to->y + mT * (mSinPhi * mRx * sinTheta2 - mCosPhi * mRy * cosTheta2); + + // do next segment + mTheta = theta2; + mFrom = *to; + ++mSegIndex; + + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathDataParser.h b/dom/svg/SVGPathDataParser.h new file mode 100644 index 0000000000..02073f7f4e --- /dev/null +++ b/dom/svg/SVGPathDataParser.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHDATAPARSER_H_ +#define DOM_SVG_SVGPATHDATAPARSER_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/gfx/Point.h" +#include "SVGDataParser.h" + +namespace mozilla { +class SVGPathData; + +//////////////////////////////////////////////////////////////////////// +// SVGPathDataParser: a simple recursive descent parser that builds +// DOMSVGPathSegs from path data strings. The grammar for path data +// can be found in SVG CR 20001102, chapter 8. + +class SVGPathDataParser : public SVGDataParser { + public: + SVGPathDataParser(const nsAString& aValue, mozilla::SVGPathData* aList) + : SVGDataParser(aValue), mPathSegList(aList) { + MOZ_ASSERT(aList, "null path data"); + } + + bool Parse(); + + private: + bool ParseCoordPair(float& aX, float& aY); + bool ParseFlag(bool& aFlag); + + bool ParsePath(); + bool IsStartOfSubPath() const; + bool ParseSubPath(); + + bool ParseSubPathElements(); + bool ParseSubPathElement(char16_t aCommandType, bool aAbsCoords); + + bool ParseMoveto(); + bool ParseClosePath(); + bool ParseLineto(bool aAbsCoords); + bool ParseHorizontalLineto(bool aAbsCoords); + bool ParseVerticalLineto(bool aAbsCoords); + bool ParseCurveto(bool aAbsCoords); + bool ParseSmoothCurveto(bool aAbsCoords); + bool ParseQuadBezierCurveto(bool aAbsCoords); + bool ParseSmoothQuadBezierCurveto(bool aAbsCoords); + bool ParseEllipticalArc(bool aAbsCoords); + + mozilla::SVGPathData* const mPathSegList; +}; + +class SVGArcConverter { + using Point = mozilla::gfx::Point; + + public: + SVGArcConverter(const Point& from, const Point& to, const Point& radii, + double angle, bool largeArcFlag, bool sweepFlag); + bool GetNextSegment(Point* cp1, Point* cp2, Point* to); + + protected: + int32_t mNumSegs, mSegIndex; + double mTheta, mDelta, mT; + double mSinPhi, mCosPhi; + double mRx, mRy; + Point mFrom, mC; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHDATAPARSER_H_ diff --git a/dom/svg/SVGPathElement.cpp b/dom/svg/SVGPathElement.cpp new file mode 100644 index 0000000000..b3fb7af260 --- /dev/null +++ b/dom/svg/SVGPathElement.cpp @@ -0,0 +1,403 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPathElement.h" + +#include <algorithm> + +#include "DOMSVGPathSeg.h" +#include "DOMSVGPathSegList.h" +#include "SVGGeometryProperty.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include "nsStyleConsts.h" +#include "nsStyleStruct.h" +#include "nsWindowSizes.h" +#include "mozilla/dom/SVGPathElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGContentUtils.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Path) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGPathElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGPathElement::SVGPathElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGPathElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// memory reporting methods + +void SVGPathElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, + size_t* aNodeSize) const { + SVGPathElementBase::AddSizeOfExcludingThis(aSizes, aNodeSize); + *aNodeSize += mD.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement) + +uint32_t SVGPathElement::GetPathSegAtLength(float distance) { + uint32_t seg = 0; + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + if (styleSVGReset->mD.IsPath()) { + seg = SVGPathData::GetPathSegAtLength( + styleSVGReset->mD.AsPath()._0.AsSpan(), distance); + } + }; + + if (StaticPrefs::layout_css_d_property_enabled()) { + FlushStyleIfNeeded(); + if (SVGGeometryProperty::DoForComputedStyle(this, callback)) { + return seg; + } + } + return mD.GetAnimValue().GetPathSegAtLength(distance); +} + +already_AddRefed<DOMSVGPathSegClosePath> +SVGPathElement::CreateSVGPathSegClosePath() { + RefPtr<DOMSVGPathSegClosePath> pathSeg = new DOMSVGPathSegClosePath(); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegMovetoAbs> +SVGPathElement::CreateSVGPathSegMovetoAbs(float x, float y) { + RefPtr<DOMSVGPathSegMovetoAbs> pathSeg = new DOMSVGPathSegMovetoAbs(x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegMovetoRel> +SVGPathElement::CreateSVGPathSegMovetoRel(float x, float y) { + RefPtr<DOMSVGPathSegMovetoRel> pathSeg = new DOMSVGPathSegMovetoRel(x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegLinetoAbs> +SVGPathElement::CreateSVGPathSegLinetoAbs(float x, float y) { + RefPtr<DOMSVGPathSegLinetoAbs> pathSeg = new DOMSVGPathSegLinetoAbs(x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegLinetoRel> +SVGPathElement::CreateSVGPathSegLinetoRel(float x, float y) { + RefPtr<DOMSVGPathSegLinetoRel> pathSeg = new DOMSVGPathSegLinetoRel(x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoCubicAbs> +SVGPathElement::CreateSVGPathSegCurvetoCubicAbs(float x, float y, float x1, + float y1, float x2, float y2) { + // Note that we swap from DOM API argument order to the argument order used + // in the <path> element's 'd' attribute (i.e. we put the arguments for the + // end point of the segment last instead of first). + RefPtr<DOMSVGPathSegCurvetoCubicAbs> pathSeg = + new DOMSVGPathSegCurvetoCubicAbs(x1, y1, x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoCubicRel> +SVGPathElement::CreateSVGPathSegCurvetoCubicRel(float x, float y, float x1, + float y1, float x2, float y2) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr<DOMSVGPathSegCurvetoCubicRel> pathSeg = + new DOMSVGPathSegCurvetoCubicRel(x1, y1, x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoQuadraticAbs> +SVGPathElement::CreateSVGPathSegCurvetoQuadraticAbs(float x, float y, float x1, + float y1) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr<DOMSVGPathSegCurvetoQuadraticAbs> pathSeg = + new DOMSVGPathSegCurvetoQuadraticAbs(x1, y1, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoQuadraticRel> +SVGPathElement::CreateSVGPathSegCurvetoQuadraticRel(float x, float y, float x1, + float y1) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr<DOMSVGPathSegCurvetoQuadraticRel> pathSeg = + new DOMSVGPathSegCurvetoQuadraticRel(x1, y1, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegArcAbs> SVGPathElement::CreateSVGPathSegArcAbs( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr<DOMSVGPathSegArcAbs> pathSeg = + new DOMSVGPathSegArcAbs(r1, r2, angle, largeArcFlag, sweepFlag, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegArcRel> SVGPathElement::CreateSVGPathSegArcRel( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr<DOMSVGPathSegArcRel> pathSeg = + new DOMSVGPathSegArcRel(r1, r2, angle, largeArcFlag, sweepFlag, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegLinetoHorizontalAbs> +SVGPathElement::CreateSVGPathSegLinetoHorizontalAbs(float x) { + RefPtr<DOMSVGPathSegLinetoHorizontalAbs> pathSeg = + new DOMSVGPathSegLinetoHorizontalAbs(x); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegLinetoHorizontalRel> +SVGPathElement::CreateSVGPathSegLinetoHorizontalRel(float x) { + RefPtr<DOMSVGPathSegLinetoHorizontalRel> pathSeg = + new DOMSVGPathSegLinetoHorizontalRel(x); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegLinetoVerticalAbs> +SVGPathElement::CreateSVGPathSegLinetoVerticalAbs(float y) { + RefPtr<DOMSVGPathSegLinetoVerticalAbs> pathSeg = + new DOMSVGPathSegLinetoVerticalAbs(y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegLinetoVerticalRel> +SVGPathElement::CreateSVGPathSegLinetoVerticalRel(float y) { + RefPtr<DOMSVGPathSegLinetoVerticalRel> pathSeg = + new DOMSVGPathSegLinetoVerticalRel(y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothAbs> +SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothAbs(float x, float y, + float x2, float y2) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr<DOMSVGPathSegCurvetoCubicSmoothAbs> pathSeg = + new DOMSVGPathSegCurvetoCubicSmoothAbs(x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothRel> +SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothRel(float x, float y, + float x2, float y2) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr<DOMSVGPathSegCurvetoCubicSmoothRel> pathSeg = + new DOMSVGPathSegCurvetoCubicSmoothRel(x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothAbs> +SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) { + RefPtr<DOMSVGPathSegCurvetoQuadraticSmoothAbs> pathSeg = + new DOMSVGPathSegCurvetoQuadraticSmoothAbs(x, y); + return pathSeg.forget(); +} + +already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothRel> +SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) { + RefPtr<DOMSVGPathSegCurvetoQuadraticSmoothRel> pathSeg = + new DOMSVGPathSegCurvetoQuadraticSmoothRel(x, y); + return pathSeg.forget(); +} + +// FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This +// preference is off by default in Bug 1388931, and will be dropped later. +// So we are not planning to map d property for this API. +already_AddRefed<DOMSVGPathSegList> SVGPathElement::PathSegList() { + return DOMSVGPathSegList::GetDOMWrapper(mD.GetBaseValKey(), this, false); +} + +// FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This +// preference is off by default in Bug 1388931, and will be dropped later. +// So we are not planning to map d property for this API. +already_AddRefed<DOMSVGPathSegList> SVGPathElement::AnimatedPathSegList() { + return DOMSVGPathSegList::GetDOMWrapper(mD.GetAnimValKey(), this, true); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGPathElement::HasValidDimensions() const { + bool hasPath = false; + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + hasPath = + styleSVGReset->mD.IsPath() && !styleSVGReset->mD.AsPath()._0.IsEmpty(); + }; + + SVGGeometryProperty::DoForComputedStyle(this, callback); + // If hasPath is false, we may disable the pref of d property, so we fallback + // to check mD. + return hasPath || !mD.GetAnimValue().IsEmpty(); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGPathElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sMarkersMap}; + + return FindAttributeDependence(name, map) || + (StaticPrefs::layout_css_d_property_enabled() && + name == nsGkAtoms::d) || + SVGPathElementBase::IsAttributeMapped(name); +} + +already_AddRefed<Path> SVGPathElement::GetOrBuildPathForMeasuring() { + if (!StaticPrefs::layout_css_d_property_enabled()) { + return mD.GetAnimValue().BuildPathForMeasuring(); + } + + // FIXME: Bug 1715387, the IDL methods should flush style, but internal + // callers shouldn't. We have to make sure we flush the style well from the + // caller. + + RefPtr<Path> path; + bool success = SVGGeometryProperty::DoForComputedStyle( + this, [&path](const ComputedStyle* s) { + const auto& d = s->StyleSVGReset()->mD; + if (d.IsNone()) { + return; + } + path = SVGPathData::BuildPathForMeasuring(d.AsPath()._0.AsSpan()); + }); + return success ? path.forget() : mD.GetAnimValue().BuildPathForMeasuring(); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGPathElement::AttributeDefinesGeometry(const nsAtom* aName) { + return aName == nsGkAtoms::d || aName == nsGkAtoms::pathLength; +} + +bool SVGPathElement::IsMarkable() { return true; } + +void SVGPathElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) { + auto callback = [aMarks](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + if (styleSVGReset->mD.IsPath()) { + Span<const StylePathCommand> path = + styleSVGReset->mD.AsPath()._0.AsSpan(); + SVGPathData::GetMarkerPositioningData(path, aMarks); + } + }; + + if (StaticPrefs::layout_css_d_property_enabled() && + SVGGeometryProperty::DoForComputedStyle(this, callback)) { + return; + } + + mD.GetAnimValue().GetMarkerPositioningData(aMarks); +} + +void SVGPathElement::GetAsSimplePath(SimplePath* aSimplePath) { + aSimplePath->Reset(); + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + if (styleSVGReset->mD.IsPath()) { + auto pathData = styleSVGReset->mD.AsPath()._0.AsSpan(); + auto maybeRect = SVGPathToAxisAlignedRect(pathData); + if (maybeRect.isSome()) { + Rect r = maybeRect.value(); + aSimplePath->SetRect(r.x, r.y, r.width, r.height); + } + } + }; + + SVGGeometryProperty::DoForComputedStyle(this, callback); +} + +already_AddRefed<Path> SVGPathElement::BuildPath(PathBuilder* aBuilder) { + // The Moz2D PathBuilder that our SVGPathData will be using only cares about + // the fill rule. However, in order to fulfill the requirements of the SVG + // spec regarding zero length sub-paths when square line caps are in use, + // SVGPathData needs to know our stroke-linecap style and, if "square", then + // also our stroke width. See the comment for + // ApproximateZeroLengthSubpathSquareCaps for more info. + + auto strokeLineCap = StyleStrokeLinecap::Butt; + Float strokeWidth = 0; + RefPtr<Path> path; + const bool useDProperty = StaticPrefs::layout_css_d_property_enabled(); + + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVG* styleSVG = s->StyleSVG(); + // Note: the path that we return may be used for hit-testing, and SVG + // exposes hit-testing of strokes that are not actually painted. For that + // reason we do not check for eStyleSVGPaintType_None or check the stroke + // opacity here. + if (styleSVG->mStrokeLinecap != StyleStrokeLinecap::Butt) { + strokeLineCap = styleSVG->mStrokeLinecap; + strokeWidth = SVGContentUtils::GetStrokeWidth(this, s, nullptr); + } + + if (!useDProperty) { + return; + } + + const auto& d = s->StyleSVGReset()->mD; + if (d.IsPath()) { + path = SVGPathData::BuildPath(d.AsPath()._0.AsSpan(), aBuilder, + strokeLineCap, strokeWidth); + } + }; + + bool success = SVGGeometryProperty::DoForComputedStyle(this, callback); + if (success && useDProperty) { + return path.forget(); + } + + // Fallback to use the d attribute if it exists. + return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth); +} + +bool SVGPathElement::GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray<double>* aOutput) { + bool ret = false; + auto callback = [&ret, aOutput](const ComputedStyle* s) { + const auto& d = s->StyleSVGReset()->mD; + ret = d.IsNone() || + SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( + d.AsPath()._0.AsSpan(), aOutput); + }; + + if (StaticPrefs::layout_css_d_property_enabled() && + SVGGeometryProperty::DoForComputedStyle(this, callback)) { + return ret; + } + + return mD.GetAnimValue().GetDistancesFromOriginToEndsOfVisibleSegments( + aOutput); +} + +/* static */ +bool SVGPathElement::IsDPropertyChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + return aNewStyle.StyleSVGReset()->mD != aOldStyle.StyleSVGReset()->mD; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPathElement.h b/dom/svg/SVGPathElement.h new file mode 100644 index 0000000000..d377755917 --- /dev/null +++ b/dom/svg/SVGPathElement.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHELEMENT_H_ +#define DOM_SVG_SVGPATHELEMENT_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGGeometryElement.h" +#include "DOMSVGPathSeg.h" + +nsresult NS_NewSVGPathElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGPathElementBase = SVGGeometryElement; + +class SVGPathElement final : public SVGPathElementBase { + using Path = mozilla::gfx::Path; + + protected: + friend nsresult(::NS_NewSVGPathElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + explicit SVGPathElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + void GetAsSimplePath(SimplePath* aSimplePath) override; + + public: + NS_DECL_ADDSIZEOFEXCLUDINGTHIS + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool AttributeDefinesGeometry(const nsAtom* aName) override; + bool IsMarkable() override; + void GetMarkPoints(nsTArray<SVGMark>* aMarks) override; + /* + * Note: This function maps d attribute to CSS d property, and we don't flush + * style in this function because some callers don't need it, so if the caller + * needs style to be flushed (e.g. DOM APIs), the caller should flush style + * before calling this. + */ + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override; + + /** + * This returns a path without the extra little line segments that + * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps. + * See the comment for that function for more info on that. + * + * Note: This function maps d attribute to CSS d property, and we don't flush + * style in this function because some callers don't need it, so if the caller + * needs style to be flushed (e.g. DOM APIs), the caller should flush style + * before calling this. + */ + already_AddRefed<Path> GetOrBuildPathForMeasuring() override; + + bool GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray<double>* aOutput) override; + + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + SVGAnimatedPathSegList* GetAnimPathSegList() override { return &mD; } + + nsStaticAtom* GetPathDataAttrName() const override { return nsGkAtoms::d; } + + // WebIDL + MOZ_CAN_RUN_SCRIPT uint32_t GetPathSegAtLength(float distance); + already_AddRefed<DOMSVGPathSegClosePath> CreateSVGPathSegClosePath(); + already_AddRefed<DOMSVGPathSegMovetoAbs> CreateSVGPathSegMovetoAbs(float x, + float y); + already_AddRefed<DOMSVGPathSegMovetoRel> CreateSVGPathSegMovetoRel(float x, + float y); + already_AddRefed<DOMSVGPathSegLinetoAbs> CreateSVGPathSegLinetoAbs(float x, + float y); + already_AddRefed<DOMSVGPathSegLinetoRel> CreateSVGPathSegLinetoRel(float x, + float y); + already_AddRefed<DOMSVGPathSegCurvetoCubicAbs> + CreateSVGPathSegCurvetoCubicAbs(float x, float y, float x1, float y1, + float x2, float y2); + already_AddRefed<DOMSVGPathSegCurvetoCubicRel> + CreateSVGPathSegCurvetoCubicRel(float x, float y, float x1, float y1, + float x2, float y2); + already_AddRefed<DOMSVGPathSegCurvetoQuadraticAbs> + CreateSVGPathSegCurvetoQuadraticAbs(float x, float y, float x1, float y1); + already_AddRefed<DOMSVGPathSegCurvetoQuadraticRel> + CreateSVGPathSegCurvetoQuadraticRel(float x, float y, float x1, float y1); + already_AddRefed<DOMSVGPathSegArcAbs> CreateSVGPathSegArcAbs( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag); + already_AddRefed<DOMSVGPathSegArcRel> CreateSVGPathSegArcRel( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag); + already_AddRefed<DOMSVGPathSegLinetoHorizontalAbs> + CreateSVGPathSegLinetoHorizontalAbs(float x); + already_AddRefed<DOMSVGPathSegLinetoHorizontalRel> + CreateSVGPathSegLinetoHorizontalRel(float x); + already_AddRefed<DOMSVGPathSegLinetoVerticalAbs> + CreateSVGPathSegLinetoVerticalAbs(float y); + already_AddRefed<DOMSVGPathSegLinetoVerticalRel> + CreateSVGPathSegLinetoVerticalRel(float y); + already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothAbs> + CreateSVGPathSegCurvetoCubicSmoothAbs(float x, float y, float x2, float y2); + already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothRel> + CreateSVGPathSegCurvetoCubicSmoothRel(float x, float y, float x2, float y2); + already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothAbs> + CreateSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y); + already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothRel> + CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y); + already_AddRefed<DOMSVGPathSegList> PathSegList(); + already_AddRefed<DOMSVGPathSegList> AnimatedPathSegList(); + + static bool IsDPropertyChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + + protected: + SVGAnimatedPathSegList mD; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPATHELEMENT_H_ diff --git a/dom/svg/SVGPathSegListSMILType.cpp b/dom/svg/SVGPathSegListSMILType.cpp new file mode 100644 index 0000000000..613e1bc83d --- /dev/null +++ b/dom/svg/SVGPathSegListSMILType.cpp @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathSegListSMILType.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/SMILValue.h" +#include "SVGPathData.h" +#include "SVGPathSegUtils.h" + +using namespace mozilla::dom::SVGPathSeg_Binding; + +// Indices of boolean flags within 'arc' segment chunks in path-data arrays +// (where '0' would correspond to the index of the encoded segment type): +#define LARGE_ARC_FLAG_IDX 4 +#define SWEEP_FLAG_IDX 5 + +namespace mozilla { + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGPathSegListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + aValue.mU.mPtr = new SVGPathDataAndInfo(); + aValue.mType = this; +} + +void SVGPathSegListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast<SVGPathDataAndInfo*>(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGPathSegListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGPathDataAndInfo* src = + static_cast<const SVGPathDataAndInfo*>(aSrc.mU.mPtr); + SVGPathDataAndInfo* dest = static_cast<SVGPathDataAndInfo*>(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGPathSegListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast<const SVGPathDataAndInfo*>(aLeft.mU.mPtr) == + *static_cast<const SVGPathDataAndInfo*>(aRight.mU.mPtr); +} + +static bool ArcFlagsDiffer(SVGPathDataAndInfo::const_iterator aPathData1, + SVGPathDataAndInfo::const_iterator aPathData2) { + MOZ_ASSERT( + SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1[0])), + "ArcFlagsDiffer called with non-arc segment"); + MOZ_ASSERT( + SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData2[0])), + "ArcFlagsDiffer called with non-arc segment"); + + return aPathData1[LARGE_ARC_FLAG_IDX] != aPathData2[LARGE_ARC_FLAG_IDX] || + aPathData1[SWEEP_FLAG_IDX] != aPathData2[SWEEP_FLAG_IDX]; +} + +enum PathInterpolationResult { + eCannotInterpolate, + eRequiresConversion, + eCanInterpolate +}; + +static PathInterpolationResult CanInterpolate(const SVGPathDataAndInfo& aStart, + const SVGPathDataAndInfo& aEnd) { + if (aStart.IsIdentity()) { + return eCanInterpolate; + } + + if (aStart.Length() != aEnd.Length()) { + return eCannotInterpolate; + } + + PathInterpolationResult result = eCanInterpolate; + + SVGPathDataAndInfo::const_iterator pStart = aStart.begin(); + SVGPathDataAndInfo::const_iterator pEnd = aEnd.begin(); + SVGPathDataAndInfo::const_iterator pStartDataEnd = aStart.end(); + SVGPathDataAndInfo::const_iterator pEndDataEnd = aEnd.end(); + + while (pStart < pStartDataEnd && pEnd < pEndDataEnd) { + uint32_t startType = SVGPathSegUtils::DecodeType(*pStart); + uint32_t endType = SVGPathSegUtils::DecodeType(*pEnd); + + if (SVGPathSegUtils::IsArcType(startType) && + SVGPathSegUtils::IsArcType(endType) && ArcFlagsDiffer(pStart, pEnd)) { + return eCannotInterpolate; + } + + if (startType != endType) { + if (!SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType)) { + return eCannotInterpolate; + } + + result = eRequiresConversion; + } + + pStart += 1 + SVGPathSegUtils::ArgCountForType(startType); + pEnd += 1 + SVGPathSegUtils::ArgCountForType(endType); + } + + MOZ_ASSERT(pStart <= pStartDataEnd && pEnd <= pEndDataEnd, + "Iterated past end of buffer! (Corrupt path data?)"); + + if (pStart != pStartDataEnd || pEnd != pEndDataEnd) { + return eCannotInterpolate; + } + + return result; +} + +enum RelativenessAdjustmentType { eAbsoluteToRelative, eRelativeToAbsolute }; + +static inline void AdjustSegmentForRelativeness( + RelativenessAdjustmentType aAdjustmentType, + const SVGPathDataAndInfo::iterator& aSegmentToAdjust, + const SVGPathTraversalState& aState) { + if (aAdjustmentType == eAbsoluteToRelative) { + aSegmentToAdjust[0] -= aState.pos.x; + aSegmentToAdjust[1] -= aState.pos.y; + } else { + aSegmentToAdjust[0] += aState.pos.x; + aSegmentToAdjust[1] += aState.pos.y; + } +} + +/** + * Helper function for AddWeightedPathSegLists, to add multiples of two + * path-segments of the same type. + * + * NOTE: |aSeg1| is allowed to be nullptr, so we use |aSeg2| as the + * authoritative source of things like segment-type and boolean arc flags. + * + * @param aCoeff1 The coefficient to use on the first segment. + * @param aSeg1 An iterator pointing to the first segment. This can be + * null, which is treated as identity (zero). + * @param aCoeff2 The coefficient to use on the second segment. + * @param aSeg2 An iterator pointing to the second segment. + * @param [out] aResultSeg An iterator pointing to where we should write the + * result of this operation. + */ +static inline void AddWeightedPathSegs( + double aCoeff1, SVGPathDataAndInfo::const_iterator& aSeg1, double aCoeff2, + SVGPathDataAndInfo::const_iterator& aSeg2, + SVGPathDataAndInfo::iterator& aResultSeg) { + MOZ_ASSERT(aSeg2, "2nd segment must be non-null"); + MOZ_ASSERT(aResultSeg, "result segment must be non-null"); + + uint32_t segType = SVGPathSegUtils::DecodeType(aSeg2[0]); + MOZ_ASSERT(!aSeg1 || SVGPathSegUtils::DecodeType(*aSeg1) == segType, + "unexpected segment type"); + + // FIRST: Directly copy the arguments that don't make sense to add. + aResultSeg[0] = aSeg2[0]; // encoded segment type + + bool isArcType = SVGPathSegUtils::IsArcType(segType); + if (isArcType) { + // Copy boolean arc flags. + MOZ_ASSERT(!aSeg1 || !ArcFlagsDiffer(aSeg1, aSeg2), + "Expecting arc flags to match"); + aResultSeg[LARGE_ARC_FLAG_IDX] = aSeg2[LARGE_ARC_FLAG_IDX]; + aResultSeg[SWEEP_FLAG_IDX] = aSeg2[SWEEP_FLAG_IDX]; + } + + // SECOND: Add the arguments that are supposed to be added. + // (The 1's below are to account for segment type) + uint32_t numArgs = SVGPathSegUtils::ArgCountForType(segType); + for (uint32_t i = 1; i < 1 + numArgs; ++i) { + // Need to skip arc flags for arc-type segments. (already handled them) + if (!(isArcType && (i == LARGE_ARC_FLAG_IDX || i == SWEEP_FLAG_IDX))) { + aResultSeg[i] = (aSeg1 ? aCoeff1 * aSeg1[i] : 0.0) + aCoeff2 * aSeg2[i]; + } + } + + // FINALLY: Shift iterators forward. ("1+" is to include seg-type) + if (aSeg1) { + aSeg1 += 1 + numArgs; + } + aSeg2 += 1 + numArgs; + aResultSeg += 1 + numArgs; +} + +/** + * Helper function for Add & Interpolate, to add multiples of two path-segment + * lists. + * + * NOTE: aList1 and aList2 are assumed to have their segment-types and + * segment-count match exactly (unless aList1 is an identity value). + * + * NOTE: aResult, the output list, is expected to either be an identity value + * (in which case we'll grow it) *or* to already have the exactly right length + * (e.g. in cases where aList1 and aResult are actually the same list). + * + * @param aCoeff1 The coefficient to use on the first path segment list. + * @param aList1 The first path segment list. Allowed to be identity. + * @param aCoeff2 The coefficient to use on the second path segment list. + * @param aList2 The second path segment list. + * @param [out] aResultSeg The resulting path segment list. Allowed to be + * identity, in which case we'll grow it to the right + * size. Also allowed to be the same list as aList1. + */ +static nsresult AddWeightedPathSegLists(double aCoeff1, + const SVGPathDataAndInfo& aList1, + double aCoeff2, + const SVGPathDataAndInfo& aList2, + SVGPathDataAndInfo& aResult) { + MOZ_ASSERT(aCoeff1 >= 0.0 && aCoeff2 >= 0.0, + "expecting non-negative coefficients"); + MOZ_ASSERT(!aList2.IsIdentity(), "expecting 2nd list to be non-identity"); + MOZ_ASSERT(aList1.IsIdentity() || aList1.Length() == aList2.Length(), + "expecting 1st list to be identity or to have same " + "length as 2nd list"); + MOZ_ASSERT(aResult.IsIdentity() || aResult.Length() == aList2.Length(), + "expecting result list to be identity or to have same " + "length as 2nd list"); + + SVGPathDataAndInfo::const_iterator iter1, end1; + if (aList1.IsIdentity()) { + iter1 = end1 = nullptr; // indicate that this is an identity list + } else { + iter1 = aList1.begin(); + end1 = aList1.end(); + } + SVGPathDataAndInfo::const_iterator iter2 = aList2.begin(); + SVGPathDataAndInfo::const_iterator end2 = aList2.end(); + + // Grow |aResult| if necessary. (NOTE: It's possible that aResult and aList1 + // are the same list, so this may implicitly resize aList1. That's fine, + // because in that case, we will have already set iter1 to nullptr above, to + // record that our first operand is an identity value.) + if (aResult.IsIdentity()) { + if (!aResult.SetLength(aList2.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult.SetElement(aList2.Element()); // propagate target element info! + } + + SVGPathDataAndInfo::iterator resultIter = aResult.begin(); + + while ((!iter1 || iter1 != end1) && iter2 != end2) { + AddWeightedPathSegs(aCoeff1, iter1, aCoeff2, iter2, resultIter); + } + MOZ_ASSERT( + (!iter1 || iter1 == end1) && iter2 == end2 && resultIter == aResult.end(), + "Very, very bad - path data corrupt"); + return NS_OK; +} + +static void ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart, + SVGPathDataAndInfo::const_iterator& aEnd, + SVGPathDataAndInfo::iterator& aResult, + SVGPathTraversalState& aState) { + uint32_t startType = SVGPathSegUtils::DecodeType(*aStart); + uint32_t endType = SVGPathSegUtils::DecodeType(*aEnd); + + uint32_t segmentLengthIncludingType = + 1 + SVGPathSegUtils::ArgCountForType(startType); + + SVGPathDataAndInfo::const_iterator pResultSegmentBegin = aResult; + + if (startType == endType) { + // No conversion need, just directly copy aStart. + aEnd += segmentLengthIncludingType; + while (segmentLengthIncludingType) { + *aResult++ = *aStart++; + --segmentLengthIncludingType; + } + SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState); + return; + } + + MOZ_ASSERT( + SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType), + "Incompatible path segment types passed to ConvertPathSegmentData!"); + + RelativenessAdjustmentType adjustmentType = + SVGPathSegUtils::IsRelativeType(startType) ? eRelativeToAbsolute + : eAbsoluteToRelative; + + MOZ_ASSERT( + segmentLengthIncludingType == + 1 + SVGPathSegUtils::ArgCountForType(endType), + "Compatible path segment types for interpolation had different lengths!"); + + aResult[0] = aEnd[0]; + + switch (endType) { + case PATHSEG_LINETO_HORIZONTAL_ABS: + case PATHSEG_LINETO_HORIZONTAL_REL: + aResult[1] = + aStart[1] + + (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.x; + break; + case PATHSEG_LINETO_VERTICAL_ABS: + case PATHSEG_LINETO_VERTICAL_REL: + aResult[1] = + aStart[1] + + (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.y; + break; + case PATHSEG_ARC_ABS: + case PATHSEG_ARC_REL: + aResult[1] = aStart[1]; + aResult[2] = aStart[2]; + aResult[3] = aStart[3]; + aResult[4] = aStart[4]; + aResult[5] = aStart[5]; + aResult[6] = aStart[6]; + aResult[7] = aStart[7]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 6, aState); + break; + case PATHSEG_CURVETO_CUBIC_ABS: + case PATHSEG_CURVETO_CUBIC_REL: + aResult[5] = aStart[5]; + aResult[6] = aStart[6]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState); + [[fallthrough]]; + case PATHSEG_CURVETO_QUADRATIC_ABS: + case PATHSEG_CURVETO_QUADRATIC_REL: + case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: + aResult[3] = aStart[3]; + aResult[4] = aStart[4]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState); + [[fallthrough]]; + case PATHSEG_MOVETO_ABS: + case PATHSEG_MOVETO_REL: + case PATHSEG_LINETO_ABS: + case PATHSEG_LINETO_REL: + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: + aResult[1] = aStart[1]; + aResult[2] = aStart[2]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 1, aState); + break; + } + + SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState); + aStart += segmentLengthIncludingType; + aEnd += segmentLengthIncludingType; + aResult += segmentLengthIncludingType; +} + +static void ConvertAllPathSegmentData( + SVGPathDataAndInfo::const_iterator aStart, + SVGPathDataAndInfo::const_iterator aStartDataEnd, + SVGPathDataAndInfo::const_iterator aEnd, + SVGPathDataAndInfo::const_iterator aEndDataEnd, + SVGPathDataAndInfo::iterator aResult) { + SVGPathTraversalState state; + state.mode = SVGPathTraversalState::eUpdateOnlyStartAndCurrentPos; + while (aStart < aStartDataEnd && aEnd < aEndDataEnd) { + ConvertPathSegmentData(aStart, aEnd, aResult, state); + } + MOZ_ASSERT(aStart == aStartDataEnd && aEnd == aEndDataEnd, + "Failed to convert all path segment data! (Corrupt?)"); +} + +nsresult SVGPathSegListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGPathDataAndInfo& dest = *static_cast<SVGPathDataAndInfo*>(aDest.mU.mPtr); + const SVGPathDataAndInfo& valueToAdd = + *static_cast<const SVGPathDataAndInfo*>(aValueToAdd.mU.mPtr); + + if (valueToAdd.IsIdentity()) { // Adding identity value - no-op + return NS_OK; + } + + if (!dest.IsIdentity()) { + // Neither value is identity; make sure they're compatible. + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + + PathInterpolationResult check = CanInterpolate(dest, valueToAdd); + if (check == eCannotInterpolate) { + // SVGContentUtils::ReportToConsole - can't add path segment lists with + // different numbers of segments, with arcs that have different flag + // values, or with incompatible segment types. + return NS_ERROR_FAILURE; + } + if (check == eRequiresConversion) { + // Convert dest, in-place, to match the types in valueToAdd: + ConvertAllPathSegmentData(dest.begin(), dest.end(), valueToAdd.begin(), + valueToAdd.end(), dest.begin()); + } + } + + return AddWeightedPathSegLists(1.0, dest, aCount, valueToAdd, dest); +} + +nsresult SVGPathSegListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=522306#c18 + + // SVGContentUtils::ReportToConsole + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult SVGPathSegListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGPathDataAndInfo& start = + *static_cast<const SVGPathDataAndInfo*>(aStartVal.mU.mPtr); + const SVGPathDataAndInfo& end = + *static_cast<const SVGPathDataAndInfo*>(aEndVal.mU.mPtr); + SVGPathDataAndInfo& result = + *static_cast<SVGPathDataAndInfo*>(aResult.mU.mPtr); + MOZ_ASSERT(result.IsIdentity(), + "expecting outparam to start out as identity"); + + PathInterpolationResult check = CanInterpolate(start, end); + + if (check == eCannotInterpolate) { + // SVGContentUtils::ReportToConsole - can't interpolate path segment lists + // with different numbers of segments, with arcs with different flag values, + // or with incompatible segment types. + return NS_ERROR_FAILURE; + } + + const SVGPathDataAndInfo* startListToUse = &start; + if (check == eRequiresConversion) { + // Can't convert |start| in-place, since it's const. Instead, we copy it + // into |result|, converting the types as we go, and use that as our start. + if (!result.SetLength(end.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + result.SetElement(end.Element()); // propagate target element info! + + ConvertAllPathSegmentData(start.begin(), start.end(), end.begin(), + end.end(), result.begin()); + startListToUse = &result; + } + + return AddWeightedPathSegLists(1.0 - aUnitDistance, *startListToUse, + aUnitDistance, end, result); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathSegListSMILType.h b/dom/svg/SVGPathSegListSMILType.h new file mode 100644 index 0000000000..172701caca --- /dev/null +++ b/dom/svg/SVGPathSegListSMILType.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHSEGLISTSMILTYPE_H_ +#define DOM_SVG_SVGPATHSEGLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGPathSegListSMILType +// +// Operations for animating an SVGPathData. +// +class SVGPathSegListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGPathSegListSMILType* Singleton() { + static SVGPathSegListSMILType sSingleton; + return &sSingleton; + } + + protected: + // SMILType Methods + // ------------------- + + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGPathSegListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHSEGLISTSMILTYPE_H_ diff --git a/dom/svg/SVGPathSegUtils.cpp b/dom/svg/SVGPathSegUtils.cpp new file mode 100644 index 0000000000..a506b9cc14 --- /dev/null +++ b/dom/svg/SVGPathSegUtils.cpp @@ -0,0 +1,811 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathSegUtils.h" + +#include "mozilla/ArrayUtils.h" // MOZ_ARRAY_LENGTH +#include "mozilla/ServoStyleConsts.h" // StylePathCommand +#include "gfx2DGlue.h" +#include "SVGPathDataParser.h" +#include "nsMathUtils.h" +#include "nsTextFormatter.h" + +using namespace mozilla::dom::SVGPathSeg_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +static const float PATH_SEG_LENGTH_TOLERANCE = 0.0000001f; +static const uint32_t MAX_RECURSION = 10; + +/* static */ +void SVGPathSegUtils::GetValueAsString(const float* aSeg, nsAString& aValue) { + // Adding new seg type? Is the formatting below acceptable for the new types? + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Update GetValueAsString for the new value."); + static_assert(NS_SVG_PATH_SEG_MAX_ARGS == 7, + "Add another case to the switch below."); + + uint32_t type = DecodeType(aSeg[0]); + char16_t typeAsChar = GetPathSegTypeAsLetter(type); + + // Special case arcs: + if (IsArcType(type)) { + bool largeArcFlag = aSeg[4] != 0.0f; + bool sweepFlag = aSeg[5] != 0.0f; + nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g %d,%d %g,%g", typeAsChar, + aSeg[1], aSeg[2], aSeg[3], largeArcFlag, + sweepFlag, aSeg[6], aSeg[7]); + } else { + switch (ArgCountForType(type)) { + case 0: + aValue = typeAsChar; + break; + + case 1: + nsTextFormatter::ssprintf(aValue, u"%c%g", typeAsChar, aSeg[1]); + break; + + case 2: + nsTextFormatter::ssprintf(aValue, u"%c%g,%g", typeAsChar, aSeg[1], + aSeg[2]); + break; + + case 4: + nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g", typeAsChar, aSeg[1], + aSeg[2], aSeg[3], aSeg[4]); + break; + + case 6: + nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g %g,%g", typeAsChar, + aSeg[1], aSeg[2], aSeg[3], aSeg[4], aSeg[5], + aSeg[6]); + break; + + default: + MOZ_ASSERT(false, "Unknown segment type"); + aValue = u"<unknown-segment-type>"; + return; + } + } +} + +static float CalcDistanceBetweenPoints(const Point& aP1, const Point& aP2) { + return NS_hypot(aP2.x - aP1.x, aP2.y - aP1.y); +} + +static void SplitQuadraticBezier(const Point* aCurve, Point* aLeft, + Point* aRight) { + aLeft[0].x = aCurve[0].x; + aLeft[0].y = aCurve[0].y; + aRight[2].x = aCurve[2].x; + aRight[2].y = aCurve[2].y; + aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2; + aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2; + aRight[1].x = (aCurve[1].x + aCurve[2].x) / 2; + aRight[1].y = (aCurve[1].y + aCurve[2].y) / 2; + aLeft[2].x = aRight[0].x = (aLeft[1].x + aRight[1].x) / 2; + aLeft[2].y = aRight[0].y = (aLeft[1].y + aRight[1].y) / 2; +} + +static void SplitCubicBezier(const Point* aCurve, Point* aLeft, Point* aRight) { + Point tmp; + tmp.x = (aCurve[1].x + aCurve[2].x) / 4; + tmp.y = (aCurve[1].y + aCurve[2].y) / 4; + aLeft[0].x = aCurve[0].x; + aLeft[0].y = aCurve[0].y; + aRight[3].x = aCurve[3].x; + aRight[3].y = aCurve[3].y; + aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2; + aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2; + aRight[2].x = (aCurve[2].x + aCurve[3].x) / 2; + aRight[2].y = (aCurve[2].y + aCurve[3].y) / 2; + aLeft[2].x = aLeft[1].x / 2 + tmp.x; + aLeft[2].y = aLeft[1].y / 2 + tmp.y; + aRight[1].x = aRight[2].x / 2 + tmp.x; + aRight[1].y = aRight[2].y / 2 + tmp.y; + aLeft[3].x = aRight[0].x = (aLeft[2].x + aRight[1].x) / 2; + aLeft[3].y = aRight[0].y = (aLeft[2].y + aRight[1].y) / 2; +} + +static float CalcBezLengthHelper(const Point* aCurve, uint32_t aNumPts, + uint32_t aRecursionCount, + void (*aSplit)(const Point*, Point*, Point*)) { + Point left[4]; + Point right[4]; + float length = 0, dist; + for (uint32_t i = 0; i < aNumPts - 1; i++) { + length += CalcDistanceBetweenPoints(aCurve[i], aCurve[i + 1]); + } + dist = CalcDistanceBetweenPoints(aCurve[0], aCurve[aNumPts - 1]); + if (length - dist > PATH_SEG_LENGTH_TOLERANCE && + aRecursionCount < MAX_RECURSION) { + aSplit(aCurve, left, right); + ++aRecursionCount; + return CalcBezLengthHelper(left, aNumPts, aRecursionCount, aSplit) + + CalcBezLengthHelper(right, aNumPts, aRecursionCount, aSplit); + } + return length; +} + +static inline float CalcLengthOfCubicBezier(const Point& aPos, + const Point& aCP1, + const Point& aCP2, + const Point& aTo) { + Point curve[4] = {aPos, aCP1, aCP2, aTo}; + return CalcBezLengthHelper(curve, 4, 0, SplitCubicBezier); +} + +static inline float CalcLengthOfQuadraticBezier(const Point& aPos, + const Point& aCP, + const Point& aTo) { + Point curve[3] = {aPos, aCP, aTo}; + return CalcBezLengthHelper(curve, 3, 0, SplitQuadraticBezier); +} + +static void TraverseClosePath(const float* aArgs, + SVGPathTraversalState& aState) { + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start); + aState.cp1 = aState.cp2 = aState.start; + } + aState.pos = aState.start; +} + +static void TraverseMovetoAbs(const float* aArgs, + SVGPathTraversalState& aState) { + aState.start = aState.pos = Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + // aState.length is unchanged, since move commands don't affect path length. + aState.cp1 = aState.cp2 = aState.start; + } +} + +static void TraverseMovetoRel(const float* aArgs, + SVGPathTraversalState& aState) { + aState.start = aState.pos += Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + // aState.length is unchanged, since move commands don't affect path length. + aState.cp1 = aState.cp2 = aState.start; + } +} + +static void TraverseLinetoAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, to); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, to); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoHorizontalAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[0], aState.pos.y); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.x - aState.pos.x); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoHorizontalRel(const float* aArgs, + SVGPathTraversalState& aState) { + aState.pos.x += aArgs[0]; + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(aArgs[0]); + aState.cp1 = aState.cp2 = aState.pos; + } +} + +static void TraverseLinetoVerticalAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aState.pos.x, aArgs[0]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.y - aState.pos.y); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoVerticalRel(const float* aArgs, + SVGPathTraversalState& aState) { + aState.pos.y += aArgs[0]; + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(aArgs[0]); + aState.cp1 = aState.cp2 = aState.pos; + } +} + +static void TraverseCurvetoCubicAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[4], aArgs[5]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1(aArgs[0], aArgs[1]); + Point cp2(aArgs[2], aArgs[3]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoCubicSmoothAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos - (aState.cp2 - aState.pos); + Point cp2(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoCubicRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[4], aArgs[5]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos + Point(aArgs[0], aArgs[1]); + Point cp2 = aState.pos + Point(aArgs[2], aArgs[3]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoCubicSmoothRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos - (aState.cp2 - aState.pos); + Point cp2 = aState.pos + Point(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticSmoothAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos - (aState.cp1 - aState.pos); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos + Point(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticSmoothRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos - (aState.cp1 - aState.pos); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseArcAbs(const float* aArgs, SVGPathTraversalState& aState) { + Point to(aArgs[5], aArgs[6]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + float dist = 0; + Point radii(aArgs[0], aArgs[1]); + if (radii.x == 0.0f || radii.y == 0.0f) { + dist = CalcDistanceBetweenPoints(aState.pos, to); + } else { + Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)}; + SVGArcConverter converter(aState.pos, to, radii, aArgs[2], aArgs[3] != 0, + aArgs[4] != 0); + while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) { + dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier); + bez[0] = bez[3]; + } + } + aState.length += dist; + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseArcRel(const float* aArgs, SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[5], aArgs[6]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + float dist = 0; + Point radii(aArgs[0], aArgs[1]); + if (radii.x == 0.0f || radii.y == 0.0f) { + dist = CalcDistanceBetweenPoints(aState.pos, to); + } else { + Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)}; + SVGArcConverter converter(aState.pos, to, radii, aArgs[2], aArgs[3] != 0, + aArgs[4] != 0); + while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) { + dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier); + bez[0] = bez[3]; + } + } + aState.length += dist; + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +using TraverseFunc = void (*)(const float*, SVGPathTraversalState&); + +static TraverseFunc gTraverseFuncTable[NS_SVG_PATH_SEG_TYPE_COUNT] = { + nullptr, // 0 == PATHSEG_UNKNOWN + TraverseClosePath, + TraverseMovetoAbs, + TraverseMovetoRel, + TraverseLinetoAbs, + TraverseLinetoRel, + TraverseCurvetoCubicAbs, + TraverseCurvetoCubicRel, + TraverseCurvetoQuadraticAbs, + TraverseCurvetoQuadraticRel, + TraverseArcAbs, + TraverseArcRel, + TraverseLinetoHorizontalAbs, + TraverseLinetoHorizontalRel, + TraverseLinetoVerticalAbs, + TraverseLinetoVerticalRel, + TraverseCurvetoCubicSmoothAbs, + TraverseCurvetoCubicSmoothRel, + TraverseCurvetoQuadraticSmoothAbs, + TraverseCurvetoQuadraticSmoothRel}; + +/* static */ +void SVGPathSegUtils::TraversePathSegment(const float* aData, + SVGPathTraversalState& aState) { + static_assert( + MOZ_ARRAY_LENGTH(gTraverseFuncTable) == NS_SVG_PATH_SEG_TYPE_COUNT, + "gTraverseFuncTable is out of date"); + uint32_t type = DecodeType(aData[0]); + gTraverseFuncTable[type](aData + 1, aState); +} + +// Basically, this is just a variant version of the above TraverseXXX functions. +// We just put those function inside this and use StylePathCommand instead. +// This function and the above ones should be dropped by Bug 1388931. +/* static */ +void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand, + SVGPathTraversalState& aState) { + auto toGfxPoint = [](const StyleCoordPair& aPair) { + return Point(aPair._0, aPair._1); + }; + + switch (aCommand.tag) { + case StylePathCommand::Tag::ClosePath: + TraverseClosePath(nullptr, aState); + break; + case StylePathCommand::Tag::MoveTo: { + const Point& p = toGfxPoint(aCommand.move_to.point); + aState.start = aState.pos = + aCommand.move_to.absolute == StyleIsAbsolute::Yes ? p + : aState.pos + p; + if (aState.ShouldUpdateLengthAndControlPoints()) { + // aState.length is unchanged, since move commands don't affect path= + // length. + aState.cp1 = aState.cp2 = aState.start; + } + break; + } + case StylePathCommand::Tag::LineTo: { + Point to = aCommand.line_to.absolute == StyleIsAbsolute::Yes + ? toGfxPoint(aCommand.line_to.point) + : aState.pos + toGfxPoint(aCommand.line_to.point); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, to); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::CurveTo: { + const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No; + Point to = isRelative ? aState.pos + toGfxPoint(aCommand.curve_to.point) + : toGfxPoint(aCommand.curve_to.point); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = toGfxPoint(aCommand.curve_to.control1); + Point cp2 = toGfxPoint(aCommand.curve_to.control2); + if (isRelative) { + cp1 += aState.pos; + cp2 += aState.pos; + } + aState.length += + (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::QuadBezierCurveTo: { + const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No; + Point to = + isRelative + ? aState.pos + toGfxPoint(aCommand.quad_bezier_curve_to.point) + : toGfxPoint(aCommand.quad_bezier_curve_to.point); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = isRelative + ? aState.pos + + toGfxPoint(aCommand.quad_bezier_curve_to.control1) + : toGfxPoint(aCommand.quad_bezier_curve_to.control1); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::EllipticalArc: { + Point to = aCommand.elliptical_arc.absolute == StyleIsAbsolute::Yes + ? toGfxPoint(aCommand.elliptical_arc.point) + : aState.pos + toGfxPoint(aCommand.elliptical_arc.point); + if (aState.ShouldUpdateLengthAndControlPoints()) { + const auto& arc = aCommand.elliptical_arc; + float dist = 0; + Point radii(arc.rx, arc.ry); + if (radii.x == 0.0f || radii.y == 0.0f) { + dist = CalcDistanceBetweenPoints(aState.pos, to); + } else { + Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)}; + SVGArcConverter converter(aState.pos, to, radii, arc.angle, + arc.large_arc_flag._0, arc.sweep_flag._0); + while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) { + dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier); + bez[0] = bez[3]; + } + } + aState.length += dist; + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::HorizontalLineTo: { + Point to(aCommand.horizontal_line_to.absolute == StyleIsAbsolute::Yes + ? aCommand.horizontal_line_to.x + : aState.pos.x + aCommand.horizontal_line_to.x, + aState.pos.y); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.x - aState.pos.x); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::VerticalLineTo: { + Point to(aState.pos.x, + aCommand.vertical_line_to.absolute == StyleIsAbsolute::Yes + ? aCommand.vertical_line_to.y + : aState.pos.y + aCommand.vertical_line_to.y); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.y - aState.pos.y); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::SmoothCurveTo: { + const bool isRelative = + aCommand.smooth_curve_to.absolute == StyleIsAbsolute::No; + Point to = isRelative + ? aState.pos + toGfxPoint(aCommand.smooth_curve_to.point) + : toGfxPoint(aCommand.smooth_curve_to.point); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos - (aState.cp2 - aState.pos); + Point cp2 = + isRelative + ? aState.pos + toGfxPoint(aCommand.smooth_curve_to.control2) + : toGfxPoint(aCommand.smooth_curve_to.control2); + aState.length += + (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::SmoothQuadBezierCurveTo: { + Point to = aCommand.smooth_curve_to.absolute == StyleIsAbsolute::Yes + ? toGfxPoint(aCommand.smooth_curve_to.point) + : aState.pos + toGfxPoint(aCommand.smooth_curve_to.point); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos - (aState.cp1 - aState.pos); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::Unknown: + MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type"); + } +} + +// Possible directions of an edge that doesn't immediately disqualify the path +// as a rectangle. +enum class EdgeDir { + LEFT, + RIGHT, + UP, + DOWN, + // NONE represents (almost) zero-length edges, they should be ignored. + NONE, +}; + +Maybe<EdgeDir> GetDirection(Point v) { + if (!std::isfinite(v.x.value) || !std::isfinite(v.y.value)) { + return Nothing(); + } + + bool x = fabs(v.x) > 0.001; + bool y = fabs(v.y) > 0.001; + if (x && y) { + return Nothing(); + } + + if (!x && !y) { + return Some(EdgeDir::NONE); + } + + if (x) { + return Some(v.x > 0.0 ? EdgeDir::RIGHT : EdgeDir::LEFT); + } + + return Some(v.y > 0.0 ? EdgeDir::DOWN : EdgeDir::UP); +} + +EdgeDir OppositeDirection(EdgeDir dir) { + switch (dir) { + case EdgeDir::LEFT: + return EdgeDir::RIGHT; + case EdgeDir::RIGHT: + return EdgeDir::LEFT; + case EdgeDir::UP: + return EdgeDir::DOWN; + case EdgeDir::DOWN: + return EdgeDir::UP; + default: + return EdgeDir::NONE; + } +} + +struct IsRectHelper { + Point min; + Point max; + EdgeDir currentDir; + // Index of the next corner. + uint32_t idx; + EdgeDir dirs[4]; + + bool Edge(Point from, Point to) { + auto edge = to - from; + + auto maybeDir = GetDirection(edge); + if (maybeDir.isNothing()) { + return false; + } + + EdgeDir dir = maybeDir.value(); + + if (dir == EdgeDir::NONE) { + // zero-length edges aren't an issue. + return true; + } + + if (dir != currentDir) { + // The edge forms a corner with the previous edge. + if (idx >= 4) { + // We are at the 5th corner, can't be a rectangle. + return false; + } + + if (dir == OppositeDirection(currentDir)) { + // Can turn left or right but not a full 180 degrees. + return false; + } + + dirs[idx] = dir; + idx += 1; + currentDir = dir; + } + + min.x = fmin(min.x, to.x); + min.y = fmin(min.y, to.y); + max.x = fmax(max.x, to.x); + max.y = fmax(max.y, to.y); + + return true; + } + + bool EndSubpath() { + if (idx != 4) { + return false; + } + + if (dirs[0] != OppositeDirection(dirs[2]) || + dirs[1] != OppositeDirection(dirs[3])) { + return false; + } + + return true; + } +}; + +bool ApproxEqual(gfx::Point a, gfx::Point b) { + auto v = b - a; + return fabs(v.x) < 0.001 && fabs(v.y) < 0.001; +} + +Maybe<gfx::Rect> SVGPathToAxisAlignedRect(Span<const StylePathCommand> aPath) { + Point pathStart(0.0, 0.0); + Point segStart(0.0, 0.0); + IsRectHelper helper = { + Point(0.0, 0.0), + Point(0.0, 0.0), + EdgeDir::NONE, + 0, + {EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE}, + }; + + auto ToGfxPoint = [](const StyleCoordPair& aPair) { + return Point(aPair._0, aPair._1); + }; + + for (const StylePathCommand& cmd : aPath) { + switch (cmd.tag) { + case StylePathCommand::Tag::MoveTo: { + Point to = ToGfxPoint(cmd.move_to.point); + if (helper.idx != 0) { + // This is overly strict since empty moveto sequences such as "M 10 12 + // M 3 2 M 0 0" render nothing, but I expect it won't make us miss a + // lot of rect-shaped paths in practice and lets us avoidhandling + // special caps for empty sub-paths like "M 0 0 L 0 0" and "M 1 2 Z". + return Nothing(); + } + + if (!ApproxEqual(pathStart, segStart)) { + // If we were only interested in filling we could auto-close here + // by calling helper.Edge like in the ClosePath case and detect some + // unclosed paths as rectangles. + // + // For example: + // - "M 1 0 L 0 0 L 0 1 L 1 1 L 1 0" are both rects for filling and + // stroking. + // - "M 1 0 L 0 0 L 0 1 L 1 1" fills a rect but the stroke is shaped + // like a C. + return Nothing(); + } + + if (helper.idx != 0 && !helper.EndSubpath()) { + return Nothing(); + } + + if (cmd.move_to.absolute == StyleIsAbsolute::No) { + to = segStart + to; + } + + pathStart = to; + segStart = to; + if (helper.idx == 0) { + helper.min = to; + helper.max = to; + } + + break; + } + case StylePathCommand::Tag::ClosePath: { + if (!helper.Edge(segStart, pathStart)) { + return Nothing(); + } + if (!helper.EndSubpath()) { + return Nothing(); + } + pathStart = segStart; + break; + } + case StylePathCommand::Tag::LineTo: { + Point to = ToGfxPoint(cmd.line_to.point); + if (cmd.line_to.absolute == StyleIsAbsolute::No) { + to = segStart + to; + } + + if (!helper.Edge(segStart, to)) { + return Nothing(); + } + segStart = to; + break; + } + case StylePathCommand::Tag::HorizontalLineTo: { + Point to = gfx::Point(cmd.horizontal_line_to.x, segStart.y); + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) { + to.x += segStart.x; + } + + if (!helper.Edge(segStart, to)) { + return Nothing(); + } + segStart = to; + break; + } + case StylePathCommand::Tag::VerticalLineTo: { + Point to = gfx::Point(segStart.x, cmd.vertical_line_to.y); + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) { + to.y += segStart.y; + } + + if (!helper.Edge(segStart, to)) { + return Nothing(); + } + segStart = to; + break; + } + default: + return Nothing(); + } + } + + if (!ApproxEqual(pathStart, segStart)) { + // Same situation as with moveto regarding stroking not fullly closed path + // even though the fill is a rectangle. + return Nothing(); + } + + if (!helper.EndSubpath()) { + return Nothing(); + } + + auto size = (helper.max - helper.min); + return Some(Rect(helper.min, Size(size.x, size.y))); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathSegUtils.h b/dom/svg/SVGPathSegUtils.h new file mode 100644 index 0000000000..0f16c066a9 --- /dev/null +++ b/dom/svg/SVGPathSegUtils.h @@ -0,0 +1,286 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHSEGUTILS_H_ +#define DOM_SVG_SVGPATHSEGUTILS_H_ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGPathSegBinding.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Rect.h" +#include "nsDebug.h" + +namespace mozilla { + +struct StylePathCommand; + +#define NS_SVG_PATH_SEG_MAX_ARGS 7 +#define NS_SVG_PATH_SEG_FIRST_VALID_TYPE \ + dom::SVGPathSeg_Binding::PATHSEG_CLOSEPATH +#define NS_SVG_PATH_SEG_LAST_VALID_TYPE \ + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL +#define NS_SVG_PATH_SEG_TYPE_COUNT (NS_SVG_PATH_SEG_LAST_VALID_TYPE + 1) + +/** + * Code that works with path segments can use an instance of this class to + * store/provide information about the start of the current subpath and the + * last path segment (if any). + */ +struct SVGPathTraversalState { + using Point = gfx::Point; + + enum TraversalMode { eUpdateAll, eUpdateOnlyStartAndCurrentPos }; + + SVGPathTraversalState() + : start(0.0, 0.0), + pos(0.0, 0.0), + cp1(0.0, 0.0), + cp2(0.0, 0.0), + length(0.0), + mode(eUpdateAll) {} + + bool ShouldUpdateLengthAndControlPoints() { return mode == eUpdateAll; } + + Point start; // start point of current sub path (reset each moveto) + + Point pos; // current position (end point of previous segment) + + Point cp1; // quadratic control point - if the previous segment was a + // quadratic bezier curve then this is set to the absolute + // position of its control point, otherwise its set to pos + + Point cp2; // cubic control point - if the previous segment was a cubic + // bezier curve then this is set to the absolute position of + // its second control point, otherwise it's set to pos + + float length; // accumulated path length + + TraversalMode mode; // indicates what to track while traversing a path +}; + +/** + * This class is just a collection of static methods - it doesn't have any data + * members, and it's not possible to create instances of this class. This class + * exists purely as a convenient place to gather together a bunch of methods + * related to manipulating and answering questions about path segments. + * Internally we represent path segments purely as an array of floats. See the + * comment documenting SVGPathData for more info on that. + * + * The DOM wrapper classes for encoded path segments (data contained in + * instances of SVGPathData) is DOMSVGPathSeg and its sub-classes. Note that + * there are multiple different DOM classes for path segs - one for each of the + * 19 SVG 1.1 segment types. + */ +class SVGPathSegUtils { + private: + SVGPathSegUtils() = default; // private to prevent instances + + public: + static void GetValueAsString(const float* aSeg, nsAString& aValue); + + /** + * Encode a segment type enum to a float. + * + * At some point in the future we will likely want to encode other + * information into the float, such as whether the command was explicit or + * not. For now all this method does is save on int to float runtime + * conversion by requiring uint32_t and float to be of the same size so we + * can simply do a bitwise uint32_t<->float copy. + */ + static float EncodeType(uint32_t aType) { + static_assert(sizeof(uint32_t) == sizeof(float), + "sizeof uint32_t and float must be the same"); + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + return *(reinterpret_cast<float*>(&aType)); + } + + static uint32_t DecodeType(float aType) { + static_assert(sizeof(uint32_t) == sizeof(float), + "sizeof uint32_t and float must be the same"); + uint32_t type = *(reinterpret_cast<uint32_t*>(&aType)); + MOZ_ASSERT(IsValidType(type), "Seg type not recognized"); + return type; + } + + static char16_t GetPathSegTypeAsLetter(uint32_t aType) { + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + + static const char16_t table[] = { + char16_t('x'), // 0 == PATHSEG_UNKNOWN + char16_t('z'), // 1 == PATHSEG_CLOSEPATH + char16_t('M'), // 2 == PATHSEG_MOVETO_ABS + char16_t('m'), // 3 == PATHSEG_MOVETO_REL + char16_t('L'), // 4 == PATHSEG_LINETO_ABS + char16_t('l'), // 5 == PATHSEG_LINETO_REL + char16_t('C'), // 6 == PATHSEG_CURVETO_CUBIC_ABS + char16_t('c'), // 7 == PATHSEG_CURVETO_CUBIC_REL + char16_t('Q'), // 8 == PATHSEG_CURVETO_QUADRATIC_ABS + char16_t('q'), // 9 == PATHSEG_CURVETO_QUADRATIC_REL + char16_t('A'), // 10 == PATHSEG_ARC_ABS + char16_t('a'), // 11 == PATHSEG_ARC_REL + char16_t('H'), // 12 == PATHSEG_LINETO_HORIZONTAL_ABS + char16_t('h'), // 13 == PATHSEG_LINETO_HORIZONTAL_REL + char16_t('V'), // 14 == PATHSEG_LINETO_VERTICAL_ABS + char16_t('v'), // 15 == PATHSEG_LINETO_VERTICAL_REL + char16_t('S'), // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS + char16_t('s'), // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL + char16_t('T'), // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS + char16_t('t') // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL + }; + static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT, + "Unexpected table size"); + + return table[aType]; + } + + static uint32_t ArgCountForType(uint32_t aType) { + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + + static const uint8_t table[] = { + 0, // 0 == PATHSEG_UNKNOWN + 0, // 1 == PATHSEG_CLOSEPATH + 2, // 2 == PATHSEG_MOVETO_ABS + 2, // 3 == PATHSEG_MOVETO_REL + 2, // 4 == PATHSEG_LINETO_ABS + 2, // 5 == PATHSEG_LINETO_REL + 6, // 6 == PATHSEG_CURVETO_CUBIC_ABS + 6, // 7 == PATHSEG_CURVETO_CUBIC_REL + 4, // 8 == PATHSEG_CURVETO_QUADRATIC_ABS + 4, // 9 == PATHSEG_CURVETO_QUADRATIC_REL + 7, // 10 == PATHSEG_ARC_ABS + 7, // 11 == PATHSEG_ARC_REL + 1, // 12 == PATHSEG_LINETO_HORIZONTAL_ABS + 1, // 13 == PATHSEG_LINETO_HORIZONTAL_REL + 1, // 14 == PATHSEG_LINETO_VERTICAL_ABS + 1, // 15 == PATHSEG_LINETO_VERTICAL_REL + 4, // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS + 4, // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL + 2, // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS + 2 // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL + }; + static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT, + "Unexpected table size"); + + return table[aType]; + } + + /** + * Convenience so that callers can pass a float containing an encoded type + * and have it decoded implicitly. + */ + static uint32_t ArgCountForType(float aType) { + return ArgCountForType(DecodeType(aType)); + } + + static bool IsValidType(uint32_t aType) { + return aType >= NS_SVG_PATH_SEG_FIRST_VALID_TYPE && + aType <= NS_SVG_PATH_SEG_LAST_VALID_TYPE; + } + + static bool IsCubicType(uint32_t aType) { + return aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_REL || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_ABS || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_REL || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS; + } + + static bool IsQuadraticType(uint32_t aType) { + return aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_REL || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_ABS || + aType == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL || + aType == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS; + } + + static bool IsArcType(uint32_t aType) { + return aType == dom::SVGPathSeg_Binding::PATHSEG_ARC_ABS || + aType == dom::SVGPathSeg_Binding::PATHSEG_ARC_REL; + } + + static bool IsRelativeOrAbsoluteType(uint32_t aType) { + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + + // When adding a new path segment type, ensure that the returned condition + // below is still correct. + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Unexpected type"); + + return aType >= dom::SVGPathSeg_Binding::PATHSEG_MOVETO_ABS; + } + + static bool IsRelativeType(uint32_t aType) { + MOZ_ASSERT(IsRelativeOrAbsoluteType(aType), + "IsRelativeType called with segment type that does not come in " + "relative and absolute forms"); + + // When adding a new path segment type, ensure that the returned condition + // below is still correct. + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Unexpected type"); + + return aType & 1; + } + + static uint32_t RelativeVersionOfType(uint32_t aType) { + MOZ_ASSERT(IsRelativeOrAbsoluteType(aType), + "RelativeVersionOfType called with segment type that does not " + "come in relative and absolute forms"); + + // When adding a new path segment type, ensure that the returned condition + // below is still correct. + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Unexpected type"); + + return aType | 1; + } + + static uint32_t SameTypeModuloRelativeness(uint32_t aType1, uint32_t aType2) { + if (!IsRelativeOrAbsoluteType(aType1)) { + return aType1 == aType2; + } + + return RelativeVersionOfType(aType1) == RelativeVersionOfType(aType2); + } + + /** + * Traverse the given path segment and update the SVGPathTraversalState + * object. + */ + static void TraversePathSegment(const float* aData, + SVGPathTraversalState& aState); + + /** + * Traverse the given path segment and update the SVGPathTraversalState + * object. This is identical to the above one but accepts StylePathCommand. + */ + static void TraversePathSegment(const StylePathCommand& aCommand, + SVGPathTraversalState& aState); +}; + +/// Detect whether the path represents a rectangle (for both filling AND +/// stroking) and if so returns it. +/// +/// This is typically useful for google slides which has many of these rectangle +/// shaped paths. It handles the same scenarios as skia's +/// SkPathPriv::IsRectContour which it is inspried from, including zero-length +/// edges and multiple points on edges of the rectangle, and doesn't attempt to +/// detect flat curves (that could easily be added but the expectation is that +/// since skia doesn't fast path it we're not likely to run into it in +/// practice). +/// +/// We could implement something similar for polygons. +Maybe<gfx::Rect> SVGPathToAxisAlignedRect(Span<const StylePathCommand> aPath); + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHSEGUTILS_H_ diff --git a/dom/svg/SVGPatternElement.cpp b/dom/svg/SVGPatternElement.cpp new file mode 100644 index 0000000000..36aba67825 --- /dev/null +++ b/dom/svg/SVGPatternElement.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPatternElement.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGPatternElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "DOMSVGAnimatedTransformList.h" +#include "nsGkAtoms.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Pattern) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGPatternElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGPatternElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//--------------------- Patterns ------------------------ + +SVGElement::LengthInfo SVGPatternElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +SVGElement::EnumInfo SVGPatternElement::sEnumInfo[2] = { + {nsGkAtoms::patternUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::patternContentUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_USERSPACEONUSE}}; + +SVGElement::StringInfo SVGPatternElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGPatternElement::SVGPatternElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGPatternElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode method + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPatternElement) + +//---------------------------------------------------------------------- + +already_AddRefed<SVGAnimatedRect> SVGPatternElement::ViewBox() { + return mViewBox.ToSVGAnimatedRect(this); +} + +already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> +SVGPatternElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGPatternElement::PatternUnits() { + return mEnumAttributes[PATTERNUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGPatternElement::PatternContentUnits() { + return mEnumAttributes[PATTERNCONTENTUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedTransformList> +SVGPatternElement::PatternTransform() { + // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList + // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so: + return DOMSVGAnimatedTransformList::GetDOMWrapper( + GetAnimatedTransformList(DO_ALLOCATE), this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGPatternElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGPatternElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGPatternElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGPatternElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGPatternElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGPatternElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sColorMap, + sFEFloodMap, + sFillStrokeMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sGraphicsMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return FindAttributeDependence(name, map) || + SVGPatternElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGAnimatedTransformList* SVGPatternElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!mPatternTransform && (aFlags & DO_ALLOCATE)) { + mPatternTransform = MakeUnique<SVGAnimatedTransformList>(); + } + return mPatternTransform.get(); +} + +/* virtual */ +bool SVGPatternElement::HasValidDimensions() const { + return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() && + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 && + mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() && + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0; +} + +SVGElement::LengthAttributesInfo SVGPatternElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGPatternElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGAnimatedViewBox* SVGPatternElement::GetAnimatedViewBox() { + return &mViewBox; +} + +SVGAnimatedPreserveAspectRatio* +SVGPatternElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +SVGElement::StringAttributesInfo SVGPatternElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPatternElement.h b/dom/svg/SVGPatternElement.h new file mode 100644 index 0000000000..4f8f1a292d --- /dev/null +++ b/dom/svg/SVGPatternElement.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATTERNELEMENT_H_ +#define DOM_SVG_SVGPATTERNELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedString.h" +#include "SVGAnimatedTransformList.h" +#include "SVGAnimatedViewBox.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/UniquePtr.h" + +nsresult NS_NewSVGPatternElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGPatternFrame; + +namespace dom { +class DOMSVGAnimatedTransformList; + +using SVGPatternElementBase = SVGElement; + +class SVGPatternElement final : public SVGPatternElementBase { + friend class mozilla::SVGPatternFrame; + + protected: + friend nsresult(::NS_NewSVGPatternElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGPatternElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + virtual mozilla::SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + nsStaticAtom* GetTransformListAttrName() const override { + return nsGkAtoms::patternTransform; + } + + // WebIDL + already_AddRefed<SVGAnimatedRect> ViewBox(); + already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> PreserveAspectRatio(); + already_AddRefed<DOMSVGAnimatedEnumeration> PatternUnits(); + already_AddRefed<DOMSVGAnimatedEnumeration> PatternContentUnits(); + already_AddRefed<DOMSVGAnimatedTransformList> PatternTransform(); + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + already_AddRefed<DOMSVGAnimatedString> Href(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + virtual SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() + override; + SVGAnimatedViewBox* GetAnimatedViewBox() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { PATTERNUNITS, PATTERNCONTENTUNITS }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static EnumInfo sEnumInfo[2]; + + UniquePtr<mozilla::SVGAnimatedTransformList> mPatternTransform; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + // SVGFitToViewbox properties + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGPATTERNELEMENT_H_ diff --git a/dom/svg/SVGPoint.h b/dom/svg/SVGPoint.h new file mode 100644 index 0000000000..4043566bde --- /dev/null +++ b/dom/svg/SVGPoint.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOINT_H_ +#define DOM_SVG_SVGPOINT_H_ + +#include "nsDebug.h" +#include "gfxPoint.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { + +/** + * This class is currently used for point list attributes. + * + * The DOM wrapper class for this class is DOMSVGPoint. + */ +class SVGPoint { + using Point = mozilla::gfx::Point; + + public: + SVGPoint() : mX(0.0f), mY(0.0f) {} + + SVGPoint(float aX, float aY) : mX(aX), mY(aY) { + NS_ASSERTION(IsValid(), "Constructed an invalid SVGPoint"); + } + + bool operator==(const SVGPoint& rhs) const { + return mX == rhs.mX && mY == rhs.mY; + } + + SVGPoint& operator+=(const SVGPoint& rhs) { + mX += rhs.mX; + mY += rhs.mY; + return *this; + } + + operator gfxPoint() const { return gfxPoint(mX, mY); } + + operator Point() const { return Point(mX, mY); } + +#ifdef DEBUG + bool IsValid() const { return IsFinite(mX) && IsFinite(mY); } +#endif + + void SetX(float aX) { mX = aX; } + void SetY(float aY) { mY = aY; } + float GetX() const { return mX; } + float GetY() const { return mY; } + + bool operator!=(const SVGPoint& rhs) const { + return mX != rhs.mX || mY != rhs.mY; + } + + float mX; + float mY; +}; + +inline SVGPoint operator+(const SVGPoint& aP1, const SVGPoint& aP2) { + return SVGPoint(aP1.mX + aP2.mX, aP1.mY + aP2.mY); +} + +inline SVGPoint operator-(const SVGPoint& aP1, const SVGPoint& aP2) { + return SVGPoint(aP1.mX - aP2.mX, aP1.mY - aP2.mY); +} + +inline SVGPoint operator*(float aFactor, const SVGPoint& aPoint) { + return SVGPoint(aFactor * aPoint.mX, aFactor * aPoint.mY); +} + +inline SVGPoint operator*(const SVGPoint& aPoint, float aFactor) { + return SVGPoint(aFactor * aPoint.mX, aFactor * aPoint.mY); +} + +} // namespace mozilla + +#endif // DOM_SVG_SVGPOINT_H_ diff --git a/dom/svg/SVGPointList.cpp b/dom/svg/SVGPointList.cpp new file mode 100644 index 0000000000..1a7ccf908f --- /dev/null +++ b/dom/svg/SVGPointList.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "SVGPointList.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsTextFormatter.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +nsresult SVGPointList::CopyFrom(const SVGPointList& rhs) { + if (!mItems.Assign(rhs.mItems, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGPointList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + char16_t buf[50]; + uint32_t last = mItems.Length() - 1; + for (uint32_t i = 0; i < mItems.Length(); ++i) { + // Would like to use aValue.AppendPrintf("%f,%f", item.mX, item.mY), + // but it's not possible to always avoid trailing zeros. + nsTextFormatter::snprintf(buf, ArrayLength(buf), u"%g,%g", + double(mItems[i].mX), double(mItems[i].mY)); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(buf); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGPointList::SetValueFromString(const nsAString& aValue) { + // The spec says that the list is parsed and accepted up to the first error + // encountered, so we must call CopyFrom even if an error occurs. We still + // want to throw any error code from setAttribute if there's a problem + // though, so we must take care to return any error code. + + nsresult rv = NS_OK; + + SVGPointList temp; + + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, + nsTokenizerFlags::SeparatorOptional> + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + const nsAString& token = tokenizer.nextToken(); + + RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr<const char16_t> end = + SVGContentUtils::GetEndRangedPtr(token); + + float x; + if (!SVGContentUtils::ParseNumber(iter, end, x)) { + rv = NS_ERROR_DOM_SYNTAX_ERR; + break; + } + + float y; + if (iter == end) { + if (!tokenizer.hasMoreTokens() || + !SVGContentUtils::ParseNumber(tokenizer.nextToken(), y)) { + rv = NS_ERROR_DOM_SYNTAX_ERR; + break; + } + } else { + // It's possible for the token to be 10-30 which has + // no separator but needs to be parsed as 10, -30 + const nsAString& leftOver = Substring(iter.get(), end.get()); + if (leftOver[0] != '-' || !SVGContentUtils::ParseNumber(leftOver, y)) { + rv = NS_ERROR_DOM_SYNTAX_ERR; + break; + } + } + temp.AppendItem(SVGPoint(x, y)); + } + if (tokenizer.separatorAfterCurrentToken()) { + rv = NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + nsresult rv2 = CopyFrom(temp); + if (NS_FAILED(rv2)) { + return rv2; // prioritize OOM error code over syntax errors + } + return rv; +} + +} // namespace mozilla diff --git a/dom/svg/SVGPointList.h b/dom/svg/SVGPointList.h new file mode 100644 index 0000000000..ab6a258f27 --- /dev/null +++ b/dom/svg/SVGPointList.h @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOINTLIST_H_ +#define DOM_SVG_SVGPOINTLIST_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "SVGElement.h" +#include "nsTArray.h" +#include "SVGPoint.h" + +#include <string.h> + +namespace mozilla { + +namespace dom { +class DOMSVGPoint; +class DOMSVGPointList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGPointList. + */ +class SVGPointList { + friend class SVGAnimatedPointList; + friend class dom::DOMSVGPointList; + friend class dom::DOMSVGPoint; + + public: + SVGPointList() = default; + ~SVGPointList() = default; + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mItems.IsEmpty(); } + + uint32_t Length() const { return mItems.Length(); } + + const SVGPoint& operator[](uint32_t aIndex) const { return mItems[aIndex]; } + + bool operator==(const SVGPointList& rhs) const { + // memcmp can be faster than |mItems == rhs.mItems| + return mItems.Length() == rhs.mItems.Length() && + memcmp(mItems.Elements(), rhs.mItems.Elements(), + mItems.Length() * sizeof(SVGPoint)) == 0; + } + + bool SetCapacity(uint32_t aSize) { + return mItems.SetCapacity(aSize, fallible); + } + + void Compact() { mItems.Compact(); } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedPointList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGPointList& rhs); + + SVGPoint& operator[](uint32_t aIndex) { return mItems[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mItems.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mItems.Clear(); } + + bool InsertItem(uint32_t aIndex, const SVGPoint& aPoint) { + if (aIndex >= mItems.Length()) { + aIndex = mItems.Length(); + } + return !!mItems.InsertElementAt(aIndex, aPoint, fallible); + } + + void ReplaceItem(uint32_t aIndex, const SVGPoint& aPoint) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems[aIndex] = aPoint; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems.RemoveElementAt(aIndex); + } + + bool AppendItem(SVGPoint aPoint) { + return !!mItems.AppendElement(aPoint, fallible); + } + + protected: + /* See SVGLengthList for the rationale for using FallibleTArray<SVGPoint> + * instead of FallibleTArray<SVGPoint, 1>. + */ + FallibleTArray<SVGPoint> mItems; +}; + +/** + * This SVGPointList subclass is for SVGPointListSMILType which needs a + * mutable version of SVGPointList. Instances of this class do not have + * DOM wrappers that need to be kept in sync, so we can safely expose any + * protected base class methods required by the SMIL code. + * + * This class contains a strong reference to the element that instances of + * this class are being used to animate. This is because the SMIL code stores + * instances of this class in SMILValue objects, some of which are cached. + * Holding a strong reference to the element here prevents the element from + * disappearing out from under the SMIL code unexpectedly. + */ +class SVGPointListAndInfo : public SVGPointList { + public: + explicit SVGPointListAndInfo(dom::SVGElement* aElement = nullptr) + : mElement(do_GetWeakReference(static_cast<nsINode*>(aElement))) {} + + void SetInfo(dom::SVGElement* aElement) { + mElement = do_GetWeakReference(static_cast<nsINode*>(aElement)); + } + + dom::SVGElement* Element() const { + nsCOMPtr<nsIContent> e = do_QueryReferent(mElement); + return static_cast<dom::SVGElement*>(e.get()); + } + + /** + * Returns true if this object is an "identity" value, from the perspective + * of SMIL. In other words, returns true until the initial value set up in + * SVGPointListSMILType::Init() has been changed with a SetInfo() call. + */ + bool IsIdentity() const { + if (!mElement) { + MOZ_ASSERT(IsEmpty(), "target element propagation failure"); + return true; + } + return false; + } + + nsresult CopyFrom(const SVGPointListAndInfo& rhs) { + mElement = rhs.mElement; + return SVGPointList::CopyFrom(rhs); + } + + /** + * Exposed so that SVGPointList baseVals can be copied to + * SVGPointListAndInfo objects. Note that callers should also call + * SetElement() when using this method! + */ + nsresult CopyFrom(const SVGPointList& rhs) { + return SVGPointList::CopyFrom(rhs); + } + const SVGPoint& operator[](uint32_t aIndex) const { + return SVGPointList::operator[](aIndex); + } + SVGPoint& operator[](uint32_t aIndex) { + return SVGPointList::operator[](aIndex); + } + bool SetLength(uint32_t aNumberOfItems) { + return SVGPointList::SetLength(aNumberOfItems); + } + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPOINTLIST_H_ diff --git a/dom/svg/SVGPointListSMILType.cpp b/dom/svg/SVGPointListSMILType.cpp new file mode 100644 index 0000000000..d4ce9b5eac --- /dev/null +++ b/dom/svg/SVGPointListSMILType.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPointListSMILType.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "SVGPointList.h" +#include <math.h> + +namespace mozilla { + +/*static*/ +SVGPointListSMILType SVGPointListSMILType::sSingleton; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGPointListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + SVGPointListAndInfo* pointList = new SVGPointListAndInfo(); + + aValue.mU.mPtr = pointList; + aValue.mType = this; +} + +void SVGPointListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast<SVGPointListAndInfo*>(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGPointListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGPointListAndInfo* src = + static_cast<const SVGPointListAndInfo*>(aSrc.mU.mPtr); + SVGPointListAndInfo* dest = static_cast<SVGPointListAndInfo*>(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGPointListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast<const SVGPointListAndInfo*>(aLeft.mU.mPtr) == + *static_cast<const SVGPointListAndInfo*>(aRight.mU.mPtr); +} + +nsresult SVGPointListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGPointListAndInfo& dest = *static_cast<SVGPointListAndInfo*>(aDest.mU.mPtr); + const SVGPointListAndInfo& valueToAdd = + *static_cast<const SVGPointListAndInfo*>(aValueToAdd.mU.mPtr); + + MOZ_ASSERT(dest.Element() || valueToAdd.Element(), + "Target element propagation failure"); + + if (valueToAdd.IsIdentity()) { + return NS_OK; + } + if (dest.IsIdentity()) { + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] = aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; + } + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + if (dest.Length() != valueToAdd.Length()) { + // For now we only support animation between lists with the same number of + // items. SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] += aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; +} + +nsresult SVGPointListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + const SVGPointListAndInfo& from = + *static_cast<const SVGPointListAndInfo*>(aFrom.mU.mPtr); + const SVGPointListAndInfo& to = + *static_cast<const SVGPointListAndInfo*>(aTo.mU.mPtr); + + if (from.Length() != to.Length()) { + // Lists in the 'values' attribute must have the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + // We return the root of the sum of the squares of the distances between the + // points at each corresponding index. + + double total = 0.0; + + for (uint32_t i = 0; i < to.Length(); ++i) { + double dx = to[i].mX - from[i].mX; + double dy = to[i].mY - from[i].mY; + total += dx * dx + dy * dy; + } + double distance = sqrt(total); + if (!IsFinite(distance)) { + return NS_ERROR_FAILURE; + } + aDistance = distance; + + return NS_OK; +} + +nsresult SVGPointListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGPointListAndInfo& start = + *static_cast<const SVGPointListAndInfo*>(aStartVal.mU.mPtr); + const SVGPointListAndInfo& end = + *static_cast<const SVGPointListAndInfo*>(aEndVal.mU.mPtr); + SVGPointListAndInfo& result = + *static_cast<SVGPointListAndInfo*>(aResult.mU.mPtr); + + MOZ_ASSERT(end.Element(), "Can't propagate target element"); + MOZ_ASSERT(start.Element() == end.Element() || !start.Element(), + "Different target elements"); + + if (start.Element() && // 'start' is not an "identity" value + start.Length() != end.Length()) { + // For now we only support animation between lists of the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + if (!result.SetLength(end.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + result.SetInfo(end.Element()); // propagate target element info! + + if (start.Length() != end.Length()) { + MOZ_ASSERT(start.Length() == 0, "Not an identity value"); + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = aUnitDistance * end[i]; + } + return NS_OK; + } + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = start[i] + (end[i] - start[i]) * aUnitDistance; + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGPointListSMILType.h b/dom/svg/SVGPointListSMILType.h new file mode 100644 index 0000000000..1432e5286b --- /dev/null +++ b/dom/svg/SVGPointListSMILType.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOINTLISTSMILTYPE_H_ +#define DOM_SVG_SVGPOINTLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGPointListSMILType +// +// Operations for animating an SVGPointList. +// +class SVGPointListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGPointListSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGPointListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPOINTLISTSMILTYPE_H_ diff --git a/dom/svg/SVGPolyElement.cpp b/dom/svg/SVGPolyElement.cpp new file mode 100644 index 0000000000..5f4c56a614 --- /dev/null +++ b/dom/svg/SVGPolyElement.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPolyElement.h" +#include "DOMSVGPointList.h" +#include "mozilla/gfx/2D.h" +#include "SVGContentUtils.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// Implementation + +SVGPolyElement::SVGPolyElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGPolyElementBase(std::move(aNodeInfo)) {} + +already_AddRefed<DOMSVGPointList> SVGPolyElement::Points() { + void* key = mPoints.GetBaseValKey(); + RefPtr<DOMSVGPointList> points = + DOMSVGPointList::GetDOMWrapper(key, this, false); + return points.forget(); +} + +already_AddRefed<DOMSVGPointList> SVGPolyElement::AnimatedPoints() { + void* key = mPoints.GetAnimValKey(); + RefPtr<DOMSVGPointList> points = + DOMSVGPointList::GetDOMWrapper(key, this, true); + return points.forget(); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGPolyElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sMarkersMap}; + + return FindAttributeDependence(name, map) || + SVGPolyElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGPolyElement::HasValidDimensions() const { + return !mPoints.GetAnimValue().IsEmpty(); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGPolyElement::AttributeDefinesGeometry(const nsAtom* aName) { + return aName == nsGkAtoms::points; +} + +void SVGPolyElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (!points.Length()) return; + + float px = points[0].mX, py = points[0].mY, prevAngle = 0.0; + + aMarks->AppendElement(SVGMark(px, py, 0, SVGMark::eStart)); + + for (uint32_t i = 1; i < points.Length(); ++i) { + float x = points[i].mX; + float y = points[i].mY; + float angle = std::atan2(y - py, x - px); + + // Vertex marker. + if (i == 1) { + aMarks->ElementAt(0).angle = angle; + } else { + aMarks->ElementAt(aMarks->Length() - 1).angle = + SVGContentUtils::AngleBisect(prevAngle, angle); + } + + aMarks->AppendElement(SVGMark(x, y, 0, SVGMark::eMid)); + + prevAngle = angle; + px = x; + py = y; + } + + aMarks->LastElement().angle = prevAngle; + aMarks->LastElement().type = SVGMark::eEnd; +} + +bool SVGPolyElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (!points.Length()) { + // Rendering of the element is disabled + aBounds->SetEmpty(); + return true; + } + + if (aStrokeOptions.mLineWidth > 0 || aToNonScalingStrokeSpace) { + // We don't handle non-scaling-stroke or stroke-miterlimit etc. yet + return false; + } + + if (aToBoundsSpace.IsRectilinear()) { + // We can avoid transforming each point and just transform the result. + // Important for large point lists. + Rect bounds(points[0], Size()); + for (uint32_t i = 1; i < points.Length(); ++i) { + bounds.ExpandToEnclose(points[i]); + } + *aBounds = aToBoundsSpace.TransformBounds(bounds); + } else { + *aBounds = Rect(aToBoundsSpace.TransformPoint(points[0]), Size()); + for (uint32_t i = 1; i < points.Length(); ++i) { + aBounds->ExpandToEnclose(aToBoundsSpace.TransformPoint(points[i])); + } + } + return true; +} +} // namespace mozilla::dom diff --git a/dom/svg/SVGPolyElement.h b/dom/svg/SVGPolyElement.h new file mode 100644 index 0000000000..1fcc43b350 --- /dev/null +++ b/dom/svg/SVGPolyElement.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOLYELEMENT_H_ +#define DOM_SVG_SVGPOLYELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGAnimatedPointList.h" +#include "SVGGeometryElement.h" + +namespace mozilla::dom { + +class DOMSVGPointList; + +using SVGPolyElementBase = SVGGeometryElement; + +class SVGPolyElement : public SVGPolyElementBase { + protected: + explicit SVGPolyElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + virtual ~SVGPolyElement() = default; + + public: + // interfaces + + NS_INLINE_DECL_REFCOUNTING_INHERITED(SVGPolyElement, SVGPolyElementBase) + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + SVGAnimatedPointList* GetAnimatedPointList() override { return &mPoints; } + nsStaticAtom* GetPointListAttrName() const override { + return nsGkAtoms::points; + } + + // SVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool AttributeDefinesGeometry(const nsAtom* aName) override; + bool IsMarkable() override { return true; } + void GetMarkPoints(nsTArray<SVGMark>* aMarks) override; + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + + // WebIDL + already_AddRefed<DOMSVGPointList> Points(); + already_AddRefed<DOMSVGPointList> AnimatedPoints(); + + protected: + SVGAnimatedPointList mPoints; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPOLYELEMENT_H_ diff --git a/dom/svg/SVGPolygonElement.cpp b/dom/svg/SVGPolygonElement.cpp new file mode 100644 index 0000000000..def8015153 --- /dev/null +++ b/dom/svg/SVGPolygonElement.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPolygonElement.h" +#include "mozilla/dom/SVGPolygonElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "SVGContentUtils.h" + +using namespace mozilla::gfx; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Polygon) + +namespace mozilla::dom { + +JSObject* SVGPolygonElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGPolygonElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGPolygonElement::SVGPolygonElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGPolygonElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPolygonElement) + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +void SVGPolygonElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) { + SVGPolyElement::GetMarkPoints(aMarks); + + if (aMarks->IsEmpty() || aMarks->LastElement().type != SVGMark::eEnd) { + return; + } + + SVGMark* endMark = &aMarks->LastElement(); + SVGMark* startMark = &aMarks->ElementAt(0); + float angle = + std::atan2(startMark->y - endMark->y, startMark->x - endMark->x); + + endMark->type = SVGMark::eMid; + endMark->angle = SVGContentUtils::AngleBisect(angle, endMark->angle); + startMark->angle = SVGContentUtils::AngleBisect(angle, startMark->angle); + // for a polygon (as opposed to a polyline) there's an implicit extra point + // co-located with the start point that SVGPolyElement::GetMarkPoints + // doesn't return + aMarks->AppendElement( + SVGMark(startMark->x, startMark->y, startMark->angle, SVGMark::eEnd)); +} + +already_AddRefed<Path> SVGPolygonElement::BuildPath(PathBuilder* aBuilder) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (points.IsEmpty()) { + return nullptr; + } + + aBuilder->MoveTo(points[0]); + for (uint32_t i = 1; i < points.Length(); ++i) { + aBuilder->LineTo(points[i]); + } + + aBuilder->Close(); + + return aBuilder->Finish(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPolygonElement.h b/dom/svg/SVGPolygonElement.h new file mode 100644 index 0000000000..5916b3377d --- /dev/null +++ b/dom/svg/SVGPolygonElement.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOLYGONELEMENT_H_ +#define DOM_SVG_SVGPOLYGONELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGPolyElement.h" + +nsresult NS_NewSVGPolygonElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGPolygonElementBase = SVGPolyElement; + +class SVGPolygonElement final : public SVGPolygonElementBase { + protected: + explicit SVGPolygonElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGPolygonElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + public: + // SVGGeometryElement methods: + void GetMarkPoints(nsTArray<SVGMark>* aMarks) override; + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPOLYGONELEMENT_H_ diff --git a/dom/svg/SVGPolylineElement.cpp b/dom/svg/SVGPolylineElement.cpp new file mode 100644 index 0000000000..a657b0cefd --- /dev/null +++ b/dom/svg/SVGPolylineElement.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPolylineElement.h" +#include "mozilla/dom/SVGPolylineElementBinding.h" +#include "mozilla/gfx/2D.h" + +using namespace mozilla::gfx; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Polyline) + +namespace mozilla::dom { + +JSObject* SVGPolylineElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGPolylineElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGPolylineElement::SVGPolylineElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGPolylineElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPolylineElement) + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +already_AddRefed<Path> SVGPolylineElement::BuildPath(PathBuilder* aBuilder) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (points.IsEmpty()) { + return nullptr; + } + + aBuilder->MoveTo(points[0]); + for (uint32_t i = 1; i < points.Length(); ++i) { + aBuilder->LineTo(points[i]); + } + + return aBuilder->Finish(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPolylineElement.h b/dom/svg/SVGPolylineElement.h new file mode 100644 index 0000000000..2b35354a0e --- /dev/null +++ b/dom/svg/SVGPolylineElement.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOLYLINEELEMENT_H_ +#define DOM_SVG_SVGPOLYLINEELEMENT_H_ + +#include "SVGPolyElement.h" + +nsresult NS_NewSVGPolylineElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGPolylineElementBase = SVGPolyElement; + +class SVGPolylineElement final : public SVGPolylineElementBase { + protected: + explicit SVGPolylineElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGPolylineElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + // SVGGeometryElement methods: + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPOLYLINEELEMENT_H_ diff --git a/dom/svg/SVGPreserveAspectRatio.cpp b/dom/svg/SVGPreserveAspectRatio.cpp new file mode 100644 index 0000000000..0cd947e867 --- /dev/null +++ b/dom/svg/SVGPreserveAspectRatio.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPreserveAspectRatio.h" + +#include "mozilla/dom/SVGPreserveAspectRatioBinding.h" +#include "nsContentUtils.h" +#include "nsWhitespaceTokenizer.h" +#include "SVGAnimatedPreserveAspectRatio.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; + +namespace mozilla { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGPreserveAspectRatio, + mSVGElement) + +static const char* sAlignStrings[] = { + "none", "xMinYMin", "xMidYMin", "xMaxYMin", "xMinYMid", + "xMidYMid", "xMaxYMid", "xMinYMax", "xMidYMax", "xMaxYMax"}; + +static const char* sMeetOrSliceStrings[] = {"meet", "slice"}; + +static uint16_t GetAlignForString(const nsAString& aAlignString) { + for (uint32_t i = 0; i < ArrayLength(sAlignStrings); i++) { + if (aAlignString.EqualsASCII(sAlignStrings[i])) { + return (i + SVG_ALIGN_MIN_VALID); + } + } + + return SVG_PRESERVEASPECTRATIO_UNKNOWN; +} + +static uint16_t GetMeetOrSliceForString(const nsAString& aMeetOrSlice) { + for (uint32_t i = 0; i < ArrayLength(sMeetOrSliceStrings); i++) { + if (aMeetOrSlice.EqualsASCII(sMeetOrSliceStrings[i])) { + return (i + SVG_MEETORSLICE_MIN_VALID); + } + } + + return SVG_MEETORSLICE_UNKNOWN; +} + +/* static */ +nsresult SVGPreserveAspectRatio::FromString(const nsAString& aString, + SVGPreserveAspectRatio* aValue) { + nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tokenizer( + aString); + if (tokenizer.whitespaceBeforeFirstToken() || !tokenizer.hasMoreTokens()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + const nsAString& token = tokenizer.nextToken(); + + nsresult rv; + SVGPreserveAspectRatio val; + + rv = val.SetAlign(GetAlignForString(token)); + + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + if (tokenizer.hasMoreTokens()) { + rv = val.SetMeetOrSlice(GetMeetOrSliceForString(tokenizer.nextToken())); + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + } else { + val.SetMeetOrSlice(SVG_MEETORSLICE_MEET); + } + + if (tokenizer.whitespaceAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + *aValue = val; + return NS_OK; +} + +void SVGPreserveAspectRatio::ToString(nsAString& aValueAsString) const { + MOZ_ASSERT(mAlign >= SVG_ALIGN_MIN_VALID && mAlign <= SVG_ALIGN_MAX_VALID, + "Unknown align"); + aValueAsString.AssignASCII(sAlignStrings[mAlign - SVG_ALIGN_MIN_VALID]); + + if (mAlign != uint8_t(SVG_PRESERVEASPECTRATIO_NONE)) { + MOZ_ASSERT(mMeetOrSlice >= SVG_MEETORSLICE_MIN_VALID && + mMeetOrSlice <= SVG_MEETORSLICE_MAX_VALID, + "Unknown meetOrSlice"); + aValueAsString.Append(' '); + aValueAsString.AppendASCII( + sMeetOrSliceStrings[mMeetOrSlice - SVG_MEETORSLICE_MIN_VALID]); + } +} + +bool SVGPreserveAspectRatio::operator==( + const SVGPreserveAspectRatio& aOther) const { + return mAlign == aOther.mAlign && mMeetOrSlice == aOther.mMeetOrSlice; +} + +JSObject* DOMSVGPreserveAspectRatio::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return mozilla::dom::SVGPreserveAspectRatio_Binding::Wrap(aCx, this, + aGivenProto); +} + +uint16_t DOMSVGPreserveAspectRatio::Align() { + if (mIsBaseValue) { + return mVal->GetBaseValue().GetAlign(); + } + + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue().GetAlign(); +} + +void DOMSVGPreserveAspectRatio::SetAlign(uint16_t aAlign, ErrorResult& rv) { + if (!mIsBaseValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->SetBaseAlign(aAlign, mSVGElement); +} + +uint16_t DOMSVGPreserveAspectRatio::MeetOrSlice() { + if (mIsBaseValue) { + return mVal->GetBaseValue().GetMeetOrSlice(); + } + + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue().GetMeetOrSlice(); +} + +void DOMSVGPreserveAspectRatio::SetMeetOrSlice(uint16_t aMeetOrSlice, + ErrorResult& rv) { + if (!mIsBaseValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->SetBaseMeetOrSlice(aMeetOrSlice, mSVGElement); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPreserveAspectRatio.h b/dom/svg/SVGPreserveAspectRatio.h new file mode 100644 index 0000000000..7bcc724e07 --- /dev/null +++ b/dom/svg/SVGPreserveAspectRatio.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPRESERVEASPECTRATIO_H_ +#define DOM_SVG_SVGPRESERVEASPECTRATIO_H_ + +#include "mozilla/dom/SVGPreserveAspectRatioBinding.h" +#include "mozilla/HashFunctions.h" // for HashGeneric + +#include "nsWrapperCache.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla { +class ErrorResult; + +// These constants represent the range of valid enum values for the <align> +// parameter. They exclude the sentinel _UNKNOWN value. +const uint16_t SVG_ALIGN_MIN_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_PRESERVEASPECTRATIO_NONE; +const uint16_t SVG_ALIGN_MAX_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_PRESERVEASPECTRATIO_XMAXYMAX; + +// These constants represent the range of valid enum values for the +// <meetOrSlice> parameter. They exclude the sentinel _UNKNOWN value. +const uint16_t SVG_MEETORSLICE_MIN_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_MEETORSLICE_MEET; +const uint16_t SVG_MEETORSLICE_MAX_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_MEETORSLICE_SLICE; + +class SVGAnimatedPreserveAspectRatio; + +class SVGPreserveAspectRatio final { + friend class SVGAnimatedPreserveAspectRatio; + + public: + explicit SVGPreserveAspectRatio() + : mAlign(dom::SVGPreserveAspectRatio_Binding:: + SVG_PRESERVEASPECTRATIO_UNKNOWN), + mMeetOrSlice( + dom::SVGPreserveAspectRatio_Binding::SVG_MEETORSLICE_UNKNOWN) {} + + SVGPreserveAspectRatio(uint8_t aAlign, uint8_t aMeetOrSlice) + : mAlign(aAlign), mMeetOrSlice(aMeetOrSlice) {} + + static nsresult FromString(const nsAString& aString, + SVGPreserveAspectRatio* aValue); + void ToString(nsAString& aValueAsString) const; + + bool operator==(const SVGPreserveAspectRatio& aOther) const; + + nsresult SetAlign(uint16_t aAlign) { + if (aAlign < SVG_ALIGN_MIN_VALID || aAlign > SVG_ALIGN_MAX_VALID) + return NS_ERROR_FAILURE; + mAlign = static_cast<uint8_t>(aAlign); + return NS_OK; + } + + auto GetAlign() const { return mAlign; } + + nsresult SetMeetOrSlice(uint16_t aMeetOrSlice) { + if (aMeetOrSlice < SVG_MEETORSLICE_MIN_VALID || + aMeetOrSlice > SVG_MEETORSLICE_MAX_VALID) + return NS_ERROR_FAILURE; + mMeetOrSlice = static_cast<uint8_t>(aMeetOrSlice); + return NS_OK; + } + + auto GetMeetOrSlice() const { return mMeetOrSlice; } + + PLDHashNumber Hash() const { return HashGeneric(mAlign, mMeetOrSlice); } + + private: + // We can't use enum types here because some compilers fail to pack them. + uint8_t mAlign; + uint8_t mMeetOrSlice; +}; + +namespace dom { + +class DOMSVGPreserveAspectRatio final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGPreserveAspectRatio) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGPreserveAspectRatio) + + DOMSVGPreserveAspectRatio(SVGAnimatedPreserveAspectRatio* aVal, + SVGElement* aSVGElement, bool aIsBaseValue) + : mVal(aVal), mSVGElement(aSVGElement), mIsBaseValue(aIsBaseValue) {} + + // WebIDL + SVGElement* GetParentObject() const { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + uint16_t Align(); + void SetAlign(uint16_t aAlign, ErrorResult& rv); + uint16_t MeetOrSlice(); + void SetMeetOrSlice(uint16_t aMeetOrSlice, ErrorResult& rv); + + protected: + ~DOMSVGPreserveAspectRatio(); + + SVGAnimatedPreserveAspectRatio* + mVal; // kept alive because it belongs to mSVGElement + RefPtr<SVGElement> mSVGElement; + const bool mIsBaseValue; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGPRESERVEASPECTRATIO_H_ diff --git a/dom/svg/SVGRect.cpp b/dom/svg/SVGRect.cpp new file mode 100644 index 0000000000..ccf643f5e0 --- /dev/null +++ b/dom/svg/SVGRect.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGRect.h" + +#include "mozilla/dom/SVGRectBinding.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "SVGAnimatedViewBox.h" +#include "nsWrapperCache.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// nsISupports methods: + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(SVGRect, mParent) + +//---------------------------------------------------------------------- +// implementation: + +SVGRect::SVGRect(SVGSVGElement* aSVGElement) + : mVal(nullptr), mParent(aSVGElement), mType(RectType::CreatedValue) { + MOZ_ASSERT(mParent); + mRect = gfx::Rect(0, 0, 0, 0); +} + +JSObject* SVGRect::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + MOZ_ASSERT(mParent); + return SVGRect_Binding::Wrap(aCx, this, aGivenProto); +} + +float SVGRect::X() { + switch (mType) { + case RectType::AnimValue: + static_cast<SVGElement*>(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().x; + case RectType::BaseValue: + return mVal->GetBaseValue().x; + default: + return mRect.x; + } +} + +float SVGRect::Y() { + switch (mType) { + case RectType::AnimValue: + static_cast<SVGElement*>(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().y; + case RectType::BaseValue: + return mVal->GetBaseValue().y; + default: + return mRect.y; + } +} + +float SVGRect::Width() { + switch (mType) { + case RectType::AnimValue: + static_cast<SVGElement*>(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().width; + case RectType::BaseValue: + return mVal->GetBaseValue().width; + default: + return mRect.width; + } +} + +float SVGRect::Height() { + switch (mType) { + case RectType::AnimValue: + static_cast<SVGElement*>(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().height; + case RectType::BaseValue: + return mVal->GetBaseValue().height; + default: + return mRect.height; + } +} + +void SVGRect::SetX(float aX, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.x = aX; + mVal->SetBaseValue(rect, static_cast<SVGElement*>(mParent->AsElement())); + return; + } + default: + mRect.x = aX; + } +} + +void SVGRect::SetY(float aY, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.y = aY; + mVal->SetBaseValue(rect, static_cast<SVGElement*>(mParent->AsElement())); + return; + } + default: + mRect.y = aY; + } +} + +void SVGRect::SetWidth(float aWidth, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.width = aWidth; + mVal->SetBaseValue(rect, static_cast<SVGElement*>(mParent->AsElement())); + return; + } + default: + mRect.width = aWidth; + } +} + +void SVGRect::SetHeight(float aHeight, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.height = aHeight; + mVal->SetBaseValue(rect, static_cast<SVGElement*>(mParent->AsElement())); + return; + } + default: + mRect.height = aHeight; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGRect.h b/dom/svg/SVGRect.h new file mode 100644 index 0000000000..924dc0ba9c --- /dev/null +++ b/dom/svg/SVGRect.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGRECT_H_ +#define DOM_SVG_SVGRECT_H_ + +#include "mozilla/dom/SVGElement.h" +#include "mozilla/gfx/Rect.h" + +//////////////////////////////////////////////////////////////////////// +// SVGRect class + +namespace mozilla::dom { + +class SVGSVGElement; + +class SVGRect final : public nsWrapperCache { + public: + enum class RectType : uint8_t { BaseValue, AnimValue, CreatedValue }; + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SVGRect) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(SVGRect) + + /** + * Generic ctor for objects that are created for an attribute. + */ + SVGRect(SVGAnimatedViewBox* aVal, SVGElement* aSVGElement, RectType aType) + : mVal(aVal), mParent(aSVGElement), mType(aType) { + MOZ_ASSERT(mParent); + MOZ_ASSERT(mType == RectType::BaseValue || mType == RectType::AnimValue); + } + + /** + * Ctor for creating the objects returned by SVGSVGElement.createSVGRect(), + * which do not initially belong to an attribute. + */ + explicit SVGRect(SVGSVGElement* aSVGElement); + + /** + * Ctor for all other non-attribute usage i.e getBBox, getExtentOfChar etc. + */ + SVGRect(nsIContent* aParent, const gfx::Rect& aRect) + : mVal(nullptr), + mRect(aRect), + mParent(aParent), + mType(RectType::CreatedValue) { + MOZ_ASSERT(mParent); + } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + float X(); + float Y(); + float Width(); + float Height(); + + void SetX(float aX, mozilla::ErrorResult& aRv); + void SetY(float aY, mozilla::ErrorResult& aRv); + void SetWidth(float aWidth, mozilla::ErrorResult& aRv); + void SetHeight(float aHeight, mozilla::ErrorResult& aRv); + + nsIContent* GetParentObject() const { + MOZ_ASSERT(mParent); + return mParent; + } + + private: + virtual ~SVGRect(); + + // If we're actually representing a viewBox rect then our value + // will come from that element's viewBox attribute's value. + SVGAnimatedViewBox* mVal; // kept alive because it belongs to content + gfx::Rect mRect; + + // If mType is AnimValue or BaseValue this will be an element that + // has a viewBox, otherwise it could be any nsIContent. + RefPtr<nsIContent> mParent; + const RectType mType; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGRECT_H_ diff --git a/dom/svg/SVGRectElement.cpp b/dom/svg/SVGRectElement.cpp new file mode 100644 index 0000000000..9f40acff26 --- /dev/null +++ b/dom/svg/SVGRectElement.cpp @@ -0,0 +1,280 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGRectElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGRectElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/PathHelpers.h" +#include "nsGkAtoms.h" +#include "SVGGeometryProperty.h" +#include <algorithm> + +NS_IMPL_NS_NEW_SVG_ELEMENT(Rect) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +class DOMSVGAnimatedLength; + +JSObject* SVGRectElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGRectElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGRectElement::sLengthInfo[6] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::rx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::ry, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGRectElement::SVGRectElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGRectElementBase(std::move(aNodeInfo)) {} + +bool SVGRectElement::IsAttributeMapped(const nsAtom* aAttribute) const { + return IsInLengthInfo(aAttribute, sLengthInfo) || + SVGRectElementBase::IsAttributeMapped(aAttribute); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGRectElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Rx() { + return mLengthAttributes[ATTR_RX].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Ry() { + return mLengthAttributes[ATTR_RY].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGRectElement::HasValidDimensions() const { + float width, height; + + if (SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>(this, &width, + &height)) { + return width > 0 && height > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() && + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 && + mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() && + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0; +} + +SVGElement::LengthAttributesInfo SVGRectElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGRectElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { + Rect rect; + Float rx, ry; + + DebugOnly<bool> ok = + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, + SVGT::Height, SVGT::Rx, SVGT::Ry>( + this, &rect.x, &rect.y, &rect.width, &rect.height, &rx, &ry); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (rect.IsEmpty()) { + // Rendering of the element disabled + rect.SetEmpty(); // Make sure width/height are zero and not negative + // We still want the x/y position from 'rect' + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; + } + + if (!aToBoundsSpace.IsRectilinear()) { + // We can't ignore the radii in this case if we want tight bounds + rx = std::max(rx, 0.0f); + ry = std::max(ry, 0.0f); + + if (rx != 0 || ry != 0) { + return false; + } + } + + if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + rect = aToNonScalingStrokeSpace->TransformBounds(rect); + // Note that, in principle, an author could cause the corners of the + // rect to be beveled by specifying stroke-linejoin or setting + // stroke-miterlimit to be less than sqrt(2). In that very unlikely + // event the bounds that we calculate here may be too big if + // aToBoundsSpace is non-rectilinear. This is likely to be so rare it's + // not worth handling though. + rect.Inflate(aStrokeOptions.mLineWidth / 2.f); + Matrix nonScalingToBounds = + aToNonScalingStrokeSpace->Inverse() * aToBoundsSpace; + *aBounds = nonScalingToBounds.TransformBounds(rect); + return true; + } + return false; + } + // The "beveled" comment above applies here too + rect.Inflate(aStrokeOptions.mLineWidth / 2.f); + } + + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; +} + +void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) { + float x, y, width, height, rx, ry; + + DebugOnly<bool> ok = + SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, + SVGT::Height, SVGT::Rx, SVGT::Ry>( + this, &x, &y, &width, &height, &rx, &ry); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (width <= 0 || height <= 0) { + aSimplePath->Reset(); + return; + } + + rx = std::max(rx, 0.0f); + ry = std::max(ry, 0.0f); + + if (rx != 0 || ry != 0) { + aSimplePath->Reset(); + return; + } + + aSimplePath->SetRect(x, y, width, height); +} + +already_AddRefed<Path> SVGRectElement::BuildPath(PathBuilder* aBuilder) { + float x, y, width, height, rx, ry; + + if (!SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, + SVGT::Height, SVGT::Rx, SVGT::Ry>( + this, &x, &y, &width, &height, &rx, &ry)) { + // This function might be called for element in display:none subtree + // (e.g. getTotalLength), we fall back to use SVG attributes. + GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr); + // If either the 'rx' or the 'ry' attribute isn't set, then we have to + // set it to the value of the other: + bool hasRx = mLengthAttributes[ATTR_RX].IsExplicitlySet(); + bool hasRy = mLengthAttributes[ATTR_RY].IsExplicitlySet(); + if (hasRx && !hasRy) { + ry = rx; + } else if (hasRy && !hasRx) { + rx = ry; + } + } + + if (width <= 0 || height <= 0) { + return nullptr; + } + + rx = std::max(rx, 0.0f); + ry = std::max(ry, 0.0f); + + if (rx == 0 && ry == 0) { + // Optimization for the no rounded corners case. + Rect r(x, y, width, height); + aBuilder->MoveTo(r.TopLeft()); + aBuilder->LineTo(r.TopRight()); + aBuilder->LineTo(r.BottomRight()); + aBuilder->LineTo(r.BottomLeft()); + aBuilder->Close(); + } else { + // Clamp rx and ry to half the rect's width and height respectively: + rx = std::min(rx, width / 2); + ry = std::min(ry, height / 2); + + RectCornerRadii radii(rx, ry); + AppendRoundedRectToPath(aBuilder, Rect(x, y, width, height), radii); + } + + return aBuilder->Finish(); +} + +bool SVGRectElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + const auto& newSVGReset = *aNewStyle.StyleSVGReset(); + const auto& oldSVGReset = *aOldStyle.StyleSVGReset(); + const auto& newPosition = *aNewStyle.StylePosition(); + const auto& oldPosition = *aOldStyle.StylePosition(); + return newSVGReset.mX != oldSVGReset.mX || newSVGReset.mY != oldSVGReset.mY || + newPosition.mWidth != oldPosition.mWidth || + newPosition.mHeight != oldPosition.mHeight || + newSVGReset.mRx != oldSVGReset.mRx || + newSVGReset.mRy != oldSVGReset.mRy; +} + +nsCSSPropertyID SVGRectElement::GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_X: + return eCSSProperty_x; + case ATTR_Y: + return eCSSProperty_y; + case ATTR_WIDTH: + return eCSSProperty_width; + case ATTR_HEIGHT: + return eCSSProperty_height; + case ATTR_RX: + return eCSSProperty_rx; + case ATTR_RY: + return eCSSProperty_ry; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGRectElement.h b/dom/svg/SVGRectElement.h new file mode 100644 index 0000000000..7cf4ebc4e1 --- /dev/null +++ b/dom/svg/SVGRectElement.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGRECTELEMENT_H_ +#define DOM_SVG_SVGRECTELEMENT_H_ + +#include "nsCSSPropertyID.h" +#include "SVGAnimatedLength.h" +#include "SVGGeometryElement.h" + +nsresult NS_NewSVGRectElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class ComputedStyle; + +namespace dom { + +using SVGRectElementBase = SVGGeometryElement; + +class SVGRectElement final : public SVGRectElementBase { + protected: + explicit SVGRectElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + friend nsresult(::NS_NewSVGRectElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + public: + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + void GetAsSimplePath(SimplePath* aSimplePath) override; + already_AddRefed<Path> BuildPath(PathBuilder* aBuilder = nullptr) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Rx(); + already_AddRefed<DOMSVGAnimatedLength> Ry(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT, ATTR_RX, ATTR_RY }; + SVGAnimatedLength mLengthAttributes[6]; + static LengthInfo sLengthInfo[6]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGRECTELEMENT_H_ diff --git a/dom/svg/SVGSVGElement.cpp b/dom/svg/SVGSVGElement.cpp new file mode 100644 index 0000000000..dc7cdd4f60 --- /dev/null +++ b/dom/svg/SVGSVGElement.cpp @@ -0,0 +1,587 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSVGElement.h" + +#include "mozilla/ContentEvents.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/DOMMatrix.h" +#include "mozilla/dom/SVGSVGElementBinding.h" +#include "mozilla/dom/SVGMatrix.h" +#include "mozilla/dom/SVGRect.h" +#include "mozilla/dom/SVGViewElement.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/PresShell.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/SMILTimeContainer.h" +#include "mozilla/SVGUtils.h" + +#include "DOMSVGAngle.h" +#include "DOMSVGLength.h" +#include "DOMSVGNumber.h" +#include "DOMSVGPoint.h" +#include "nsFrameSelection.h" +#include "nsIFrame.h" +#include "ISVGSVGFrame.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(SVG) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +using namespace SVGPreserveAspectRatio_Binding; +using namespace SVGSVGElement_Binding; + +SVGEnumMapping SVGSVGElement::sZoomAndPanMap[] = { + {nsGkAtoms::disable, SVG_ZOOMANDPAN_DISABLE}, + {nsGkAtoms::magnify, SVG_ZOOMANDPAN_MAGNIFY}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGSVGElement::sEnumInfo[1] = { + {nsGkAtoms::zoomAndPan, sZoomAndPanMap, SVG_ZOOMANDPAN_MAGNIFY}}; + +JSObject* SVGSVGElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGSVGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGSVGElement) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGSVGElement, + SVGSVGElementBase) + if (tmp->mTimedDocumentRoot) { + tmp->mTimedDocumentRoot->Unlink(); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGSVGElement, + SVGSVGElementBase) + if (tmp->mTimedDocumentRoot) { + tmp->mTimedDocumentRoot->Traverse(&cb); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGSVGElement) + NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGSVGElement) +NS_INTERFACE_MAP_END_INHERITING(SVGSVGElementBase); + +NS_IMPL_ADDREF_INHERITED(SVGSVGElement, SVGSVGElementBase) +NS_IMPL_RELEASE_INHERITED(SVGSVGElement, SVGSVGElementBase) + +SVGView::SVGView() { + mZoomAndPan.Init(SVGSVGElement::ZOOMANDPAN, SVG_ZOOMANDPAN_MAGNIFY); + mViewBox.Init(); + mPreserveAspectRatio.Init(); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGSVGElement::SVGSVGElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser) + : SVGSVGElementBase(std::move(aNodeInfo)), + mCurrentTranslate(0.0f, 0.0f), + mCurrentScale(1.0f), + mStartAnimationOnBindToTree(aFromParser == NOT_FROM_PARSER || + aFromParser == FROM_PARSER_FRAGMENT || + aFromParser == FROM_PARSER_XSLT), + mImageNeedsTransformInvalidation(false) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT_AND_PARSER(SVGSVGElement) + +//---------------------------------------------------------------------- +// nsIDOMSVGSVGElement methods: + +already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +bool SVGSVGElement::UseCurrentView() const { + return mSVGView || mCurrentViewID; +} + +float SVGSVGElement::CurrentScale() const { return mCurrentScale; } + +#define CURRENT_SCALE_MAX 16.0f +#define CURRENT_SCALE_MIN 0.0625f + +void SVGSVGElement::SetCurrentScale(float aCurrentScale) { + // Prevent bizarre behaviour and maxing out of CPU and memory by clamping + aCurrentScale = clamped(aCurrentScale, CURRENT_SCALE_MIN, CURRENT_SCALE_MAX); + + if (aCurrentScale == mCurrentScale) { + return; + } + mCurrentScale = aCurrentScale; + + if (IsRootSVGSVGElement()) { + InvalidateTransformNotifyFrame(); + } +} + +already_AddRefed<DOMSVGPoint> SVGSVGElement::CurrentTranslate() { + return DOMSVGPoint::GetTranslateTearOff(&mCurrentTranslate, this); +} + +uint32_t SVGSVGElement::SuspendRedraw(uint32_t max_wait_milliseconds) { + // suspendRedraw is a no-op in Mozilla, so it doesn't matter what + // we return + return 1; +} + +void SVGSVGElement::UnsuspendRedraw(uint32_t suspend_handle_id) { + // no-op +} + +void SVGSVGElement::UnsuspendRedrawAll() { + // no-op +} + +void SVGSVGElement::ForceRedraw() { + // no-op +} + +void SVGSVGElement::PauseAnimations() { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->Pause(SMILTimeContainer::PAUSE_SCRIPT); + } + // else we're not the outermost <svg> or not bound to a tree, so silently fail +} + +void SVGSVGElement::UnpauseAnimations() { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->Resume(SMILTimeContainer::PAUSE_SCRIPT); + } + // else we're not the outermost <svg> or not bound to a tree, so silently fail +} + +bool SVGSVGElement::AnimationsPaused() { + SMILTimeContainer* root = GetTimedDocumentRoot(); + return root && root->IsPausedByType(SMILTimeContainer::PAUSE_SCRIPT); +} + +float SVGSVGElement::GetCurrentTimeAsFloat() { + SMILTimeContainer* root = GetTimedDocumentRoot(); + if (root) { + double fCurrentTimeMs = double(root->GetCurrentTimeAsSMILTime()); + return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC); + } + return 0.f; +} + +void SVGSVGElement::SetCurrentTime(float seconds) { + if (mTimedDocumentRoot) { + // Make sure the timegraph is up-to-date + FlushAnimations(); + double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC; + // Round to nearest whole number before converting, to avoid precision + // errors + SMILTime lMilliseconds = SVGUtils::ClampToInt64(NS_round(fMilliseconds)); + mTimedDocumentRoot->SetCurrentTime(lMilliseconds); + AnimationNeedsResample(); + // Trigger synchronous sample now, to: + // - Make sure we get an up-to-date paint after this method + // - re-enable event firing (it got disabled during seeking, and it + // doesn't get re-enabled until the first sample after the seek -- so + // let's make that happen now.) + FlushAnimations(); + } + // else we're not the outermost <svg> or not bound to a tree, so silently fail +} + +void SVGSVGElement::DeselectAll() { + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + RefPtr<nsFrameSelection> frameSelection = frame->GetFrameSelection(); + frameSelection->ClearNormalSelection(); + } +} + +already_AddRefed<DOMSVGNumber> SVGSVGElement::CreateSVGNumber() { + return do_AddRef(new DOMSVGNumber(this)); +} + +already_AddRefed<DOMSVGLength> SVGSVGElement::CreateSVGLength() { + return do_AddRef(new DOMSVGLength()); +} + +already_AddRefed<DOMSVGAngle> SVGSVGElement::CreateSVGAngle() { + return do_AddRef(new DOMSVGAngle(this)); +} + +already_AddRefed<DOMSVGPoint> SVGSVGElement::CreateSVGPoint() { + return do_AddRef(new DOMSVGPoint(Point(0, 0))); +} + +already_AddRefed<SVGMatrix> SVGSVGElement::CreateSVGMatrix() { + return do_AddRef(new SVGMatrix()); +} + +already_AddRefed<SVGRect> SVGSVGElement::CreateSVGRect() { + return do_AddRef(new SVGRect(this)); +} + +already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransform() { + return do_AddRef(new DOMSVGTransform()); +} + +already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransformFromMatrix( + const DOMMatrix2DInit& matrix, ErrorResult& rv) { + return do_AddRef(new DOMSVGTransform(matrix, rv)); +} + +void SVGSVGElement::DidChangeTranslate() { + if (Document* doc = GetUncomposedDoc()) { + RefPtr<PresShell> presShell = doc->GetPresShell(); + // now dispatch the appropriate event if we are the root element + if (presShell && IsRootSVGSVGElement()) { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent svgScrollEvent(true, eSVGScroll); + presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status); + InvalidateTransformNotifyFrame(); + } + } +} + +//---------------------------------------------------------------------- +// SVGZoomAndPanValues +uint16_t SVGSVGElement::ZoomAndPan() const { + return mEnumAttributes[ZOOMANDPAN].GetAnimValue(); +} + +void SVGSVGElement::SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv) { + if (aZoomAndPan == SVG_ZOOMANDPAN_DISABLE || + aZoomAndPan == SVG_ZOOMANDPAN_MAGNIFY) { + ErrorResult nestedRv; + mEnumAttributes[ZOOMANDPAN].SetBaseValue(aZoomAndPan, this, nestedRv); + MOZ_ASSERT(!nestedRv.Failed(), + "We already validated our aZoomAndPan value!"); + return; + } + + rv.ThrowRangeError<MSG_INVALID_ZOOMANDPAN_VALUE_ERROR>(); +} + +//---------------------------------------------------------------------- +SMILTimeContainer* SVGSVGElement::GetTimedDocumentRoot() { + if (mTimedDocumentRoot) { + return mTimedDocumentRoot.get(); + } + + // We must not be the outermost <svg> element, try to find it + SVGSVGElement* outerSVGElement = SVGContentUtils::GetOuterSVGElement(this); + + if (outerSVGElement) { + return outerSVGElement->GetTimedDocumentRoot(); + } + // invalid structure + return nullptr; +} +//---------------------------------------------------------------------- +// SVGElement +nsresult SVGSVGElement::BindToTree(BindContext& aContext, nsINode& aParent) { + SMILAnimationController* smilController = nullptr; + + if (Document* doc = aContext.GetComposedDoc()) { + if ((smilController = doc->GetAnimationController())) { + // SMIL is enabled in this document + if (WillBeOutermostSVG(aParent)) { + // We'll be the outermost <svg> element. We'll need a time container. + if (!mTimedDocumentRoot) { + mTimedDocumentRoot = MakeUnique<SMILTimeContainer>(); + } + } else { + // We're a child of some other <svg> element, so we don't need our own + // time container. However, we need to make sure that we'll get a + // kick-start if we get promoted to be outermost later on. + mTimedDocumentRoot = nullptr; + mStartAnimationOnBindToTree = true; + } + } + } + + nsresult rv = SVGGraphicsElement::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (mTimedDocumentRoot && smilController) { + rv = mTimedDocumentRoot->SetParent(smilController); + if (mStartAnimationOnBindToTree) { + mTimedDocumentRoot->Begin(); + mStartAnimationOnBindToTree = false; + } + } + + return rv; +} + +void SVGSVGElement::UnbindFromTree(bool aNullParent) { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->SetParent(nullptr); + } + + SVGGraphicsElement::UnbindFromTree(aNullParent); +} + +SVGAnimatedTransformList* SVGSVGElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!(aFlags & DO_ALLOCATE) && mSVGView && mSVGView->mTransforms) { + return mSVGView->mTransforms.get(); + } + return SVGGraphicsElement::GetAnimatedTransformList(aFlags); +} + +void SVGSVGElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + if (aVisitor.mEvent->mMessage == eSVGLoad) { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->Begin(); + // Set 'resample needed' flag, so that if any script calls a DOM method + // that requires up-to-date animations before our first sample callback, + // we'll force a synchronous sample. + AnimationNeedsResample(); + } + } + SVGSVGElementBase::GetEventTargetParent(aVisitor); +} + +bool SVGSVGElement::IsEventAttributeNameInternal(nsAtom* aName) { + /* The events in EventNameType_SVGSVG are for events that are only + applicable to outermost 'svg' elements. We don't check if we're an outer + 'svg' element in case we're not inserted into the document yet, but since + the target of the events in question will always be the outermost 'svg' + element, this shouldn't cause any real problems. + */ + return nsContentUtils::IsEventAttributeName( + aName, (EventNameType_SVGGraphic | EventNameType_SVGSVG)); +} + +//---------------------------------------------------------------------- +// public helpers: + +int32_t SVGSVGElement::GetIntrinsicWidth() { + if (mLengthAttributes[ATTR_WIDTH].IsPercentage()) { + return -1; + } + // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue + // that uses the passed argument as the context, but that's fine since we + // know the length isn't a percentage so the context won't be used (and we + // need to pass the element to be able to resolve em/ex units). + float width = mLengthAttributes[ATTR_WIDTH].GetAnimValue(this); + return SVGUtils::ClampToInt(width); +} + +int32_t SVGSVGElement::GetIntrinsicHeight() { + if (mLengthAttributes[ATTR_HEIGHT].IsPercentage()) { + return -1; + } + // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue + // that uses the passed argument as the context, but that's fine since we + // know the length isn't a percentage so the context won't be used (and we + // need to pass the element to be able to resolve em/ex units). + float height = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(this); + return SVGUtils::ClampToInt(height); +} + +void SVGSVGElement::FlushImageTransformInvalidation() { + MOZ_ASSERT(!GetParent(), "Should only be called on root node"); + MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(), + "Should only be called on image documents"); + + if (mImageNeedsTransformInvalidation) { + InvalidateTransformNotifyFrame(); + mImageNeedsTransformInvalidation = false; + } +} + +//---------------------------------------------------------------------- +// implementation helpers + +bool SVGSVGElement::WillBeOutermostSVG(nsINode& aParent) const { + nsINode* parent = &aParent; + while (parent && parent->IsSVGElement()) { + if (parent->IsSVGElement(nsGkAtoms::foreignObject)) { + // SVG in a foreignObject must have its own <svg> (SVGOuterSVGFrame). + return false; + } + if (parent->IsSVGElement(nsGkAtoms::svg)) { + return false; + } + parent = parent->GetParentOrShadowHostNode(); + } + + return true; +} + +void SVGSVGElement::InvalidateTransformNotifyFrame() { + ISVGSVGFrame* svgframe = do_QueryFrame(GetPrimaryFrame()); + // might fail this check if we've failed conditional processing + if (svgframe) { + svgframe->NotifyViewportOrTransformChanged( + ISVGDisplayableFrame::TRANSFORM_CHANGED); + } +} + +SVGElement::EnumAttributesInfo SVGSVGElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +void SVGSVGElement::SetImageOverridePreserveAspectRatio( + const SVGPreserveAspectRatio& aPAR) { + MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(), + "should only override preserveAspectRatio in images"); + + bool hasViewBox = HasViewBox(); + if (!hasViewBox && ShouldSynthesizeViewBox()) { + // My non-<svg:image> clients will have been painting me with a synthesized + // viewBox, but my <svg:image> client that's about to paint me now does NOT + // want that. Need to tell ourselves to flush our transform. + mImageNeedsTransformInvalidation = true; + } + + if (!hasViewBox) { + return; // preserveAspectRatio irrelevant (only matters if we have viewBox) + } + + if (SetPreserveAspectRatioProperty(aPAR)) { + mImageNeedsTransformInvalidation = true; + } +} + +void SVGSVGElement::ClearImageOverridePreserveAspectRatio() { + MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(), + "should only override image preserveAspectRatio in images"); + + if (!HasViewBox() && ShouldSynthesizeViewBox()) { + // My non-<svg:image> clients will want to paint me with a synthesized + // viewBox, but my <svg:image> client that just painted me did NOT + // use that. Need to tell ourselves to flush our transform. + mImageNeedsTransformInvalidation = true; + } + + if (ClearPreserveAspectRatioProperty()) { + mImageNeedsTransformInvalidation = true; + } +} + +bool SVGSVGElement::SetPreserveAspectRatioProperty( + const SVGPreserveAspectRatio& aPAR) { + SVGPreserveAspectRatio* pAROverridePtr = new SVGPreserveAspectRatio(aPAR); + nsresult rv = + SetProperty(nsGkAtoms::overridePreserveAspectRatio, pAROverridePtr, + nsINode::DeleteProperty<SVGPreserveAspectRatio>, true); + MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN, + "Setting override value when it's already set...?"); + + if (MOZ_UNLIKELY(NS_FAILED(rv))) { + // property-insertion failed (e.g. OOM in property-table code) + delete pAROverridePtr; + return false; + } + return true; +} + +const SVGPreserveAspectRatio* SVGSVGElement::GetPreserveAspectRatioProperty() + const { + void* valPtr = GetProperty(nsGkAtoms::overridePreserveAspectRatio); + if (valPtr) { + return static_cast<SVGPreserveAspectRatio*>(valPtr); + } + return nullptr; +} + +bool SVGSVGElement::ClearPreserveAspectRatioProperty() { + void* valPtr = TakeProperty(nsGkAtoms::overridePreserveAspectRatio); + bool didHaveProperty = !!valPtr; + delete static_cast<SVGPreserveAspectRatio*>(valPtr); + return didHaveProperty; +} + +SVGPreserveAspectRatio SVGSVGElement::GetPreserveAspectRatioWithOverride() + const { + Document* doc = GetUncomposedDoc(); + if (doc && doc->IsBeingUsedAsImage()) { + const SVGPreserveAspectRatio* pAROverridePtr = + GetPreserveAspectRatioProperty(); + if (pAROverridePtr) { + return *pAROverridePtr; + } + } + + SVGViewElement* viewElement = GetCurrentViewElement(); + + // This check is equivalent to "!HasViewBox() && + // ShouldSynthesizeViewBox()". We're just holding onto the viewElement that + // HasViewBox() would look up, so that we don't have to look it up again + // later. + if (!((viewElement && viewElement->mViewBox.HasRect()) || + (mSVGView && mSVGView->mViewBox.HasRect()) || mViewBox.HasRect()) && + ShouldSynthesizeViewBox()) { + // If we're synthesizing a viewBox, use preserveAspectRatio="none"; + return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE, + SVG_MEETORSLICE_SLICE); + } + + if (viewElement && viewElement->mPreserveAspectRatio.IsExplicitlySet()) { + return viewElement->mPreserveAspectRatio.GetAnimValue(); + } + if (mSVGView && mSVGView->mPreserveAspectRatio.IsExplicitlySet()) { + return mSVGView->mPreserveAspectRatio.GetAnimValue(); + } + return mPreserveAspectRatio.GetAnimValue(); +} + +SVGViewElement* SVGSVGElement::GetCurrentViewElement() const { + if (mCurrentViewID) { + // XXXsmaug It is unclear how this should work in case we're in Shadow DOM. + Document* doc = GetUncomposedDoc(); + if (doc) { + Element* element = doc->GetElementById(*mCurrentViewID); + if (element && element->IsSVGElement(nsGkAtoms::view)) { + return static_cast<SVGViewElement*>(element); + } + } + } + return nullptr; +} + +const SVGAnimatedViewBox& SVGSVGElement::GetViewBoxInternal() const { + SVGViewElement* viewElement = GetCurrentViewElement(); + + if (viewElement && viewElement->mViewBox.HasRect()) { + return viewElement->mViewBox; + } + if (mSVGView && mSVGView->mViewBox.HasRect()) { + return mSVGView->mViewBox; + } + + return mViewBox; +} + +SVGAnimatedTransformList* SVGSVGElement::GetTransformInternal() const { + return (mSVGView && mSVGView->mTransforms) ? mSVGView->mTransforms.get() + : mTransforms.get(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSVGElement.h b/dom/svg/SVGSVGElement.h new file mode 100644 index 0000000000..86a172d39a --- /dev/null +++ b/dom/svg/SVGSVGElement.h @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSVGELEMENT_H_ +#define DOM_SVG_SVGSVGELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGViewportElement.h" + +nsresult NS_NewSVGSVGElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +// {4b83982c-e5e9-4ca1-abd4-14d27e8b3531} +#define MOZILLA_SVGSVGELEMENT_IID \ + { \ + 0x4b83982c, 0xe5e9, 0x4ca1, { \ + 0xab, 0xd4, 0x14, 0xd2, 0x7e, 0x8b, 0x35, 0x31 \ + } \ + } + +namespace mozilla { +class AutoSVGViewHandler; +class SMILTimeContainer; +class SVGFragmentIdentifier; +class EventChainPreVisitor; + +namespace dom { +struct DOMMatrix2DInit; +class DOMSVGAngle; +class DOMSVGLength; +class DOMSVGNumber; +class DOMSVGPoint; +class SVGMatrix; +class SVGRect; +class SVGSVGElement; + +// Stores svgView arguments of SVG fragment identifiers. +class SVGView { + public: + SVGView(); + + SVGAnimatedEnumeration mZoomAndPan; + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + UniquePtr<SVGAnimatedTransformList> mTransforms; +}; + +using SVGSVGElementBase = SVGViewportElement; + +class SVGSVGElement final : public SVGSVGElementBase { + friend class mozilla::SVGFragmentIdentifier; + friend class mozilla::SVGOuterSVGFrame; + friend class mozilla::AutoSVGViewHandler; + friend class mozilla::AutoPreserveAspectRatioOverride; + friend class mozilla::dom::SVGView; + + protected: + SVGSVGElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser); + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + friend nsresult(::NS_NewSVGSVGElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser)); + + ~SVGSVGElement() = default; + + public: + // interfaces: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_SVGSVGELEMENT_IID) + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGSVGElement, SVGSVGElementBase) + + /* + * Send appropriate events and updates if our root translate + * has changed. + */ + MOZ_CAN_RUN_SCRIPT + void DidChangeTranslate(); + + // nsIContent interface + void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; + bool IsEventAttributeNameInternal(nsAtom* aName) override; + + // nsINode methods: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + bool UseCurrentView() const; + float CurrentScale() const; + void SetCurrentScale(float aCurrentScale); + already_AddRefed<DOMSVGPoint> CurrentTranslate(); + uint32_t SuspendRedraw(uint32_t max_wait_milliseconds); + void UnsuspendRedraw(uint32_t suspend_handle_id); + void UnsuspendRedrawAll(); + void ForceRedraw(); + void PauseAnimations(); + void UnpauseAnimations(); + bool AnimationsPaused(); + float GetCurrentTimeAsFloat(); + void SetCurrentTime(float seconds); + void DeselectAll(); + already_AddRefed<DOMSVGNumber> CreateSVGNumber(); + already_AddRefed<DOMSVGLength> CreateSVGLength(); + already_AddRefed<DOMSVGAngle> CreateSVGAngle(); + already_AddRefed<DOMSVGPoint> CreateSVGPoint(); + already_AddRefed<SVGMatrix> CreateSVGMatrix(); + already_AddRefed<SVGRect> CreateSVGRect(); + already_AddRefed<DOMSVGTransform> CreateSVGTransform(); + already_AddRefed<DOMSVGTransform> CreateSVGTransformFromMatrix( + const DOMMatrix2DInit& matrix, ErrorResult& rv); + using nsINode::GetElementById; // This does what we want + uint16_t ZoomAndPan() const; + void SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv); + + // SVGElement overrides + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + + // SVGSVGElement methods: + + // Returns true IFF our attributes are currently overridden by a <view> + // element and that element's ID matches the passed-in string. + bool IsOverriddenBy(const nsAString& aViewID) const { + return mCurrentViewID && mCurrentViewID->Equals(aViewID); + } + + SMILTimeContainer* GetTimedDocumentRoot(); + + // public helpers: + + const SVGPoint& GetCurrentTranslate() const { return mCurrentTranslate; } + bool IsScaledOrTranslated() const { + return mCurrentTranslate != SVGPoint() || mCurrentScale != 1.0f; + } + + /** + * Returns -1 if the width/height is a percentage, else returns the user unit + * length clamped to fit in a int32_t. + * XXX see bug 1112533 comment 3 - we should fix drawImage so that we can + * change these methods to make zero the error flag for percentages. + */ + int32_t GetIntrinsicWidth(); + int32_t GetIntrinsicHeight(); + + // This services any pending notifications for the transform on on this root + // <svg> node needing to be recalculated. (Only applicable in + // SVG-as-an-image documents.) + virtual void FlushImageTransformInvalidation(); + + private: + // SVGViewportElement methods: + + virtual SVGViewElement* GetCurrentViewElement() const; + SVGPreserveAspectRatio GetPreserveAspectRatioWithOverride() const override; + + // implementation helpers: + + /* + * While binding to the tree we need to determine if we will be the outermost + * <svg> element _before_ the children are bound (as they want to know what + * timed document root to register with) and therefore _before_ our parent is + * set (both actions are performed by Element::BindToTree) so we + * can't use GetOwnerSVGElement() as it relies on GetParent(). This code is + * basically a simplified version of GetOwnerSVGElement that uses the parent + * parameters passed in instead. + * + * FIXME(bug 1596690): GetOwnerSVGElement() uses the flattened tree parent + * rather than the DOM tree parent nowadays. + */ + bool WillBeOutermostSVG(nsINode& aParent) const; + + // invalidate viewbox -> viewport xform & inform frames + void InvalidateTransformNotifyFrame(); + + // Methods for <image> elements to override my "PreserveAspectRatio" value. + // These are private so that only our friends + // (AutoPreserveAspectRatioOverride in particular) have access. + void SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio& aPAR); + void ClearImageOverridePreserveAspectRatio(); + + // Set/Clear properties to hold old version of preserveAspectRatio + // when it's being overridden by an <image> element that we are inside of. + bool SetPreserveAspectRatioProperty(const SVGPreserveAspectRatio& aPAR); + const SVGPreserveAspectRatio* GetPreserveAspectRatioProperty() const; + bool ClearPreserveAspectRatioProperty(); + + const SVGAnimatedViewBox& GetViewBoxInternal() const override; + SVGAnimatedTransformList* GetTransformInternal() const override; + + EnumAttributesInfo GetEnumInfo() override; + + enum { ZOOMANDPAN }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sZoomAndPanMap[]; + static EnumInfo sEnumInfo[1]; + + // The time container for animations within this SVG document fragment. Set + // for all outermost <svg> elements (not nested <svg> elements). + UniquePtr<SMILTimeContainer> mTimedDocumentRoot; + + SVGPoint mCurrentTranslate; + float mCurrentScale; + + // For outermost <svg> elements created from parsing, animation is started by + // the onload event in accordance with the SVG spec, but for <svg> elements + // created by script or promoted from inner <svg> to outermost <svg> we need + // to manually kick off animation when they are bound to the tree. + bool mStartAnimationOnBindToTree; + + bool mImageNeedsTransformInvalidation; + + // mCurrentViewID and mSVGView are mutually exclusive; we can have + // at most one non-null. + UniquePtr<nsString> mCurrentViewID; + UniquePtr<SVGView> mSVGView; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGSVGElement, MOZILLA_SVGSVGELEMENT_IID) + +} // namespace dom + +class MOZ_RAII AutoSVGTimeSetRestore { + public: + AutoSVGTimeSetRestore(dom::SVGSVGElement* aRootElem, float aFrameTime) + : mRootElem(aRootElem), + mOriginalTime(mRootElem->GetCurrentTimeAsFloat()) { + mRootElem->SetCurrentTime( + aFrameTime); // Does nothing if there's no change. + } + + ~AutoSVGTimeSetRestore() { mRootElem->SetCurrentTime(mOriginalTime); } + + private: + const RefPtr<dom::SVGSVGElement> mRootElem; + const float mOriginalTime; +}; + +class MOZ_RAII AutoPreserveAspectRatioOverride { + public: + AutoPreserveAspectRatioOverride(const SVGImageContext& aSVGContext, + dom::SVGSVGElement* aRootElem) + : mRootElem(aRootElem), mDidOverride(false) { + MOZ_ASSERT(mRootElem, "No SVG/Symbol node to manage?"); + + if (aSVGContext.GetPreserveAspectRatio().isSome()) { + // Override preserveAspectRatio in our helper document. + // XXXdholbert We should technically be overriding the helper doc's clip + // and overflow properties here, too. See bug 272288 comment 36. + mRootElem->SetImageOverridePreserveAspectRatio( + *aSVGContext.GetPreserveAspectRatio()); + mDidOverride = true; + } + } + + ~AutoPreserveAspectRatioOverride() { + if (mDidOverride) { + mRootElem->ClearImageOverridePreserveAspectRatio(); + } + } + + private: + const RefPtr<dom::SVGSVGElement> mRootElem; + bool mDidOverride; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGSVGELEMENT_H_ diff --git a/dom/svg/SVGScriptElement.cpp b/dom/svg/SVGScriptElement.cpp new file mode 100644 index 0000000000..04d21f28ce --- /dev/null +++ b/dom/svg/SVGScriptElement.cpp @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGScriptElement.h" + +#include "nsGkAtoms.h" +#include "nsNetUtil.h" +#include "nsContentUtils.h" +#include "mozilla/dom/SVGScriptElementBinding.h" +#include "nsIScriptError.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(Script) + +namespace mozilla::dom { + +JSObject* SVGScriptElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGScriptElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGScriptElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, false}, + {nsGkAtoms::href, kNameSpaceID_XLink, false}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGScriptElement, SVGScriptElementBase, + nsIScriptLoaderObserver, nsIScriptElement, + nsIMutationObserver) + +//---------------------------------------------------------------------- +// Implementation + +SVGScriptElement::SVGScriptElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser) + : SVGScriptElementBase(std::move(aNodeInfo)), ScriptElement(aFromParser) { + AddMutationObserver(this); +} + +//---------------------------------------------------------------------- +// nsINode methods + +nsresult SVGScriptElement::Clone(dom::NodeInfo* aNodeInfo, + nsINode** aResult) const { + *aResult = nullptr; + + SVGScriptElement* it = new (aNodeInfo->NodeInfoManager()) + SVGScriptElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER); + + nsCOMPtr<nsINode> kungFuDeathGrip = it; + nsresult rv1 = it->Init(); + nsresult rv2 = const_cast<SVGScriptElement*>(this)->CopyInnerTo(it); + NS_ENSURE_SUCCESS(rv1, rv1); + NS_ENSURE_SUCCESS(rv2, rv2); + + // The clone should be marked evaluated if we are. + it->mAlreadyStarted = mAlreadyStarted; + it->mLineNumber = mLineNumber; + it->mMalformed = mMalformed; + + kungFuDeathGrip.swap(*aResult); + + return NS_OK; +} + +//---------------------------------------------------------------------- +void SVGScriptElement::GetType(nsAString& aType) { GetScriptType(aType); } + +void SVGScriptElement::SetType(const nsAString& aType, ErrorResult& rv) { + rv = SetAttr(kNameSpaceID_None, nsGkAtoms::type, aType, true); +} + +void SVGScriptElement::GetCrossOrigin(nsAString& aCrossOrigin) { + // Null for both missing and invalid defaults is ok, since we + // always parse to an enum value, so we don't need an invalid + // default, and we _want_ the missing default to be null. + GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aCrossOrigin); +} + +void SVGScriptElement::SetCrossOrigin(const nsAString& aCrossOrigin, + ErrorResult& aError) { + SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError); +} + +already_AddRefed<DOMSVGAnimatedString> SVGScriptElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIScriptElement methods + +bool SVGScriptElement::GetScriptType(nsAString& type) { + return GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); +} + +void SVGScriptElement::GetScriptText(nsAString& text) const { + nsContentUtils::GetNodeTextContent(this, false, text); +} + +void SVGScriptElement::GetScriptCharset(nsAString& charset) { + charset.Truncate(); +} + +void SVGScriptElement::FreezeExecutionAttrs(Document* aOwnerDoc) { + if (mFrozen) { + return; + } + + if (mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) { + // variation of this code in nsHTMLScriptElement - check if changes + // need to be transferred when modifying + bool isHref = false; + nsAutoString src; + if (mStringAttributes[HREF].IsExplicitlySet()) { + mStringAttributes[HREF].GetAnimValue(src, this); + isHref = true; + } else { + mStringAttributes[XLINK_HREF].GetAnimValue(src, this); + } + + // Empty src should be treated as invalid URL. + if (!src.IsEmpty()) { + NS_NewURI(getter_AddRefs(mUri), src, nullptr, GetBaseURI()); + + if (!mUri) { + AutoTArray<nsString, 2> params = { + isHref ? u"href"_ns : u"xlink:href"_ns, src}; + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "SVG"_ns, OwnerDoc(), + nsContentUtils::eDOM_PROPERTIES, "ScriptSourceInvalidUri", params, + nullptr, u""_ns, GetScriptLineNumber(), GetScriptColumnNumber()); + } + } else { + AutoTArray<nsString, 1> params = {isHref ? u"href"_ns : u"xlink:href"_ns}; + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "SVG"_ns, OwnerDoc(), + nsContentUtils::eDOM_PROPERTIES, "ScriptSourceEmpty", params, nullptr, + u""_ns, GetScriptLineNumber(), GetScriptColumnNumber()); + } + + // At this point mUri will be null for invalid URLs. + mExternal = true; + } + + mFrozen = true; +} + +//---------------------------------------------------------------------- +// ScriptElement methods + +bool SVGScriptElement::HasScriptContent() { + return (mFrozen ? mExternal + : mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) || + nsContentUtils::HasNonEmptyTextContent(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGScriptElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGScriptElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGScriptElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (IsInComposedDoc()) { + MaybeProcessScript(); + } + + return NS_OK; +} + +nsresult SVGScriptElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if ((aNamespaceID == kNameSpaceID_XLink || + aNamespaceID == kNameSpaceID_None) && + aName == nsGkAtoms::href) { + MaybeProcessScript(); + } + return SVGScriptElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +bool SVGScriptElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::crossorigin) { + ParseCORSValue(aValue, aResult); + return true; + } + + return SVGScriptElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +CORSMode SVGScriptElement::GetCORSMode() const { + return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGScriptElement.h b/dom/svg/SVGScriptElement.h new file mode 100644 index 0000000000..c33a8ae307 --- /dev/null +++ b/dom/svg/SVGScriptElement.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSCRIPTELEMENT_H_ +#define DOM_SVG_SVGSCRIPTELEMENT_H_ + +#include "SVGAnimatedString.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/ScriptElement.h" +#include "mozilla/dom/SVGElement.h" + +nsresult NS_NewSVGScriptElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +namespace mozilla::dom { + +using SVGScriptElementBase = SVGElement; + +class SVGScriptElement final : public SVGScriptElementBase, + public ScriptElement { + protected: + friend nsresult(::NS_NewSVGScriptElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser)); + SVGScriptElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + + // nsIScriptElement + bool GetScriptType(nsAString& type) override; + void GetScriptText(nsAString& text) const override; + void GetScriptCharset(nsAString& charset) override; + void FreezeExecutionAttrs(Document* aOwnerDoc) override; + CORSMode GetCORSMode() const override; + + // ScriptElement + bool HasScriptContent() override; + + // nsIContent specializations: + nsresult BindToTree(BindContext&, nsINode& aParent) override; + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + void GetType(nsAString& aType); + void SetType(const nsAString& aType, ErrorResult& rv); + void GetCrossOrigin(nsAString& aCrossOrigin); + void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError); + already_AddRefed<DOMSVGAnimatedString> Href(); + + protected: + ~SVGScriptElement() = default; + + StringAttributesInfo GetStringInfo() override; + + // SVG Script elements don't have the ability to set async properties on + // themselves, so this will always return false. + bool GetAsyncState() override { return false; } + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSCRIPTELEMENT_H_ diff --git a/dom/svg/SVGSetElement.cpp b/dom/svg/SVGSetElement.cpp new file mode 100644 index 0000000000..420f9e707c --- /dev/null +++ b/dom/svg/SVGSetElement.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSetElement.h" +#include "mozilla/dom/SVGSetElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Set) + +namespace mozilla::dom { + +JSObject* SVGSetElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGSetElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGSetElement::SVGSetElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGAnimationElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSetElement) + +//---------------------------------------------------------------------- + +SMILAnimationFunction& SVGSetElement::AnimationFunction() { + return mAnimationFunction; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSetElement.h b/dom/svg/SVGSetElement.h new file mode 100644 index 0000000000..7dc9bfb8d5 --- /dev/null +++ b/dom/svg/SVGSetElement.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSETELEMENT_H_ +#define DOM_SVG_SVGSETELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/SMILSetAnimationFunction.h" + +nsresult NS_NewSVGSetElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +class SVGSetElement final : public SVGAnimationElement { + protected: + explicit SVGSetElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + + SMILSetAnimationFunction mAnimationFunction; + + friend nsresult(::NS_NewSVGSetElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsINode + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGAnimationElement + SMILAnimationFunction& AnimationFunction() override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSETELEMENT_H_ diff --git a/dom/svg/SVGStopElement.cpp b/dom/svg/SVGStopElement.cpp new file mode 100644 index 0000000000..59a3bffb53 --- /dev/null +++ b/dom/svg/SVGStopElement.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGStopElement.h" +#include "mozilla/dom/SVGStopElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Stop) + +namespace mozilla::dom { + +JSObject* SVGStopElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGStopElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGStopElement::sNumberInfo = {nsGkAtoms::offset, 0, + true}; + +//---------------------------------------------------------------------- +// Implementation + +SVGStopElement::SVGStopElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGStopElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGStopElement) + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedNumber> SVGStopElement::Offset() { + return mOffset.ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// sSVGElement methods + +SVGElement::NumberAttributesInfo SVGStopElement::GetNumberInfo() { + return NumberAttributesInfo(&mOffset, &sNumberInfo, 1); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGStopElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sColorMap, + sGradientStopMap}; + + return FindAttributeDependence(name, map) || + SVGStopElementBase::IsAttributeMapped(name); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGStopElement.h b/dom/svg/SVGStopElement.h new file mode 100644 index 0000000000..6c55810b0b --- /dev/null +++ b/dom/svg/SVGStopElement.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSTOPELEMENT_H_ +#define DOM_SVG_SVGSTOPELEMENT_H_ + +#include "mozilla/dom/SVGElement.h" +#include "SVGAnimatedNumber.h" + +nsresult NS_NewSVGStopElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGStopElementBase = SVGElement; + +class SVGStopElement final : public SVGStopElementBase { + protected: + friend nsresult(::NS_NewSVGStopElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGStopElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed<DOMSVGAnimatedNumber> Offset(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + SVGAnimatedNumber mOffset; + static NumberInfo sNumberInfo; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSTOPELEMENT_H_ diff --git a/dom/svg/SVGStringList.cpp b/dom/svg/SVGStringList.cpp new file mode 100644 index 0000000000..f0db815569 --- /dev/null +++ b/dom/svg/SVGStringList.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGStringList.h" +#include "nsError.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsWhitespaceTokenizer.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +nsresult SVGStringList::CopyFrom(const SVGStringList& rhs) { + if (!mStrings.Assign(rhs.mStrings, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + mIsSet = true; + return NS_OK; +} + +void SVGStringList::GetValue(nsAString& aValue) const { + aValue = StringJoin(mIsCommaSeparated ? u", "_ns : u" "_ns, mStrings); +} + +nsresult SVGStringList::SetValue(const nsAString& aValue) { + SVGStringList temp; + + if (mIsCommaSeparated) { + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + if (!temp.AppendItem(tokenizer.nextToken())) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + if (tokenizer.separatorAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + } else { + nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tokenizer( + aValue); + + while (tokenizer.hasMoreTokens()) { + if (!temp.AppendItem(tokenizer.nextToken())) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + return CopyFrom(temp); +} + +} // namespace mozilla diff --git a/dom/svg/SVGStringList.h b/dom/svg/SVGStringList.h new file mode 100644 index 0000000000..cedf42aa60 --- /dev/null +++ b/dom/svg/SVGStringList.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSTRINGLIST_H_ +#define DOM_SVG_SVGSTRINGLIST_H_ + +#include "nsDebug.h" +#include "nsTArray.h" +#include "nsString.h" // IWYU pragma: keep + +namespace mozilla { + +namespace dom { +class DOMSVGStringList; +} + +/** + * + * The DOM wrapper class for this class is DOMSVGStringList. + */ +class SVGStringList { + friend class dom::DOMSVGStringList; + + public: + SVGStringList() : mIsSet(false), mIsCommaSeparated(false) {} + ~SVGStringList() = default; + + void SetIsCommaSeparated(bool aIsCommaSeparated) { + mIsCommaSeparated = aIsCommaSeparated; + } + nsresult SetValue(const nsAString& aValue); + + void Clear() { + mStrings.Clear(); + mIsSet = false; + } + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValue(nsAString& aValue) const; + + bool IsEmpty() const { return mStrings.IsEmpty(); } + + uint32_t Length() const { return mStrings.Length(); } + + const nsAString& operator[](uint32_t aIndex) const { + return mStrings[aIndex]; + } + + bool operator==(const SVGStringList& rhs) const { + return mStrings == rhs.mStrings; + } + + bool SetCapacity(uint32_t size) { + return mStrings.SetCapacity(size, fallible); + } + + void Compact() { mStrings.Compact(); } + + // Returns true if the value of this stringlist has been explicitly + // set by markup or a DOM call, false otherwise. + bool IsExplicitlySet() const { return mIsSet; } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // DOMSVGAnimatedStringList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGStringList& rhs); + + nsAString& operator[](uint32_t aIndex) { return mStrings[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aStringOfItems) { + return mStrings.SetLength(aStringOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + bool InsertItem(uint32_t aIndex, const nsAString& aString) { + if (aIndex >= mStrings.Length()) { + aIndex = mStrings.Length(); + } + if (mStrings.InsertElementAt(aIndex, aString, fallible)) { + mIsSet = true; + return true; + } + return false; + } + + void ReplaceItem(uint32_t aIndex, const nsAString& aString) { + MOZ_ASSERT(aIndex < mStrings.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mStrings[aIndex] = aString; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mStrings.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mStrings.RemoveElementAt(aIndex); + } + + bool AppendItem(const nsAString& aString) { + if (mStrings.AppendElement(aString, fallible)) { + mIsSet = true; + return true; + } + return false; + } + + protected: + /* See SVGLengthList for the rationale for using FallibleTArray<float> instead + * of FallibleTArray<float, 1>. + */ + FallibleTArray<nsString> mStrings; + bool mIsSet; + bool mIsCommaSeparated; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGSTRINGLIST_H_ diff --git a/dom/svg/SVGStyleElement.cpp b/dom/svg/SVGStyleElement.cpp new file mode 100644 index 0000000000..08041522f7 --- /dev/null +++ b/dom/svg/SVGStyleElement.cpp @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGStyleElement.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/SVGStyleElementBinding.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Style) + +namespace mozilla::dom { + +JSObject* SVGStyleElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGStyleElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGStyleElement, + SVGStyleElementBase, + nsIMutationObserver) + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGStyleElement) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGStyleElement, + SVGStyleElementBase) + tmp->LinkStyle::Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGStyleElement, + SVGStyleElementBase) + tmp->LinkStyle::Unlink(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +//---------------------------------------------------------------------- +// Implementation + +SVGStyleElement::SVGStyleElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGStyleElementBase(std::move(aNodeInfo)) { + AddMutationObserver(this); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGStyleElement) + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGStyleElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGStyleElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + void (SVGStyleElement::*update)() = + &SVGStyleElement::UpdateStyleSheetInternal; + nsContentUtils::AddScriptRunner( + NewRunnableMethod("dom::SVGStyleElement::BindToTree", this, update)); + + return rv; +} + +void SVGStyleElement::UnbindFromTree(bool aNullParent) { + nsCOMPtr<Document> oldDoc = GetUncomposedDoc(); + ShadowRoot* oldShadow = GetContainingShadow(); + SVGStyleElementBase::UnbindFromTree(aNullParent); + Unused << UpdateStyleSheetInternal(oldDoc, oldShadow); +} + +nsresult SVGStyleElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + if (aNameSpaceID == kNameSpaceID_None) { + if (aName == nsGkAtoms::title || aName == nsGkAtoms::media || + aName == nsGkAtoms::type) { + Unused << UpdateStyleSheetInternal(nullptr, nullptr, ForceUpdate::Yes); + } + } + + return SVGStyleElementBase::AfterSetAttr( + aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); +} + +bool SVGStyleElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::crossorigin) { + ParseCORSValue(aValue, aResult); + return true; + } + + return SVGStyleElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +//---------------------------------------------------------------------- +// nsIMutationObserver methods + +void SVGStyleElement::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + ContentChanged(aContent); +} + +void SVGStyleElement::ContentAppended(nsIContent* aFirstNewContent) { + ContentChanged(aFirstNewContent->GetParent()); +} + +void SVGStyleElement::ContentInserted(nsIContent* aChild) { + ContentChanged(aChild); +} + +void SVGStyleElement::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + ContentChanged(aChild); +} + +void SVGStyleElement::ContentChanged(nsIContent* aContent) { + if (nsContentUtils::IsInSameAnonymousTree(this, aContent)) { + Unused << UpdateStyleSheetInternal(nullptr, nullptr); + } +} + +//---------------------------------------------------------------------- + +void SVGStyleElement::GetMedia(nsAString& aMedia) { + GetAttr(nsGkAtoms::media, aMedia); +} + +void SVGStyleElement::SetMedia(const nsAString& aMedia, ErrorResult& rv) { + SetAttr(nsGkAtoms::media, aMedia, rv); +} + +void SVGStyleElement::GetType(nsAString& aType) { + GetAttr(nsGkAtoms::type, aType); +} + +void SVGStyleElement::SetType(const nsAString& aType, ErrorResult& rv) { + SetAttr(nsGkAtoms::type, aType, rv); +} + +void SVGStyleElement::GetTitle(nsAString& aTitle) { + GetAttr(nsGkAtoms::title, aTitle); +} + +void SVGStyleElement::SetTitle(const nsAString& aTitle, ErrorResult& rv) { + SetAttr(nsGkAtoms::title, aTitle, rv); +} + +bool SVGStyleElement::Disabled() const { + StyleSheet* ss = GetSheet(); + return ss && ss->Disabled(); +} + +void SVGStyleElement::SetDisabled(bool aDisabled) { + if (StyleSheet* ss = GetSheet()) { + ss->SetDisabled(aDisabled); + } +} + +//---------------------------------------------------------------------- +// nsStyleLinkElement methods + +Maybe<LinkStyle::SheetInfo> SVGStyleElement::GetStyleSheetInfo() { + if (!IsCSSMimeTypeAttributeForStyleElement(*this)) { + return Nothing(); + } + + nsAutoString title; + nsAutoString media; + GetTitleAndMediaForElement(*this, title, media); + + return Some(SheetInfo{ + *OwnerDoc(), + this, + nullptr, + // FIXME(bug 1459822): Why doesn't this need a principal, but + // HTMLStyleElement does? + nullptr, + // FIXME(bug 1459822): Why does this need a crossorigin attribute, but + // HTMLStyleElement doesn't? + MakeAndAddRef<ReferrerInfo>(*this), + AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)), + title, + media, + /* integrity = */ u""_ns, + /* nsStyleUtil::CSPAllowsInlineStyle takes care of nonce checking for + inline styles. Bug 1607011 */ + /* nonce = */ u""_ns, + HasAlternateRel::No, + IsInline::Yes, + IsExplicitlyEnabled::No, + }); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGStyleElement.h b/dom/svg/SVGStyleElement.h new file mode 100644 index 0000000000..443968a984 --- /dev/null +++ b/dom/svg/SVGStyleElement.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSTYLEELEMENT_H_ +#define DOM_SVG_SVGSTYLEELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/LinkStyle.h" +#include "SVGElement.h" +#include "nsStubMutationObserver.h" + +nsresult NS_NewSVGStyleElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGStyleElementBase = SVGElement; + +class SVGStyleElement final : public SVGStyleElementBase, + public nsStubMutationObserver, + public LinkStyle { + protected: + friend nsresult(::NS_NewSVGStyleElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGStyleElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + ~SVGStyleElement() = default; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGStyleElement, SVGStyleElementBase) + + // nsIContent + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent = true) override; + virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) override; + virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + // WebIDL + bool Disabled() const; + void SetDisabled(bool aDisabled); + void GetMedia(nsAString& aMedia); + void SetMedia(const nsAString& aMedia, ErrorResult& rv); + void GetType(nsAString& aType); + void SetType(const nsAString& aType, ErrorResult& rv); + void GetTitle(nsAString& aTitle); + void SetTitle(const nsAString& aTitle, ErrorResult& rv); + + protected: + // Dummy init method to make the NS_IMPL_NS_NEW_SVG_ELEMENT and + // NS_IMPL_ELEMENT_CLONE_WITH_INIT usable with this class. This should be + // completely optimized away. + inline nsresult Init() { return NS_OK; } + + // LinkStyle overrides + nsIContent& AsContent() final { return *this; } + const LinkStyle* AsLinkStyle() const final { return this; } + Maybe<SheetInfo> GetStyleSheetInfo() final; + + /** + * Common method to call from the various mutation observer methods. + * aContent is a content node that's either the one that changed or its + * parent; we should only respond to the change if aContent is non-anonymous. + */ + void ContentChanged(nsIContent* aContent); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSTYLEELEMENT_H_ diff --git a/dom/svg/SVGSwitchElement.cpp b/dom/svg/SVGSwitchElement.cpp new file mode 100644 index 0000000000..32ed893dfe --- /dev/null +++ b/dom/svg/SVGSwitchElement.cpp @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSwitchElement.h" + +#include "nsLayoutUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGSwitchElementBinding.h" + +class nsIFrame; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Switch) + +namespace mozilla::dom { + +JSObject* SVGSwitchElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGSwitchElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGSwitchElement, SVGSwitchElementBase, + mActiveChild) + +NS_IMPL_ADDREF_INHERITED(SVGSwitchElement, SVGSwitchElementBase) +NS_IMPL_RELEASE_INHERITED(SVGSwitchElement, SVGSwitchElementBase) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGSwitchElement) +NS_INTERFACE_MAP_END_INHERITING(SVGSwitchElementBase) + +//---------------------------------------------------------------------- +// Implementation + +SVGSwitchElement::SVGSwitchElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGSwitchElementBase(std::move(aNodeInfo)) {} + +void SVGSwitchElement::MaybeInvalidate() { + // We must not change mActiveChild until after + // InvalidateAndScheduleBoundsUpdate has been called, otherwise + // it will not correctly invalidate the old mActiveChild area. + + nsIContent* newActiveChild = FindActiveChild(); + + if (newActiveChild == mActiveChild) { + return; + } + + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(frame); + } + + mActiveChild = newActiveChild; +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSwitchElement) + +//---------------------------------------------------------------------- +// nsINode methods + +void SVGSwitchElement::InsertChildBefore(nsIContent* aKid, + nsIContent* aBeforeThis, bool aNotify, + ErrorResult& aRv) { + SVGSwitchElementBase::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv); + if (aRv.Failed()) { + return; + } + + MaybeInvalidate(); +} + +void SVGSwitchElement::RemoveChildNode(nsIContent* aKid, bool aNotify) { + SVGSwitchElementBase::RemoveChildNode(aKid, aNotify); + MaybeInvalidate(); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGSwitchElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return FindAttributeDependence(name, map) || + SVGSwitchElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// Implementation Helpers: + +nsIContent* SVGSwitchElement::FindActiveChild() const { + nsAutoString acceptLangs; + Preferences::GetLocalizedString("intl.accept_languages", acceptLangs); + + int32_t bestLanguagePreferenceRank = -1; + nsIContent* bestChild = nullptr; + nsIContent* defaultChild = nullptr; + for (nsIContent* child = nsINode::GetFirstChild(); child; + child = child->GetNextSibling()) { + if (!child->IsElement()) { + continue; + } + nsCOMPtr<SVGTests> tests(do_QueryInterface(child)); + if (tests) { + if (tests->PassesConditionalProcessingTestsIgnoringSystemLanguage()) { + int32_t languagePreferenceRank = + tests->GetBestLanguagePreferenceRank(acceptLangs); + switch (languagePreferenceRank) { + case 0: + // best possible match + return child; + case -1: + // no match + break; + case -2: + // no systemLanguage attribute. If there's nothing better + // we'll use the first such child. + if (!defaultChild) { + defaultChild = child; + } + break; + default: + if (bestLanguagePreferenceRank == -1 || + languagePreferenceRank < bestLanguagePreferenceRank) { + bestLanguagePreferenceRank = languagePreferenceRank; + bestChild = child; + } + break; + } + } + } else if (!bestChild) { + bestChild = child; + } + } + return bestChild ? bestChild : defaultChild; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSwitchElement.h b/dom/svg/SVGSwitchElement.h new file mode 100644 index 0000000000..4cfadb9a59 --- /dev/null +++ b/dom/svg/SVGSwitchElement.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSWITCHELEMENT_H_ +#define DOM_SVG_SVGSWITCHELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "nsCOMPtr.h" + +nsresult NS_NewSVGSwitchElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class ErrorResult; +namespace dom { + +using SVGSwitchElementBase = SVGGraphicsElement; + +class SVGSwitchElement final : public SVGSwitchElementBase { + protected: + friend nsresult(::NS_NewSVGSwitchElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGSwitchElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + ~SVGSwitchElement() = default; + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + nsIContent* GetActiveChild() const { return mActiveChild; } + void MaybeInvalidate(); + + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGSwitchElement, + SVGSwitchElementBase) + // nsINode + virtual void InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis, + bool aNotify, ErrorResult& aRv) override; + void RemoveChildNode(nsIContent* aKid, bool aNotify) override; + + // nsIContent + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + private: + void UpdateActiveChild() { mActiveChild = FindActiveChild(); } + nsIContent* FindActiveChild() const; + + // only this child will be displayed + nsCOMPtr<nsIContent> mActiveChild; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGSWITCHELEMENT_H_ diff --git a/dom/svg/SVGSymbolElement.cpp b/dom/svg/SVGSymbolElement.cpp new file mode 100644 index 0000000000..d1e18b8923 --- /dev/null +++ b/dom/svg/SVGSymbolElement.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSymbolElement.h" +#include "mozilla/dom/SVGSymbolElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Symbol) + +namespace mozilla::dom { + +JSObject* SVGSymbolElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGSymbolElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGSymbolElement, SVGSymbolElementBase, + mozilla::dom::SVGTests) + +//---------------------------------------------------------------------- +// Implementation + +SVGSymbolElement::SVGSymbolElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGSymbolElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSymbolElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSymbolElement.h b/dom/svg/SVGSymbolElement.h new file mode 100644 index 0000000000..2e2b1558f6 --- /dev/null +++ b/dom/svg/SVGSymbolElement.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSYMBOLELEMENT_H_ +#define DOM_SVG_SVGSYMBOLELEMENT_H_ + +#include "SVGViewportElement.h" + +nsresult NS_NewSVGSymbolElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGSymbolElementBase = SVGViewportElement; + +class SVGSymbolElement final : public SVGSymbolElementBase { + protected: + friend nsresult(::NS_NewSVGSymbolElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGSymbolElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + ~SVGSymbolElement() = default; + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSYMBOLELEMENT_H_ diff --git a/dom/svg/SVGTSpanElement.cpp b/dom/svg/SVGTSpanElement.cpp new file mode 100644 index 0000000000..f441a67910 --- /dev/null +++ b/dom/svg/SVGTSpanElement.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTSpanElement.h" +#include "mozilla/dom/SVGTSpanElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(TSpan) + +namespace mozilla::dom { + +JSObject* SVGTSpanElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGTSpanElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGTSpanElement::SVGTSpanElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGTSpanElementBase(std::move(aNodeInfo)) {} + +SVGElement::EnumAttributesInfo SVGTSpanElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::LengthAttributesInfo SVGTSpanElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTSpanElement) + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGTSpanElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = { + sColorMap, sFillStrokeMap, sFontSpecificationMap, sGraphicsMap, + sTextContentElementsMap}; + + return FindAttributeDependence(name, map) || + SVGTSpanElementBase::IsAttributeMapped(name); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTSpanElement.h b/dom/svg/SVGTSpanElement.h new file mode 100644 index 0000000000..41c6a0ef95 --- /dev/null +++ b/dom/svg/SVGTSpanElement.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTSPANELEMENT_H_ +#define DOM_SVG_SVGTSPANELEMENT_H_ + +#include "mozilla/dom/SVGTextPositioningElement.h" + +nsresult NS_NewSVGTSpanElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGTSpanElementBase = SVGTextPositioningElement; + +class SVGTSpanElement final : public SVGTSpanElementBase { + protected: + friend nsresult(::NS_NewSVGTSpanElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGTSpanElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + protected: + EnumAttributesInfo GetEnumInfo() override; + LengthAttributesInfo GetLengthInfo() override; + + SVGAnimatedEnumeration mEnumAttributes[1]; + SVGAnimatedEnumeration* EnumAttributes() override { return mEnumAttributes; } + + SVGAnimatedLength mLengthAttributes[1]; + SVGAnimatedLength* LengthAttributes() override { return mLengthAttributes; } +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTSPANELEMENT_H_ diff --git a/dom/svg/SVGTagList.h b/dom/svg/SVGTagList.h new file mode 100644 index 0000000000..6fb5c0bef8 --- /dev/null +++ b/dom/svg/SVGTagList.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/****** + + This file contains the list of all SVG tags. + + It is designed to be used as inline input to SVGElementFactory.cpp + through the magic of C preprocessing. + + Additionally, it is consumed by the self-regeneration code in + ElementName.java from which nsHtml5ElementName.cpp/h is translated. + See parser/html/java/README.txt. + + If you edit this list, you need to re-run ElementName.java + self-regeneration and the HTML parser Java to C++ translation. + + All entries must be enclosed in the macro SVG_TAG or SVG_FROM_PARSER_TAG + which will have cruel and unusual things done to them. + + SVG_FROM_PARSER_TAG is used where the element creation method takes + a FromParser argument, and SVG_TAG where it does not. + + It is recommended (but not strictly necessary) to keep all entries + in alphabetical order. + + The first argument to SVG_TAG is both the enum identifier of the + property and the atom name. The second argument is the "creator" + method of the form NS_New$TAGNAMEElement, that will be used by + SVGElementFactory.cpp to create a content object for a tag of that + type. + + ******/ + +SVG_TAG(a, A) +SVG_TAG(animate, Animate) +SVG_TAG(animateMotion, AnimateMotion) +SVG_TAG(animateTransform, AnimateTransform) +SVG_TAG(circle, Circle) +SVG_TAG(clipPath, ClipPath) +SVG_TAG(defs, Defs) +SVG_TAG(desc, Desc) +SVG_TAG(ellipse, Ellipse) +SVG_TAG(feBlend, FEBlend) +SVG_TAG(feColorMatrix, FEColorMatrix) +SVG_TAG(feComponentTransfer, FEComponentTransfer) +SVG_TAG(feComposite, FEComposite) +SVG_TAG(feConvolveMatrix, FEConvolveMatrix) +SVG_TAG(feDiffuseLighting, FEDiffuseLighting) +SVG_TAG(feDisplacementMap, FEDisplacementMap) +SVG_TAG(feDistantLight, FEDistantLight) +SVG_TAG(feDropShadow, FEDropShadow) +SVG_TAG(feFlood, FEFlood) +SVG_TAG(feFuncA, FEFuncA) +SVG_TAG(feFuncB, FEFuncB) +SVG_TAG(feFuncG, FEFuncG) +SVG_TAG(feFuncR, FEFuncR) +SVG_TAG(feGaussianBlur, FEGaussianBlur) +SVG_TAG(feImage, FEImage) +SVG_TAG(feMerge, FEMerge) +SVG_TAG(feMergeNode, FEMergeNode) +SVG_TAG(feMorphology, FEMorphology) +SVG_TAG(feOffset, FEOffset) +SVG_TAG(fePointLight, FEPointLight) +SVG_TAG(feSpecularLighting, FESpecularLighting) +SVG_TAG(feSpotLight, FESpotLight) +SVG_TAG(feTile, FETile) +SVG_TAG(feTurbulence, FETurbulence) +SVG_TAG(filter, Filter) +SVG_TAG(foreignObject, ForeignObject) +SVG_TAG(g, G) +SVG_TAG(image, Image) +SVG_TAG(line, Line) +SVG_TAG(linearGradient, LinearGradient) +SVG_TAG(marker, Marker) +SVG_TAG(mask, Mask) +SVG_TAG(metadata, Metadata) +SVG_TAG(mpath, MPath) +SVG_TAG(path, Path) +SVG_TAG(pattern, Pattern) +SVG_TAG(polygon, Polygon) +SVG_TAG(polyline, Polyline) +SVG_TAG(radialGradient, RadialGradient) +SVG_TAG(rect, Rect) +SVG_FROM_PARSER_TAG(script, Script) +SVG_TAG(set, Set) +SVG_TAG(stop, Stop) +SVG_TAG(style, Style) +SVG_FROM_PARSER_TAG(svg, SVG) +SVG_TAG(svgSwitch, Switch) +SVG_TAG(symbol, Symbol) +SVG_TAG(text, Text) +SVG_TAG(textPath, TextPath) +SVG_TAG(title, Title) +SVG_TAG(tspan, TSpan) +SVG_TAG(use, Use) +SVG_TAG(view, View) diff --git a/dom/svg/SVGTests.cpp b/dom/svg/SVGTests.cpp new file mode 100644 index 0000000000..dd9f6e540c --- /dev/null +++ b/dom/svg/SVGTests.cpp @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTests.h" +#include "DOMSVGStringList.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "mozilla/dom/SVGSwitchElement.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsStyleUtil.h" +#include "mozilla/Preferences.h" + +namespace mozilla::dom { + +nsStaticAtom* const SVGTests::sStringListNames[2] = { + nsGkAtoms::requiredExtensions, + nsGkAtoms::systemLanguage, +}; + +SVGTests::SVGTests() { + mStringListAttributes[LANGUAGE].SetIsCommaSeparated(true); +} + +already_AddRefed<DOMSVGStringList> SVGTests::RequiredExtensions() { + return DOMSVGStringList::GetDOMWrapper(&mStringListAttributes[EXTENSIONS], + AsSVGElement(), true, EXTENSIONS); +} + +already_AddRefed<DOMSVGStringList> SVGTests::SystemLanguage() { + return DOMSVGStringList::GetDOMWrapper(&mStringListAttributes[LANGUAGE], + AsSVGElement(), true, LANGUAGE); +} + +bool SVGTests::HasExtension(const nsAString& aExtension) const { +#define SVG_SUPPORTED_EXTENSION(str) \ + if (aExtension.EqualsLiteral(str)) return true; + SVG_SUPPORTED_EXTENSION("http://www.w3.org/1999/xhtml") + nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance(); + if (AsSVGElement()->IsInChromeDocument() || + !nameSpaceManager->mMathMLDisabled) { + SVG_SUPPORTED_EXTENSION("http://www.w3.org/1998/Math/MathML") + } +#undef SVG_SUPPORTED_EXTENSION + + return false; +} + +bool SVGTests::IsConditionalProcessingAttribute( + const nsAtom* aAttribute) const { + for (uint32_t i = 0; i < ArrayLength(sStringListNames); i++) { + if (aAttribute == sStringListNames[i]) { + return true; + } + } + return false; +} + +int32_t SVGTests::GetBestLanguagePreferenceRank( + const nsAString& aAcceptLangs) const { + if (!mStringListAttributes[LANGUAGE].IsExplicitlySet()) { + return -2; + } + + int32_t lowestRank = -1; + + for (uint32_t i = 0; i < mStringListAttributes[LANGUAGE].Length(); i++) { + int32_t index = 0; + for (const nsAString& languageToken : + nsCharSeparatedTokenizer(aAcceptLangs, ',').ToRange()) { + bool exactMatch = languageToken.Equals(mStringListAttributes[LANGUAGE][i], + nsCaseInsensitiveStringComparator); + bool prefixOnlyMatch = + !exactMatch && nsStyleUtil::DashMatchCompare( + mStringListAttributes[LANGUAGE][i], languageToken, + nsCaseInsensitiveStringComparator); + if (index == 0 && exactMatch) { + // best possible match + return 0; + } + if ((exactMatch || prefixOnlyMatch) && + (lowestRank == -1 || 2 * index + prefixOnlyMatch < lowestRank)) { + lowestRank = 2 * index + prefixOnlyMatch; + } + ++index; + } + } + return lowestRank; +} + +bool SVGTests::PassesConditionalProcessingTestsIgnoringSystemLanguage() const { + // Required Extensions + // + // The requiredExtensions attribute defines a list of required language + // extensions. Language extensions are capabilities within a user agent that + // go beyond the feature set defined in the SVG specification. + // Each extension is identified by a URI reference. + // For now, claim that mozilla's SVG implementation supports XHTML and MathML. + if (mStringListAttributes[EXTENSIONS].IsExplicitlySet()) { + if (mStringListAttributes[EXTENSIONS].IsEmpty()) { + return false; + } + for (uint32_t i = 0; i < mStringListAttributes[EXTENSIONS].Length(); i++) { + if (!HasExtension(mStringListAttributes[EXTENSIONS][i])) { + return false; + } + } + } + return true; +} + +bool SVGTests::PassesConditionalProcessingTests() const { + if (!PassesConditionalProcessingTestsIgnoringSystemLanguage()) { + return false; + } + + // systemLanguage + // + // Evaluates to "true" if one of the languages indicated by user preferences + // exactly equals one of the languages given in the value of this parameter, + // or if one of the languages indicated by user preferences exactly equals a + // prefix of one of the languages given in the value of this parameter such + // that the first tag character following the prefix is "-". + if (mStringListAttributes[LANGUAGE].IsExplicitlySet()) { + if (mStringListAttributes[LANGUAGE].IsEmpty()) { + return false; + } + + // Get our language preferences + nsAutoString acceptLangs; + Preferences::GetLocalizedString("intl.accept_languages", acceptLangs); + + if (acceptLangs.IsEmpty()) { + NS_WARNING( + "no default language specified for systemLanguage conditional test"); + return false; + } + + for (uint32_t i = 0; i < mStringListAttributes[LANGUAGE].Length(); i++) { + nsCharSeparatedTokenizer languageTokenizer(acceptLangs, ','); + while (languageTokenizer.hasMoreTokens()) { + if (nsStyleUtil::DashMatchCompare(mStringListAttributes[LANGUAGE][i], + languageTokenizer.nextToken(), + nsCaseInsensitiveStringComparator)) { + return true; + } + } + } + return false; + } + + return true; +} + +bool SVGTests::ParseConditionalProcessingAttribute(nsAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult) { + for (uint32_t i = 0; i < ArrayLength(sStringListNames); i++) { + if (aAttribute == sStringListNames[i]) { + nsresult rv = mStringListAttributes[i].SetValue(aValue); + if (NS_FAILED(rv)) { + mStringListAttributes[i].Clear(); + } + MaybeInvalidate(); + return true; + } + } + return false; +} + +void SVGTests::UnsetAttr(const nsAtom* aAttribute) { + for (uint32_t i = 0; i < ArrayLength(sStringListNames); i++) { + if (aAttribute == sStringListNames[i]) { + mStringListAttributes[i].Clear(); + MaybeInvalidate(); + return; + } + } +} + +nsStaticAtom* SVGTests::GetAttrName(uint8_t aAttrEnum) const { + return sStringListNames[aAttrEnum]; +} + +void SVGTests::GetAttrValue(uint8_t aAttrEnum, nsAttrValue& aValue) const { + MOZ_ASSERT(aAttrEnum < ArrayLength(sStringListNames), + "aAttrEnum out of range"); + aValue.SetTo(mStringListAttributes[aAttrEnum], nullptr); +} + +void SVGTests::MaybeInvalidate() { + nsIContent* parent = AsSVGElement()->GetFlattenedTreeParent(); + + if (parent && + parent->NodeInfo()->Equals(nsGkAtoms::svgSwitch, kNameSpaceID_SVG)) { + static_cast<dom::SVGSwitchElement*>(parent)->MaybeInvalidate(); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTests.h b/dom/svg/SVGTests.h new file mode 100644 index 0000000000..148c35fc5c --- /dev/null +++ b/dom/svg/SVGTests.h @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTESTS_H_ +#define DOM_SVG_SVGTESTS_H_ + +#include "nsStringFwd.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/SVGStringList.h" + +class nsAttrValue; +class nsAtom; +class nsStaticAtom; + +namespace mozilla { + +namespace dom { +class DOMSVGStringList; +} + +#define MOZILLA_DOMSVGTESTS_IID \ + { \ + 0x92370da8, 0xda28, 0x4895, { \ + 0x9b, 0x1b, 0xe0, 0x06, 0x0d, 0xb7, 0x3f, 0xc3 \ + } \ + } + +namespace dom { + +class SVGElement; + +class SVGTests : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOMSVGTESTS_IID) + + SVGTests(); + + friend class dom::DOMSVGStringList; + using SVGStringList = mozilla::SVGStringList; + + /** + * Compare the language name(s) in a systemLanguage attribute to the + * user's language preferences, as defined in + * http://www.w3.org/TR/SVG11/struct.html#SystemLanguageAttribute + * We have a match if a language name in the users language preferences + * exactly equals one of the language names or exactly equals a prefix of + * one of the language names in the systemLanguage attribute. + * @returns 2 * the lowest index in the aAcceptLangs that matches + 1 + * if only the prefix matches, -2 if there's no systemLanguage attribute, + * or -1 if no indices match. + * XXX This algorithm is O(M*N). + */ + int32_t GetBestLanguagePreferenceRank(const nsAString& aAcceptLangs) const; + + /** + * Check whether the conditional processing attributes other than + * systemLanguage "return true" if they apply to and are specified + * on the given element. Returns true if this element should be + * rendered, false if it should not. + */ + bool PassesConditionalProcessingTestsIgnoringSystemLanguage() const; + + /** + * Check whether the conditional processing attributes requiredExtensions + * and systemLanguage both "return true" if they apply to + * and are specified on the given element. Returns true if this element + * should be rendered, false if it should not. + */ + bool PassesConditionalProcessingTests() const; + + /** + * Check whether the conditional processing attributes requiredExtensions + * and systemLanguage both "return true" if they apply to + * and are specified on the given element. Returns true if this element + * should be rendered, false if it should not. + * + * @param aAcceptLangs The value of the intl.accept_languages preference + */ + bool PassesConditionalProcessingTests(const nsAString& aAcceptLangs) const; + + /** + * Returns true if the attribute is one of the conditional processing + * attributes. + */ + bool IsConditionalProcessingAttribute(const nsAtom* aAttribute) const; + + bool ParseConditionalProcessingAttribute(nsAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult); + + /** + * Unsets a conditional processing attribute. + */ + void UnsetAttr(const nsAtom* aAttribute); + + nsStaticAtom* GetAttrName(uint8_t aAttrEnum) const; + void GetAttrValue(uint8_t aAttrEnum, nsAttrValue& aValue) const; + + void MaybeInvalidate(); + + // WebIDL + already_AddRefed<DOMSVGStringList> RequiredExtensions(); + already_AddRefed<DOMSVGStringList> SystemLanguage(); + + bool HasExtension(const nsAString& aExtension) const; + + virtual SVGElement* AsSVGElement() = 0; + + const SVGElement* AsSVGElement() const { + return const_cast<SVGTests*>(this)->AsSVGElement(); + } + + protected: + virtual ~SVGTests() = default; + + private: + enum { EXTENSIONS, LANGUAGE }; + SVGStringList mStringListAttributes[2]; + static nsStaticAtom* const sStringListNames[2]; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGTests, MOZILLA_DOMSVGTESTS_IID) + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGTESTS_H_ diff --git a/dom/svg/SVGTextContentElement.cpp b/dom/svg/SVGTextContentElement.cpp new file mode 100644 index 0000000000..4492255628 --- /dev/null +++ b/dom/svg/SVGTextContentElement.cpp @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextContentElement.h" + +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGTextContentElementBinding.h" +#include "mozilla/dom/SVGRect.h" +#include "nsBidiUtils.h" +#include "DOMSVGPoint.h" +#include "nsLayoutUtils.h" +#include "nsTextFragment.h" +#include "nsTextFrameUtils.h" +#include "nsTextNode.h" +#include "SVGTextFrame.h" + +namespace mozilla::dom { + +using namespace SVGTextContentElement_Binding; + +SVGEnumMapping SVGTextContentElement::sLengthAdjustMap[] = { + {nsGkAtoms::spacing, LENGTHADJUST_SPACING}, + {nsGkAtoms::spacingAndGlyphs, LENGTHADJUST_SPACINGANDGLYPHS}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGTextContentElement::sEnumInfo[1] = { + {nsGkAtoms::lengthAdjust, sLengthAdjustMap, LENGTHADJUST_SPACING}}; + +SVGElement::LengthInfo SVGTextContentElement::sLengthInfo[1] = { + {nsGkAtoms::textLength, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::XY}}; + +SVGTextFrame* SVGTextContentElement::GetSVGTextFrame() { + nsIFrame* frame = GetPrimaryFrame(FlushType::Layout); + nsIFrame* textFrame = + nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText); + return static_cast<SVGTextFrame*>(textFrame); +} + +SVGTextFrame* +SVGTextContentElement::GetSVGTextFrameForNonLayoutDependentQuery() { + nsIFrame* frame = GetPrimaryFrame(FlushType::Frames); + nsIFrame* textFrame = + nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText); + return static_cast<SVGTextFrame*>(textFrame); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGTextContentElement::TextLength() { + return LengthAttributes()[TEXTLENGTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> +SVGTextContentElement::LengthAdjust() { + return EnumAttributes()[LENGTHADJUST].ToDOMAnimatedEnum(this); +} + +//---------------------------------------------------------------------- + +template <typename T> +static bool FragmentHasSkippableCharacter(const T* aBuffer, uint32_t aLength) { + for (uint32_t i = 0; i < aLength; i++) { + if (nsTextFrameUtils::IsSkippableCharacterForTransformText(aBuffer[i])) { + return true; + } + } + return false; +} + +Maybe<int32_t> SVGTextContentElement::GetNonLayoutDependentNumberOfChars() { + SVGTextFrame* frame = GetSVGTextFrameForNonLayoutDependentQuery(); + if (!frame || frame != GetPrimaryFrame()) { + // Only support this fast path on <text>, not child <tspan>s, etc. + return Nothing(); + } + + uint32_t num = 0; + + for (nsINode* n = Element::GetFirstChild(); n; n = n->GetNextSibling()) { + if (!n->IsText()) { + return Nothing(); + } + + const nsTextFragment* text = &n->AsText()->TextFragment(); + uint32_t length = text->GetLength(); + + if (text->Is2b()) { + if (FragmentHasSkippableCharacter(text->Get2b(), length)) { + return Nothing(); + } + } else { + auto buffer = reinterpret_cast<const uint8_t*>(text->Get1b()); + if (FragmentHasSkippableCharacter(buffer, length)) { + return Nothing(); + } + } + + num += length; + } + + return Some(num); +} + +int32_t SVGTextContentElement::GetNumberOfChars() { + Maybe<int32_t> num = GetNonLayoutDependentNumberOfChars(); + if (num) { + return *num; + } + + SVGTextFrame* textFrame = GetSVGTextFrame(); + return textFrame ? textFrame->GetNumberOfChars(this) : 0; +} + +float SVGTextContentElement::GetComputedTextLength() { + SVGTextFrame* textFrame = GetSVGTextFrame(); + return textFrame ? textFrame->GetComputedTextLength(this) : 0.0f; +} + +void SVGTextContentElement::SelectSubString(uint32_t charnum, uint32_t nchars, + ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + if (!textFrame) return; + + textFrame->SelectSubString(this, charnum, nchars, rv); +} + +float SVGTextContentElement::GetSubStringLength(uint32_t charnum, + uint32_t nchars, + ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrameForNonLayoutDependentQuery(); + if (!textFrame) return 0.0f; + + return textFrame->GetSubStringLength(this, charnum, nchars, rv); +} + +already_AddRefed<DOMSVGPoint> SVGTextContentElement::GetStartPositionOfChar( + uint32_t charnum, ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + return textFrame->GetStartPositionOfChar(this, charnum, rv); +} + +already_AddRefed<DOMSVGPoint> SVGTextContentElement::GetEndPositionOfChar( + uint32_t charnum, ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + return textFrame->GetEndPositionOfChar(this, charnum, rv); +} + +already_AddRefed<SVGRect> SVGTextContentElement::GetExtentOfChar( + uint32_t charnum, ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + return textFrame->GetExtentOfChar(this, charnum, rv); +} + +float SVGTextContentElement::GetRotationOfChar(uint32_t charnum, + ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return 0.0f; + } + + return textFrame->GetRotationOfChar(this, charnum, rv); +} + +int32_t SVGTextContentElement::GetCharNumAtPosition( + const DOMPointInit& aPoint) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + return textFrame ? textFrame->GetCharNumAtPosition(this, aPoint) : -1; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextContentElement.h b/dom/svg/SVGTextContentElement.h new file mode 100644 index 0000000000..697c016a41 --- /dev/null +++ b/dom/svg/SVGTextContentElement.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTCONTENTELEMENT_H_ +#define DOM_SVG_SVGTEXTCONTENTELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" + +namespace mozilla { + +class SVGTextFrame; + +namespace dom { + +struct DOMPointInit; +class DOMSVGAnimatedEnumeration; +class DOMSVGPoint; +class SVGRect; + +using SVGTextContentElementBase = SVGGraphicsElement; + +class SVGTextContentElement : public SVGTextContentElementBase { + friend class mozilla::SVGTextFrame; + + public: + using FragmentOrElement::TextLength; + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> TextLength(); + already_AddRefed<DOMSVGAnimatedEnumeration> LengthAdjust(); + MOZ_CAN_RUN_SCRIPT int32_t GetNumberOfChars(); + MOZ_CAN_RUN_SCRIPT float GetComputedTextLength(); + MOZ_CAN_RUN_SCRIPT + void SelectSubString(uint32_t charnum, uint32_t nchars, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + float GetSubStringLength(uint32_t charnum, uint32_t nchars, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + already_AddRefed<DOMSVGPoint> GetStartPositionOfChar(uint32_t charnum, + ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + already_AddRefed<DOMSVGPoint> GetEndPositionOfChar(uint32_t charnum, + ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + already_AddRefed<SVGRect> GetExtentOfChar(uint32_t charnum, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT float GetRotationOfChar(uint32_t charnum, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT int32_t GetCharNumAtPosition(const DOMPointInit& aPoint); + + protected: + explicit SVGTextContentElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGTextContentElementBase(std::move(aNodeInfo)) {} + + MOZ_CAN_RUN_SCRIPT SVGTextFrame* GetSVGTextFrame(); + MOZ_CAN_RUN_SCRIPT SVGTextFrame* GetSVGTextFrameForNonLayoutDependentQuery(); + MOZ_CAN_RUN_SCRIPT mozilla::Maybe<int32_t> + GetNonLayoutDependentNumberOfChars(); + + enum { LENGTHADJUST }; + virtual SVGAnimatedEnumeration* EnumAttributes() = 0; + static SVGEnumMapping sLengthAdjustMap[]; + static EnumInfo sEnumInfo[1]; + + enum { TEXTLENGTH }; + virtual SVGAnimatedLength* LengthAttributes() = 0; + static LengthInfo sLengthInfo[1]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGTEXTCONTENTELEMENT_H_ diff --git a/dom/svg/SVGTextElement.cpp b/dom/svg/SVGTextElement.cpp new file mode 100644 index 0000000000..90c70e3e1e --- /dev/null +++ b/dom/svg/SVGTextElement.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextElement.h" +#include "mozilla/dom/SVGTextElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Text) + +namespace mozilla::dom { + +JSObject* SVGTextElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGTextElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGTextElement::SVGTextElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGTextElementBase(std::move(aNodeInfo)) {} + +SVGElement::EnumAttributesInfo SVGTextElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::LengthAttributesInfo SVGTextElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTextElement) + +bool SVGTextElement::IsNodeOfType(uint32_t aFlags) const { + return !(aFlags & ~eUSE_TARGET); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGTextElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sTextContentElementsMap, + sFontSpecificationMap}; + + return FindAttributeDependence(name, map) || + SVGTextElementBase::IsAttributeMapped(name); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextElement.h b/dom/svg/SVGTextElement.h new file mode 100644 index 0000000000..04e9627c7c --- /dev/null +++ b/dom/svg/SVGTextElement.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTELEMENT_H_ +#define DOM_SVG_SVGTEXTELEMENT_H_ + +#include "mozilla/dom/SVGTextPositioningElement.h" + +nsresult NS_NewSVGTextElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +using SVGTextElementBase = SVGTextPositioningElement; + +class SVGTextElement final : public SVGTextElementBase { + protected: + explicit SVGTextElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + friend nsresult(::NS_NewSVGTextElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + + public: + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + bool IsNodeOfType(uint32_t aFlags) const override; + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + protected: + EnumAttributesInfo GetEnumInfo() override; + LengthAttributesInfo GetLengthInfo() override; + + SVGAnimatedEnumeration mEnumAttributes[1]; + SVGAnimatedEnumeration* EnumAttributes() override { return mEnumAttributes; } + + SVGAnimatedLength mLengthAttributes[1]; + SVGAnimatedLength* LengthAttributes() override { return mLengthAttributes; } +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTEXTELEMENT_H_ diff --git a/dom/svg/SVGTextPathElement.cpp b/dom/svg/SVGTextPathElement.cpp new file mode 100644 index 0000000000..ed14fe5c38 --- /dev/null +++ b/dom/svg/SVGTextPathElement.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextPathElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGTextContentElementBinding.h" +#include "mozilla/dom/SVGTextPathElementBinding.h" +#include "SVGElement.h" +#include "nsGkAtoms.h" +#include "nsError.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(TextPath) + +namespace mozilla::dom { + +using namespace SVGTextContentElement_Binding; +using namespace SVGTextPathElement_Binding; + +class DOMSVGAnimatedLength; + +JSObject* SVGTextPathElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGTextPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGTextPathElement::sLengthInfo[2] = { + // from SVGTextContentElement: + {nsGkAtoms::textLength, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::XY}, + // from SVGTextPathElement: + {nsGkAtoms::startOffset, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}}; + +SVGEnumMapping SVGTextPathElement::sMethodMap[] = { + {nsGkAtoms::align, TEXTPATH_METHODTYPE_ALIGN}, + {nsGkAtoms::stretch, TEXTPATH_METHODTYPE_STRETCH}, + {nullptr, 0}}; + +SVGEnumMapping SVGTextPathElement::sSpacingMap[] = { + {nsGkAtoms::_auto, TEXTPATH_SPACINGTYPE_AUTO}, + {nsGkAtoms::exact, TEXTPATH_SPACINGTYPE_EXACT}, + {nullptr, 0}}; + +SVGEnumMapping SVGTextPathElement::sSideMap[] = { + {nsGkAtoms::left, TEXTPATH_SIDETYPE_LEFT}, + {nsGkAtoms::right, TEXTPATH_SIDETYPE_RIGHT}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGTextPathElement::sEnumInfo[4] = { + // from SVGTextContentElement: + {nsGkAtoms::lengthAdjust, sLengthAdjustMap, LENGTHADJUST_SPACING}, + // from SVGTextPathElement: + {nsGkAtoms::method, sMethodMap, TEXTPATH_METHODTYPE_ALIGN}, + {nsGkAtoms::spacing, sSpacingMap, TEXTPATH_SPACINGTYPE_EXACT}, + {nsGkAtoms::side_, sSideMap, TEXTPATH_SIDETYPE_LEFT}}; + +SVGElement::StringInfo SVGTextPathElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGTextPathElement::SVGTextPathElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGTextPathElementBase(std::move(aNodeInfo)) {} + +void SVGTextPathElement::HrefAsString(nsAString& aHref) { + if (mStringAttributes[SVGTextPathElement::HREF].IsExplicitlySet()) { + mStringAttributes[SVGTextPathElement::HREF].GetAnimValue(aHref, this); + } else { + mStringAttributes[SVGTextPathElement::XLINK_HREF].GetAnimValue(aHref, this); + } +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTextPathElement) + +already_AddRefed<DOMSVGAnimatedString> SVGTextPathElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGTextPathElement::StartOffset() { + return mLengthAttributes[STARTOFFSET].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGTextPathElement::Method() { + return mEnumAttributes[METHOD].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGTextPathElement::Spacing() { + return mEnumAttributes[SPACING].ToDOMAnimatedEnum(this); +} + +already_AddRefed<DOMSVGAnimatedEnumeration> SVGTextPathElement::Side() { + return mEnumAttributes[SIDE].ToDOMAnimatedEnum(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGTextPathElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = { + sColorMap, sFillStrokeMap, sFontSpecificationMap, sGraphicsMap, + sTextContentElementsMap}; + + return FindAttributeDependence(name, map) || + SVGTextPathElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement overrides + +SVGElement::LengthAttributesInfo SVGTextPathElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGTextPathElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGTextPathElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextPathElement.h b/dom/svg/SVGTextPathElement.h new file mode 100644 index 0000000000..62c2661d00 --- /dev/null +++ b/dom/svg/SVGTextPathElement.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTPATHELEMENT_H_ +#define DOM_SVG_SVGTEXTPATHELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGTextContentElement.h" + +class nsAtom; +class nsIContent; + +nsresult NS_NewSVGTextPathElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla::dom { + +// textPath side types +static const uint16_t TEXTPATH_SIDETYPE_LEFT = 1; +static const uint16_t TEXTPATH_SIDETYPE_RIGHT = 2; + +using SVGTextPathElementBase = SVGTextContentElement; + +class SVGTextPathElement final : public SVGTextPathElementBase { + friend class mozilla::SVGTextFrame; + + protected: + friend nsresult(::NS_NewSVGTextPathElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGTextPathElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + SVGAnimatedPathSegList* GetAnimPathSegList() override { return &mPath; } + + nsStaticAtom* GetPathDataAttrName() const override { return nsGkAtoms::path; } + + // WebIDL + already_AddRefed<DOMSVGAnimatedLength> StartOffset(); + already_AddRefed<DOMSVGAnimatedEnumeration> Method(); + already_AddRefed<DOMSVGAnimatedEnumeration> Spacing(); + already_AddRefed<DOMSVGAnimatedEnumeration> Side(); + already_AddRefed<DOMSVGAnimatedString> Href(); + + void HrefAsString(nsAString& aHref); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { /* TEXTLENGTH, */ STARTOFFSET = 1 }; + SVGAnimatedLength mLengthAttributes[2]; + SVGAnimatedLength* LengthAttributes() override { return mLengthAttributes; } + static LengthInfo sLengthInfo[2]; + + enum { /* LENGTHADJUST, */ METHOD = 1, SPACING, SIDE }; + SVGAnimatedEnumeration mEnumAttributes[4]; + SVGAnimatedEnumeration* EnumAttributes() override { return mEnumAttributes; } + static SVGEnumMapping sMethodMap[]; + static SVGEnumMapping sSpacingMap[]; + static SVGEnumMapping sSideMap[]; + static EnumInfo sEnumInfo[4]; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + SVGAnimatedPathSegList mPath; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTEXTPATHELEMENT_H_ diff --git a/dom/svg/SVGTextPositioningElement.cpp b/dom/svg/SVGTextPositioningElement.cpp new file mode 100644 index 0000000000..7baa060e30 --- /dev/null +++ b/dom/svg/SVGTextPositioningElement.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextPositioningElement.h" + +#include "mozilla/ArrayUtils.h" +#include "SVGAnimatedLengthList.h" +#include "DOMSVGAnimatedLengthList.h" +#include "DOMSVGAnimatedNumberList.h" +#include "SVGContentUtils.h" + +namespace mozilla::dom { + +SVGElement::LengthListInfo SVGTextPositioningElement::sLengthListInfo[4] = { + {nsGkAtoms::x, SVGContentUtils::X, false}, + {nsGkAtoms::y, SVGContentUtils::Y, false}, + {nsGkAtoms::dx, SVGContentUtils::X, true}, + {nsGkAtoms::dy, SVGContentUtils::Y, true}}; + +SVGElement::LengthListAttributesInfo +SVGTextPositioningElement::GetLengthListInfo() { + return LengthListAttributesInfo(mLengthListAttributes, sLengthListInfo, + ArrayLength(sLengthListInfo)); +} + +SVGElement::NumberListInfo SVGTextPositioningElement::sNumberListInfo[1] = { + {nsGkAtoms::rotate}}; + +SVGElement::NumberListAttributesInfo +SVGTextPositioningElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLengthList> SVGTextPositioningElement::X() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_X], this, ATTR_X, SVGContentUtils::X); +} + +already_AddRefed<DOMSVGAnimatedLengthList> SVGTextPositioningElement::Y() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_Y], this, ATTR_Y, SVGContentUtils::Y); +} + +already_AddRefed<DOMSVGAnimatedLengthList> SVGTextPositioningElement::Dx() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_DX], this, ATTR_DX, SVGContentUtils::X); +} + +already_AddRefed<DOMSVGAnimatedLengthList> SVGTextPositioningElement::Dy() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_DY], this, ATTR_DY, SVGContentUtils::Y); +} + +already_AddRefed<DOMSVGAnimatedNumberList> SVGTextPositioningElement::Rotate() { + return DOMSVGAnimatedNumberList::GetDOMWrapper(&mNumberListAttributes[ROTATE], + this, ROTATE); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextPositioningElement.h b/dom/svg/SVGTextPositioningElement.h new file mode 100644 index 0000000000..a7b4b4b73d --- /dev/null +++ b/dom/svg/SVGTextPositioningElement.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTPOSITIONINGELEMENT_H_ +#define DOM_SVG_SVGTEXTPOSITIONINGELEMENT_H_ + +#include "mozilla/dom/SVGTextContentElement.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAnimatedNumberList.h" + +namespace mozilla { +class SVGAnimatedLengthList; + +namespace dom { +class DOMSVGAnimatedLengthList; +class DOMSVGAnimatedNumberList; +using SVGTextPositioningElementBase = SVGTextContentElement; + +class SVGTextPositioningElement : public SVGTextPositioningElementBase { + public: + // WebIDL + already_AddRefed<DOMSVGAnimatedLengthList> X(); + already_AddRefed<DOMSVGAnimatedLengthList> Y(); + already_AddRefed<DOMSVGAnimatedLengthList> Dx(); + already_AddRefed<DOMSVGAnimatedLengthList> Dy(); + already_AddRefed<DOMSVGAnimatedNumberList> Rotate(); + + protected: + explicit SVGTextPositioningElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGTextPositioningElementBase(std::move(aNodeInfo)) {} + + LengthListAttributesInfo GetLengthListInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_DX, ATTR_DY }; + SVGAnimatedLengthList mLengthListAttributes[4]; + static LengthListInfo sLengthListInfo[4]; + + enum { ROTATE }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGTEXTPOSITIONINGELEMENT_H_ diff --git a/dom/svg/SVGTitleElement.cpp b/dom/svg/SVGTitleElement.cpp new file mode 100644 index 0000000000..d56fc44934 --- /dev/null +++ b/dom/svg/SVGTitleElement.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTitleElement.h" +#include "mozilla/dom/SVGTitleElementBinding.h" + +#include "mozilla/dom/Document.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Title) + +namespace mozilla::dom { + +JSObject* SVGTitleElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGTitleElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGTitleElement, SVGTitleElementBase, + nsIMutationObserver) + +//---------------------------------------------------------------------- +// Implementation + +SVGTitleElement::SVGTitleElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGTitleElementBase(std::move(aNodeInfo)) { + AddMutationObserver(this); +} + +void SVGTitleElement::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + SendTitleChangeEvent(false); +} + +void SVGTitleElement::ContentAppended(nsIContent* aFirstNewContent) { + SendTitleChangeEvent(false); +} + +void SVGTitleElement::ContentInserted(nsIContent* aChild) { + SendTitleChangeEvent(false); +} + +void SVGTitleElement::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + SendTitleChangeEvent(false); +} + +nsresult SVGTitleElement::BindToTree(BindContext& aContext, nsINode& aParent) { + // Let this fall through. + nsresult rv = SVGTitleElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + SendTitleChangeEvent(true); + + return NS_OK; +} + +void SVGTitleElement::UnbindFromTree(bool aNullParent) { + SendTitleChangeEvent(false); + + // Let this fall through. + SVGTitleElementBase::UnbindFromTree(aNullParent); +} + +void SVGTitleElement::DoneAddingChildren(bool aHaveNotified) { + if (!aHaveNotified) { + SendTitleChangeEvent(false); + } +} + +void SVGTitleElement::SendTitleChangeEvent(bool aBound) { + Document* doc = GetUncomposedDoc(); + if (doc) { + doc->NotifyPossibleTitleChange(aBound); + } +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTitleElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTitleElement.h b/dom/svg/SVGTitleElement.h new file mode 100644 index 0000000000..52f8bf886c --- /dev/null +++ b/dom/svg/SVGTitleElement.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTITLEELEMENT_H_ +#define DOM_SVG_SVGTITLEELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" +#include "nsStubMutationObserver.h" + +nsresult NS_NewSVGTitleElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); +namespace mozilla::dom { + +using SVGTitleElementBase = SVGElement; + +class SVGTitleElement final : public SVGTitleElementBase, + public nsStubMutationObserver { + protected: + friend nsresult(::NS_NewSVGTitleElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGTitleElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + ~SVGTitleElement() = default; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + public: + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + void UnbindFromTree(bool aNullParent = true) override; + + void DoneAddingChildren(bool aHaveNotified) override; + + private: + void SendTitleChangeEvent(bool aBound); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTITLEELEMENT_H_ diff --git a/dom/svg/SVGTransform.cpp b/dom/svg/SVGTransform.cpp new file mode 100644 index 0000000000..c993253c4c --- /dev/null +++ b/dom/svg/SVGTransform.cpp @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransform.h" + +#include "nsError.h" +#include "nsContentUtils.h" // for NS_ENSURE_FINITE +#include "nsTextFormatter.h" + +namespace { +const double kRadPerDegree = 2.0 * M_PI / 360.0; +} // namespace + +namespace mozilla { + +using namespace dom::SVGTransform_Binding; + +void SVGTransform::GetValueAsString(nsAString& aValue) const { + switch (mType) { + case SVG_TRANSFORM_TRANSLATE: + // The spec say that if Y is not provided, it is assumed to be zero. + if (mMatrix._32 != 0) + nsTextFormatter::ssprintf(aValue, u"translate(%g, %g)", mMatrix._31, + mMatrix._32); + else + nsTextFormatter::ssprintf(aValue, u"translate(%g)", mMatrix._31); + break; + case SVG_TRANSFORM_ROTATE: + if (mOriginX != 0.0f || mOriginY != 0.0f) + nsTextFormatter::ssprintf(aValue, u"rotate(%g, %g, %g)", mAngle, + mOriginX, mOriginY); + else + nsTextFormatter::ssprintf(aValue, u"rotate(%g)", mAngle); + break; + case SVG_TRANSFORM_SCALE: + if (mMatrix._11 != mMatrix._22) + nsTextFormatter::ssprintf(aValue, u"scale(%g, %g)", mMatrix._11, + mMatrix._22); + else + nsTextFormatter::ssprintf(aValue, u"scale(%g)", mMatrix._11); + break; + case SVG_TRANSFORM_SKEWX: + nsTextFormatter::ssprintf(aValue, u"skewX(%g)", mAngle); + break; + case SVG_TRANSFORM_SKEWY: + nsTextFormatter::ssprintf(aValue, u"skewY(%g)", mAngle); + break; + case SVG_TRANSFORM_MATRIX: + nsTextFormatter::ssprintf(aValue, u"matrix(%g, %g, %g, %g, %g, %g)", + mMatrix._11, mMatrix._12, mMatrix._21, + mMatrix._22, mMatrix._31, mMatrix._32); + break; + default: + aValue.Truncate(); + NS_ERROR("unknown transformation type"); + break; + } +} + +void SVGTransform::SetMatrix(const gfxMatrix& aMatrix) { + mType = SVG_TRANSFORM_MATRIX; + mMatrix = aMatrix; + // We set the other members here too, since operator== requires it and + // the DOM requires it for mAngle. + mAngle = 0.f; + mOriginX = 0.f; + mOriginY = 0.f; +} + +void SVGTransform::SetTranslate(float aTx, float aTy) { + mType = SVG_TRANSFORM_TRANSLATE; + mMatrix = gfxMatrix::Translation(aTx, aTy); + mAngle = 0.f; + mOriginX = 0.f; + mOriginY = 0.f; +} + +void SVGTransform::SetScale(float aSx, float aSy) { + mType = SVG_TRANSFORM_SCALE; + mMatrix = gfxMatrix::Scaling(aSx, aSy); + mAngle = 0.f; + mOriginX = 0.f; + mOriginY = 0.f; +} + +void SVGTransform::SetRotate(float aAngle, float aCx, float aCy) { + mType = SVG_TRANSFORM_ROTATE; + mMatrix = gfxMatrix::Translation(aCx, aCy) + .PreRotate(aAngle * kRadPerDegree) + .PreTranslate(-aCx, -aCy); + mAngle = aAngle; + mOriginX = aCx; + mOriginY = aCy; +} + +nsresult SVGTransform::SetSkewX(float aAngle) { + double ta = tan(aAngle * kRadPerDegree); + // No one actually cares about the exact error return type here. + NS_ENSURE_FINITE(ta, NS_ERROR_INVALID_ARG); + + mType = SVG_TRANSFORM_SKEWX; + mMatrix = gfxMatrix(); + mMatrix._21 = ta; + mAngle = aAngle; + mOriginX = 0.f; + mOriginY = 0.f; + return NS_OK; +} + +nsresult SVGTransform::SetSkewY(float aAngle) { + double ta = tan(aAngle * kRadPerDegree); + // No one actually cares about the exact error return type here. + NS_ENSURE_FINITE(ta, NS_ERROR_INVALID_ARG); + + mType = SVG_TRANSFORM_SKEWY; + mMatrix = gfxMatrix(); + mMatrix._12 = ta; + mAngle = aAngle; + mOriginX = 0.f; + mOriginY = 0.f; + return NS_OK; +} + +SVGTransformSMILData::SVGTransformSMILData(const SVGTransform& aTransform) + : mTransformType(aTransform.Type()) { + MOZ_ASSERT(mTransformType >= SVG_TRANSFORM_MATRIX && + mTransformType <= SVG_TRANSFORM_SKEWY, + "Unexpected transform type"); + + for (uint32_t i = 0; i < NUM_STORED_PARAMS; ++i) { + mParams[i] = 0.f; + } + + switch (mTransformType) { + case SVG_TRANSFORM_MATRIX: { + const gfxMatrix& mx = aTransform.GetMatrix(); + mParams[0] = static_cast<float>(mx._11); + mParams[1] = static_cast<float>(mx._12); + mParams[2] = static_cast<float>(mx._21); + mParams[3] = static_cast<float>(mx._22); + mParams[4] = static_cast<float>(mx._31); + mParams[5] = static_cast<float>(mx._32); + break; + } + case SVG_TRANSFORM_TRANSLATE: { + const gfxMatrix& mx = aTransform.GetMatrix(); + mParams[0] = static_cast<float>(mx._31); + mParams[1] = static_cast<float>(mx._32); + break; + } + case SVG_TRANSFORM_SCALE: { + const gfxMatrix& mx = aTransform.GetMatrix(); + mParams[0] = static_cast<float>(mx._11); + mParams[1] = static_cast<float>(mx._22); + break; + } + case SVG_TRANSFORM_ROTATE: + mParams[0] = aTransform.Angle(); + aTransform.GetRotationOrigin(mParams[1], mParams[2]); + break; + + case SVG_TRANSFORM_SKEWX: + case SVG_TRANSFORM_SKEWY: + mParams[0] = aTransform.Angle(); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected transform type"); + break; + } +} + +SVGTransform SVGTransformSMILData::ToSVGTransform() const { + SVGTransform result; + + switch (mTransformType) { + case SVG_TRANSFORM_MATRIX: + result.SetMatrix(gfxMatrix(mParams[0], mParams[1], mParams[2], mParams[3], + mParams[4], mParams[5])); + break; + + case SVG_TRANSFORM_TRANSLATE: + result.SetTranslate(mParams[0], mParams[1]); + break; + + case SVG_TRANSFORM_SCALE: + result.SetScale(mParams[0], mParams[1]); + break; + + case SVG_TRANSFORM_ROTATE: + result.SetRotate(mParams[0], mParams[1], mParams[2]); + break; + + case SVG_TRANSFORM_SKEWX: + result.SetSkewX(mParams[0]); + break; + + case SVG_TRANSFORM_SKEWY: + result.SetSkewY(mParams[0]); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected transform type"); + break; + } + return result; +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransform.h b/dom/svg/SVGTransform.h new file mode 100644 index 0000000000..50a330c854 --- /dev/null +++ b/dom/svg/SVGTransform.h @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORM_H_ +#define DOM_SVG_SVGTRANSFORM_H_ + +#include "gfxMatrix.h" +#include "mozilla/dom/SVGTransformBinding.h" +#include "mozilla/gfx/Matrix.h" +#include "nsDebug.h" + +namespace mozilla { + +/* + * The DOM wrapper class for this class is DOMSVGTransform. + */ +class SVGTransform { + public: + // Default ctor initialises to matrix type with identity matrix + SVGTransform() + : mMatrix() // Initialises to identity + , + mAngle(0.f), + mOriginX(0.f), + mOriginY(0.f), + mType(dom::SVGTransform_Binding::SVG_TRANSFORM_MATRIX) {} + + explicit SVGTransform(const gfxMatrix& aMatrix) + : mMatrix(aMatrix), + mAngle(0.f), + mOriginX(0.f), + mOriginY(0.f), + mType(dom::SVGTransform_Binding::SVG_TRANSFORM_MATRIX) {} + + bool operator==(const SVGTransform& rhs) const { + return mType == rhs.mType && MatricesEqual(mMatrix, rhs.mMatrix) && + mAngle == rhs.mAngle && mOriginX == rhs.mOriginX && + mOriginY == rhs.mOriginY; + } + + void GetValueAsString(nsAString& aValue) const; + + float Angle() const { return mAngle; } + void GetRotationOrigin(float& aOriginX, float& aOriginY) const { + aOriginX = mOriginX; + aOriginY = mOriginY; + } + uint16_t Type() const { return mType; } + + const gfxMatrix& GetMatrix() const { return mMatrix; } + void SetMatrix(const gfxMatrix& aMatrix); + void SetTranslate(float aTx, float aTy); + void SetScale(float aSx, float aSy); + void SetRotate(float aAngle, float aCx, float aCy); + nsresult SetSkewX(float aAngle); + nsresult SetSkewY(float aAngle); + + static bool MatricesEqual(const gfxMatrix& a, const gfxMatrix& b) { + return a._11 == b._11 && a._12 == b._12 && a._21 == b._21 && + a._22 == b._22 && a._31 == b._31 && a._32 == b._32; + } + + protected: + gfxMatrix mMatrix; + float mAngle, mOriginX, mOriginY; + uint16_t mType; +}; + +/* + * A slightly more light-weight version of SVGTransform for SMIL animation. + * + * Storing the parameters in an array (rather than a matrix) also allows simpler + * (transform type-agnostic) interpolation and addition. + * + * The meaning of the mParams array depends on the transform type as follows: + * + * Type | mParams[0], mParams[1], mParams[2], ... + * --------------------+----------------------------------------- + * translate | tx, ty + * scale | sx, sy + * rotate | rotation-angle (in degrees), cx, cy + * skewX | skew-angle (in degrees) + * skewY | skew-angle (in degrees) + * matrix | a, b, c, d, e, f + * + * The matrix type is never generated by animation code (it is only produced + * when the user inserts one via the DOM) and often requires special handling + * when we do encounter it. Therefore many users of this class are only + * interested in the first three parameters and so we provide a special + * constructor for setting those parameters only. + */ +class SVGTransformSMILData { + public: + // Number of float-params required in constructor, if constructing one of the + // 'simple' transform types (all but matrix type) + static const uint32_t NUM_SIMPLE_PARAMS = 3; + + // Number of float-params required in constructor for matrix type. + // This is also the number of params we actually store, regardless of type. + static const uint32_t NUM_STORED_PARAMS = 6; + + explicit SVGTransformSMILData(uint16_t aType) : mTransformType(aType) { + MOZ_ASSERT(aType >= dom::SVGTransform_Binding::SVG_TRANSFORM_MATRIX && + aType <= dom::SVGTransform_Binding::SVG_TRANSFORM_SKEWY, + "Unexpected transform type"); + for (uint32_t i = 0; i < NUM_STORED_PARAMS; ++i) { + mParams[i] = 0.f; + } + } + + SVGTransformSMILData(uint16_t aType, float (&aParams)[NUM_SIMPLE_PARAMS]) + : mTransformType(aType) { + MOZ_ASSERT(aType >= dom::SVGTransform_Binding::SVG_TRANSFORM_TRANSLATE && + aType <= dom::SVGTransform_Binding::SVG_TRANSFORM_SKEWY, + "Expected 'simple' transform type"); + for (uint32_t i = 0; i < NUM_SIMPLE_PARAMS; ++i) { + mParams[i] = aParams[i]; + } + for (uint32_t i = NUM_SIMPLE_PARAMS; i < NUM_STORED_PARAMS; ++i) { + mParams[i] = 0.f; + } + } + + // Conversion to/from a fully-fledged SVGTransform + explicit SVGTransformSMILData(const SVGTransform& aTransform); + SVGTransform ToSVGTransform() const; + + bool operator==(const SVGTransformSMILData& aOther) const { + if (mTransformType != aOther.mTransformType) return false; + + for (uint32_t i = 0; i < NUM_STORED_PARAMS; ++i) { + if (mParams[i] != aOther.mParams[i]) { + return false; + } + } + + return true; + } + + bool operator!=(const SVGTransformSMILData& aOther) const { + return !(*this == aOther); + } + + uint16_t mTransformType; + float mParams[NUM_STORED_PARAMS]; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORM_H_ diff --git a/dom/svg/SVGTransformList.cpp b/dom/svg/SVGTransformList.cpp new file mode 100644 index 0000000000..244ca61c85 --- /dev/null +++ b/dom/svg/SVGTransformList.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformList.h" +#include "SVGTransformListParser.h" +#include "nsString.h" +#include "nsError.h" + +namespace mozilla { + +gfxMatrix SVGTransformList::GetConsolidationMatrix() const { + // To benefit from Return Value Optimization and avoid copy constructor calls + // due to our use of return-by-value, we must return the exact same object + // from ALL return points. This function must only return THIS variable: + gfxMatrix result; + + if (mItems.IsEmpty()) return result; + + result = mItems[0].GetMatrix(); + + if (mItems.Length() == 1) return result; + + for (uint32_t i = 1; i < mItems.Length(); ++i) { + result.PreMultiply(mItems[i].GetMatrix()); + } + + return result; +} + +nsresult SVGTransformList::CopyFrom(const SVGTransformList& rhs) { + return CopyFrom(rhs.mItems); +} + +nsresult SVGTransformList::CopyFrom( + const nsTArray<SVGTransform>& aTransformArray) { + if (!mItems.Assign(aTransformArray, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGTransformList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + uint32_t last = mItems.Length() - 1; + for (uint32_t i = 0; i < mItems.Length(); ++i) { + nsAutoString length; + mItems[i].GetValueAsString(length); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(length); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGTransformList::SetValueFromString(const nsAString& aValue) { + SVGTransformListParser parser(aValue); + if (!parser.Parse()) { + // there was a parse error. + return NS_ERROR_DOM_SYNTAX_ERR; + } + + return CopyFrom(parser.GetTransformList()); +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransformList.h b/dom/svg/SVGTransformList.h new file mode 100644 index 0000000000..55aacf7007 --- /dev/null +++ b/dom/svg/SVGTransformList.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMLIST_H_ +#define DOM_SVG_SVGTRANSFORMLIST_H_ + +#include "gfxMatrix.h" +#include "mozilla/dom/SVGTransform.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { +class DOMSVGTransform; +class DOMSVGTransformList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGTransformList. + */ +class SVGTransformList { + friend class SVGAnimatedTransformList; + friend class dom::DOMSVGTransform; + friend class dom::DOMSVGTransformList; + + public: + SVGTransformList() = default; + ~SVGTransformList() = default; + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mItems.IsEmpty(); } + + uint32_t Length() const { return mItems.Length(); } + + const SVGTransform& operator[](uint32_t aIndex) const { + return mItems[aIndex]; + } + + bool operator==(const SVGTransformList& rhs) const { + return mItems == rhs.mItems; + } + + bool SetCapacity(uint32_t size) { return mItems.SetCapacity(size, fallible); } + + void Compact() { mItems.Compact(); } + + gfxMatrix GetConsolidationMatrix() const; + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // DOMSVGAnimatedTransformList and having that class act as an intermediary so + // it can take care of keeping DOM wrappers in sync. + + protected: + /** + * These may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGTransformList& rhs); + nsresult CopyFrom(const nsTArray<SVGTransform>& aTransformArray); + + SVGTransform& operator[](uint32_t aIndex) { return mItems[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mItems.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mItems.Clear(); } + + bool InsertItem(uint32_t aIndex, const SVGTransform& aTransform) { + if (aIndex >= mItems.Length()) { + aIndex = mItems.Length(); + } + return !!mItems.InsertElementAt(aIndex, aTransform, fallible); + } + + void ReplaceItem(uint32_t aIndex, const SVGTransform& aTransform) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems[aIndex] = aTransform; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems.RemoveElementAt(aIndex); + } + + bool AppendItem(const SVGTransform& aTransform) { + return !!mItems.AppendElement(aTransform, fallible); + } + + protected: + /* + * See SVGLengthList for the rationale for using + * FallibleTArray<SVGTransform> instead of FallibleTArray<SVGTransform, + * 1>. + */ + FallibleTArray<SVGTransform> mItems; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORMLIST_H_ diff --git a/dom/svg/SVGTransformListParser.cpp b/dom/svg/SVGTransformListParser.cpp new file mode 100644 index 0000000000..692577bea7 --- /dev/null +++ b/dom/svg/SVGTransformListParser.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformListParser.h" + +#include "mozilla/ArrayUtils.h" +#include "SVGContentUtils.h" +#include "SVGTransform.h" +#include "nsGkAtoms.h" +#include "nsAtom.h" + +namespace mozilla { + +//---------------------------------------------------------------------- +// private methods + +bool SVGTransformListParser::Parse() { + mTransforms.Clear(); + return ParseTransforms(); +} + +bool SVGTransformListParser::ParseTransforms() { + if (!SkipWsp()) { + return true; + } + + if (!ParseTransform()) { + return false; + } + + while (SkipWsp()) { + // The SVG BNF allows multiple comma-wsp between transforms + while (*mIter == ',') { + ++mIter; + if (!SkipWsp()) { + return false; + } + } + + if (!ParseTransform()) { + return false; + } + } + return true; +} + +bool SVGTransformListParser::ParseTransform() { + RangedPtr<const char16_t> start(mIter); + while (IsAlpha(*mIter)) { + ++mIter; + if (mIter == mEnd) { + return false; + } + } + + if (start == mIter) { + // Didn't read anything + return false; + } + + const nsAString& transform = Substring(start.get(), mIter.get()); + nsStaticAtom* keyAtom = NS_GetStaticAtom(transform); + + if (!keyAtom || !SkipWsp()) { + return false; + } + + if (keyAtom == nsGkAtoms::translate) { + return ParseTranslate(); + } + if (keyAtom == nsGkAtoms::scale) { + return ParseScale(); + } + if (keyAtom == nsGkAtoms::rotate) { + return ParseRotate(); + } + if (keyAtom == nsGkAtoms::skewX) { + return ParseSkewX(); + } + if (keyAtom == nsGkAtoms::skewY) { + return ParseSkewY(); + } + if (keyAtom == nsGkAtoms::matrix) { + return ParseMatrix(); + } + return false; +} + +bool SVGTransformListParser::ParseArguments(float* aResult, uint32_t aMaxCount, + uint32_t* aParsedCount) { + if (*mIter != '(') { + return false; + } + ++mIter; + + if (!SkipWsp()) { + return false; + } + + if (!SVGContentUtils::ParseNumber(mIter, mEnd, aResult[0])) { + return false; + } + *aParsedCount = 1; + + while (SkipWsp()) { + if (*mIter == ')') { + ++mIter; + return true; + } + if (*aParsedCount == aMaxCount) { + return false; + } + SkipCommaWsp(); + if (!SVGContentUtils::ParseNumber(mIter, mEnd, + aResult[(*aParsedCount)++])) { + return false; + } + } + return false; +} + +bool SVGTransformListParser::ParseTranslate() { + float t[2]; + uint32_t count; + + if (!ParseArguments(t, ArrayLength(t), &count)) { + return false; + } + + switch (count) { + case 1: + t[1] = 0.f; + [[fallthrough]]; + case 2: { + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetTranslate(t[0], t[1]); + return true; + } + } + + return false; +} + +bool SVGTransformListParser::ParseScale() { + float s[2]; + uint32_t count; + + if (!ParseArguments(s, ArrayLength(s), &count)) { + return false; + } + + switch (count) { + case 1: + s[1] = s[0]; + [[fallthrough]]; + case 2: { + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetScale(s[0], s[1]); + return true; + } + } + + return false; +} + +bool SVGTransformListParser::ParseRotate() { + float r[3]; + uint32_t count; + + if (!ParseArguments(r, ArrayLength(r), &count)) { + return false; + } + + switch (count) { + case 1: + r[1] = r[2] = 0.f; + [[fallthrough]]; + case 3: { + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetRotate(r[0], r[1], r[2]); + return true; + } + } + + return false; +} + +bool SVGTransformListParser::ParseSkewX() { + float skew; + uint32_t count; + + if (!ParseArguments(&skew, 1, &count) || count != 1) { + return false; + } + + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetSkewX(skew); + + return true; +} + +bool SVGTransformListParser::ParseSkewY() { + float skew; + uint32_t count; + + if (!ParseArguments(&skew, 1, &count) || count != 1) { + return false; + } + + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetSkewY(skew); + + return true; +} + +bool SVGTransformListParser::ParseMatrix() { + float m[6]; + uint32_t count; + + if (!ParseArguments(m, ArrayLength(m), &count) || count != 6) { + return false; + } + + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetMatrix(gfxMatrix(m[0], m[1], m[2], m[3], m[4], m[5])); + + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransformListParser.h b/dom/svg/SVGTransformListParser.h new file mode 100644 index 0000000000..38e2e3ba5c --- /dev/null +++ b/dom/svg/SVGTransformListParser.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMLISTPARSER_H_ +#define DOM_SVG_SVGTRANSFORMLISTPARSER_H_ + +#include "mozilla/Attributes.h" +#include "SVGDataParser.h" +#include "nsTArray.h" + +//////////////////////////////////////////////////////////////////////// +// SVGTransformListParser: A simple recursive descent parser that builds +// transform lists from transform attributes. The grammar for path data +// can be found in SVG 1.1, chapter 7. +// http://www.w3.org/TR/SVG11/coords.html#TransformAttribute + +namespace mozilla { + +class SVGTransform; + +class SVGTransformListParser : public SVGDataParser { + public: + explicit SVGTransformListParser(const nsAString& aValue) + : SVGDataParser(aValue) {} + + bool Parse(); + + const nsTArray<SVGTransform>& GetTransformList() const { return mTransforms; } + + private: + // helpers + bool ParseArguments(float* aResult, uint32_t aMaxCount, + uint32_t* aParsedCount); + + bool ParseTransforms(); + + bool ParseTransform(); + + bool ParseTranslate(); + bool ParseScale(); + bool ParseRotate(); + bool ParseSkewX(); + bool ParseSkewY(); + bool ParseMatrix(); + + FallibleTArray<SVGTransform> mTransforms; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORMLISTPARSER_H_ diff --git a/dom/svg/SVGTransformListSMILType.cpp b/dom/svg/SVGTransformListSMILType.cpp new file mode 100644 index 0000000000..e548f97bc7 --- /dev/null +++ b/dom/svg/SVGTransformListSMILType.cpp @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformListSMILType.h" + +#include "mozilla/SMILValue.h" +#include "nsCRT.h" +#include "SVGTransformList.h" +#include "SVGTransform.h" +#include <math.h> + +using namespace mozilla::dom::SVGTransform_Binding; + +namespace mozilla { + +using TransformArray = FallibleTArray<SVGTransformSMILData>; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGTransformListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + TransformArray* transforms = new TransformArray(1); + aValue.mU.mPtr = transforms; + aValue.mType = this; +} + +void SVGTransformListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + TransformArray* params = static_cast<TransformArray*>(aValue.mU.mPtr); + delete params; + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGTransformListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const TransformArray* srcTransforms = + static_cast<const TransformArray*>(aSrc.mU.mPtr); + TransformArray* dstTransforms = static_cast<TransformArray*>(aDest.mU.mPtr); + if (!dstTransforms->Assign(*srcTransforms, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +bool SVGTransformListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL type"); + + const TransformArray& leftArr( + *static_cast<const TransformArray*>(aLeft.mU.mPtr)); + const TransformArray& rightArr( + *static_cast<const TransformArray*>(aRight.mU.mPtr)); + + // If array-lengths don't match, we're trivially non-equal. + if (leftArr.Length() != rightArr.Length()) { + return false; + } + + // Array-lengths match -- check each array-entry for equality. + uint32_t length = leftArr.Length(); // == rightArr->Length(), if we get here + for (uint32_t i = 0; i < length; ++i) { + if (leftArr[i] != rightArr[i]) { + return false; + } + } + + // Found no differences. + return true; +} + +nsresult SVGTransformListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + + TransformArray& dstTransforms(*static_cast<TransformArray*>(aDest.mU.mPtr)); + const TransformArray& srcTransforms( + *static_cast<const TransformArray*>(aValueToAdd.mU.mPtr)); + + // We're doing a simple add here (as opposed to a sandwich add below). + // We only do this when we're accumulating a repeat result or calculating + // a by-animation value. + // + // In either case we should have 1 transform in the source array. + NS_ASSERTION(srcTransforms.Length() == 1, + "Invalid source transform list to add"); + + // And we should have 0 or 1 transforms in the dest array. + // (We can have 0 transforms in the case of by-animation when we are + // calculating the by-value as "0 + by". Zero being represented by a + // SMILValue with an empty transform array.) + NS_ASSERTION(dstTransforms.Length() < 2, + "Invalid dest transform list to add to"); + + // Get the individual transforms to add + const SVGTransformSMILData& srcTransform = srcTransforms[0]; + if (dstTransforms.IsEmpty()) { + SVGTransformSMILData* result = dstTransforms.AppendElement( + SVGTransformSMILData(srcTransform.mTransformType), fallible); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); + } + SVGTransformSMILData& dstTransform = dstTransforms[0]; + + // The types must be the same + NS_ASSERTION(srcTransform.mTransformType == dstTransform.mTransformType, + "Trying to perform simple add of different transform types"); + + // And it should be impossible that one of them is of matrix type + NS_ASSERTION(srcTransform.mTransformType != SVG_TRANSFORM_MATRIX, + "Trying to perform simple add with matrix transform"); + + // Add the parameters + for (int i = 0; i <= 2; ++i) { + dstTransform.mParams[i] += srcTransform.mParams[i] * aCount; + } + + return NS_OK; +} + +nsresult SVGTransformListSMILType::SandwichAdd( + SMILValue& aDest, const SMILValue& aValueToAdd) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + + // For <animateTransform> a sandwich add means a matrix post-multiplication + // which just means to put the additional transform on the end of the array + + TransformArray& dstTransforms(*static_cast<TransformArray*>(aDest.mU.mPtr)); + const TransformArray& srcTransforms( + *static_cast<const TransformArray*>(aValueToAdd.mU.mPtr)); + + // We should have 0 or 1 transforms in the src list. + NS_ASSERTION(srcTransforms.Length() < 2, + "Trying to do sandwich add of more than one value"); + + // The empty src transform list case only occurs in some limited circumstances + // where we create an empty 'from' value to interpolate from (e.g. + // by-animation) but then skip the interpolation step for some reason (e.g. + // because we have an indefinite duration which means we'll never get past the + // first value) and instead attempt to add that empty value to the underlying + // value. + // In any case, the expected result is that nothing is added. + if (srcTransforms.IsEmpty()) return NS_OK; + + // Stick the src on the end of the array + const SVGTransformSMILData& srcTransform = srcTransforms[0]; + SVGTransformSMILData* result = + dstTransforms.AppendElement(srcTransform, fallible); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +nsresult SVGTransformListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, + "Can't compute difference between different SMIL types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + + const TransformArray* fromTransforms = + static_cast<const TransformArray*>(aFrom.mU.mPtr); + const TransformArray* toTransforms = + static_cast<const TransformArray*>(aTo.mU.mPtr); + + // ComputeDistance is only used for calculating distances between single + // values in a values array which necessarily have the same type + // + // So we should only have one transform in each array and they should be of + // the same type + NS_ASSERTION(fromTransforms->Length() == 1, + "Wrong number of elements in from value"); + NS_ASSERTION(toTransforms->Length() == 1, + "Wrong number of elements in to value"); + + const SVGTransformSMILData& fromTransform = (*fromTransforms)[0]; + const SVGTransformSMILData& toTransform = (*toTransforms)[0]; + NS_ASSERTION(fromTransform.mTransformType == toTransform.mTransformType, + "Incompatible transform types to calculate distance between"); + + switch (fromTransform.mTransformType) { + // We adopt the SVGT1.2 notions of distance here + // See: http://www.w3.org/TR/SVGTiny12/animate.html#complexDistances + // (As discussed in bug #469040) + case SVG_TRANSFORM_TRANSLATE: + case SVG_TRANSFORM_SCALE: { + const float& a_tx = fromTransform.mParams[0]; + const float& a_ty = fromTransform.mParams[1]; + const float& b_tx = toTransform.mParams[0]; + const float& b_ty = toTransform.mParams[1]; + aDistance = sqrt(pow(a_tx - b_tx, 2) + (pow(a_ty - b_ty, 2))); + } break; + + case SVG_TRANSFORM_ROTATE: + case SVG_TRANSFORM_SKEWX: + case SVG_TRANSFORM_SKEWY: { + const float& a = fromTransform.mParams[0]; + const float& b = toTransform.mParams[0]; + aDistance = std::fabs(a - b); + } break; + + default: + NS_ERROR("Got bad transform types for calculating distances"); + aDistance = 1.0; + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult SVGTransformListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Can't interpolate between different SMIL types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected type for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const TransformArray& startTransforms = + (*static_cast<const TransformArray*>(aStartVal.mU.mPtr)); + const TransformArray& endTransforms( + *static_cast<const TransformArray*>(aEndVal.mU.mPtr)); + + // We may have 0..n transforms in the start transform array (the base + // value) but we should only have 1 transform in the end transform array + NS_ASSERTION(endTransforms.Length() == 1, + "Invalid end-point for interpolating between transform values"); + + // The end point should never be a matrix transform + const SVGTransformSMILData& endTransform = endTransforms[0]; + NS_ASSERTION(endTransform.mTransformType != SVG_TRANSFORM_MATRIX, + "End point for interpolation should not be a matrix transform"); + + // If we have 0 or more than 1 transform in the start transform array then we + // just interpolate from 0, 0, 0 + // Likewise, even if there's only 1 transform in the start transform array + // then if the type of the start transform doesn't match the end then we + // can't interpolate and should just use 0, 0, 0 + static float identityParams[3] = {0.f}; + const float* startParams = nullptr; + if (startTransforms.Length() == 1) { + const SVGTransformSMILData& startTransform = startTransforms[0]; + if (startTransform.mTransformType == endTransform.mTransformType) { + startParams = startTransform.mParams; + } + } + if (!startParams) { + startParams = identityParams; + } + + const float* endParams = endTransform.mParams; + + // Interpolate between the params + float newParams[3]; + for (int i = 0; i <= 2; ++i) { + const float& a = startParams[i]; + const float& b = endParams[i]; + newParams[i] = static_cast<float>(a + (b - a) * aUnitDistance); + } + + // Make the result + SVGTransformSMILData resultTransform(endTransform.mTransformType, newParams); + + // Clear the way for it in the result array + TransformArray& dstTransforms = + (*static_cast<TransformArray*>(aResult.mU.mPtr)); + dstTransforms.Clear(); + + // Assign the result + SVGTransformSMILData* transform = + dstTransforms.AppendElement(resultTransform, fallible); + NS_ENSURE_TRUE(transform, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +//---------------------------------------------------------------------- +// Transform array accessors + +// static +nsresult SVGTransformListSMILType::AppendTransform( + const SVGTransformSMILData& aTransform, SMILValue& aValue) { + MOZ_ASSERT(aValue.mType == Singleton(), "Unexpected SMIL value type"); + + TransformArray& transforms = *static_cast<TransformArray*>(aValue.mU.mPtr); + return transforms.AppendElement(aTransform, fallible) + ? NS_OK + : NS_ERROR_OUT_OF_MEMORY; +} + +// static +bool SVGTransformListSMILType::AppendTransforms(const SVGTransformList& aList, + SMILValue& aValue) { + MOZ_ASSERT(aValue.mType == Singleton(), "Unexpected SMIL value type"); + + TransformArray& transforms = *static_cast<TransformArray*>(aValue.mU.mPtr); + + if (!transforms.SetCapacity(transforms.Length() + aList.Length(), fallible)) + return false; + + for (uint32_t i = 0; i < aList.Length(); ++i) { + // No need to check the return value below since we have already allocated + // the necessary space + MOZ_ALWAYS_TRUE( + transforms.AppendElement(SVGTransformSMILData(aList[i]), fallible)); + } + return true; +} + +// static +bool SVGTransformListSMILType::GetTransforms( + const SMILValue& aValue, FallibleTArray<SVGTransform>& aTransforms) { + MOZ_ASSERT(aValue.mType == Singleton(), "Unexpected SMIL value type"); + + const TransformArray& smilTransforms = + *static_cast<const TransformArray*>(aValue.mU.mPtr); + + aTransforms.Clear(); + if (!aTransforms.SetCapacity(smilTransforms.Length(), fallible)) return false; + + for (uint32_t i = 0; i < smilTransforms.Length(); ++i) { + // No need to check the return value below since we have already allocated + // the necessary space + (void)aTransforms.AppendElement(smilTransforms[i].ToSVGTransform(), + fallible); + } + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransformListSMILType.h b/dom/svg/SVGTransformListSMILType.h new file mode 100644 index 0000000000..409acf5ad0 --- /dev/null +++ b/dom/svg/SVGTransformListSMILType.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMLISTSMILTYPE_H_ +#define DOM_SVG_SVGTRANSFORMLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" +#include "nsTArray.h" + +namespace mozilla { + +class SMILValue; +class SVGTransform; +class SVGTransformList; +class SVGTransformSMILData; + +//////////////////////////////////////////////////////////////////////// +// SVGTransformListSMILType +// +// Operations for animating an SVGTransformList. +// +// This class is confused somewhat by the fact that: +// (i) An <animateTransform> element animates an SVGTransformList +// (ii) BUT <animateTransform> only allows the user to specify animation values +// for an SVGTransform +// +// This may be rectified in a future edition of SVG but for now it means that +// the underlying value of an animation may be something of the form: +// +// rotate(90) scale(2) skewX(50) +// +// BUT the animation values can only ever be SINGLE transform operations such as +// +// rotate(90) +// +// (actually the syntax here is: +// <animateTransform type="rotate" from="0" to="90"... +// OR maybe +// <animateTransform type="rotate" values="0; 90; 30; 50"... +// OR even (with a rotation centre) +// <animateTransform type="rotate" values="0 50 20; 30 50 20; 70 0 0"...) +// +// This has many implications for the number of elements we expect in the +// transform array supplied for each operation. +// +// For example, Add() only ever operates on the values specified on an +// <animateTransform> element and so these values can only ever contain 0 or +// 1 TRANSFORM elements as the syntax doesn't allow more. (A "value" here is +// a single element in the values array such as "0 50 20" above.) +// +// Likewise ComputeDistance() only ever operates within the values specified on +// an <animateTransform> element so similar conditions hold. +// +// However, SandwichAdd() combines with a base value which may contain 0..n +// transforms either because the base value of the attribute specifies a series +// of transforms, e.g. +// +// <circle transform="translate(30) rotate(50)"... > +// <animateTransform.../> +// </circle> +// +// or because several animations target the same attribute and are additive and +// so are simply appended on to the transformation array, e.g. +// +// <circle transform="translate(30)"... > +// <animateTransform type="rotate" additive="sum".../> +// <animateTransform type="scale" additive="sum".../> +// <animateTransform type="skewX" additive="sum".../> +// </circle> +// +// Similar conditions hold for Interpolate() which in cases such as to-animation +// may have use a start-value the base value of the target attribute (which as +// we have seen above can contain 0..n elements) whilst the end-value comes from +// the <animateTransform> and so can only hold 1 transform. +// +class SVGTransformListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGTransformListSMILType* Singleton() { + static SVGTransformListSMILType sSingleton; + return &sSingleton; + } + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult SandwichAdd(SMILValue& aDest, + const SMILValue& aValueToAdd) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + public: + // Transform array accessors + // ------------------------- + static nsresult AppendTransform(const SVGTransformSMILData& aTransform, + SMILValue& aValue); + static bool AppendTransforms(const SVGTransformList& aList, + SMILValue& aValue); + static bool GetTransforms(const SMILValue& aValue, + FallibleTArray<SVGTransform>& aTransforms); + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGTransformListSMILType() = default; +}; + +} // end namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORMLISTSMILTYPE_H_ diff --git a/dom/svg/SVGTransformableElement.cpp b/dom/svg/SVGTransformableElement.cpp new file mode 100644 index 0000000000..2ee781f063 --- /dev/null +++ b/dom/svg/SVGTransformableElement.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformableElement.h" + +#include "DOMSVGAnimatedTransformList.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "nsContentUtils.h" +#include "nsIFrame.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +already_AddRefed<DOMSVGAnimatedTransformList> +SVGTransformableElement::Transform() { + // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList + // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so: + return DOMSVGAnimatedTransformList::GetDOMWrapper( + GetAnimatedTransformList(DO_ALLOCATE), this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGTransformableElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sColorMap, sFillStrokeMap, + sGraphicsMap}; + + return FindAttributeDependence(name, map) || + SVGElement::IsAttributeMapped(name); +} + +nsChangeHint SVGTransformableElement::GetAttributeChangeHint( + const nsAtom* aAttribute, int32_t aModType) const { + nsChangeHint retval = + SVGElement::GetAttributeChangeHint(aAttribute, aModType); + if (aAttribute == nsGkAtoms::transform || + aAttribute == nsGkAtoms::mozAnimateMotionDummyAttr) { + nsIFrame* frame = + const_cast<SVGTransformableElement*>(this)->GetPrimaryFrame(); + retval |= nsChangeHint_InvalidateRenderingObservers; + if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { + return retval; + } + + bool isAdditionOrRemoval = false; + if (aModType == MutationEvent_Binding::ADDITION || + aModType == MutationEvent_Binding::REMOVAL) { + isAdditionOrRemoval = true; + } else { + MOZ_ASSERT(aModType == MutationEvent_Binding::MODIFICATION, + "Unknown modification type."); + if (!mTransforms || !mTransforms->HasTransform()) { + // New value is empty, treat as removal. + // FIXME: Should we just rely on CreatedOrRemovedOnLastChange? + isAdditionOrRemoval = true; + } else if (mTransforms->CreatedOrRemovedOnLastChange()) { + // Old value was empty, treat as addition. + isAdditionOrRemoval = true; + } + } + + if (isAdditionOrRemoval) { + retval |= nsChangeHint_ComprehensiveAddOrRemoveTransform; + } else { + // We just assume the old and new transforms are different. + retval |= nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateTransformLayer; + } + } + return retval; +} + +bool SVGTransformableElement::IsEventAttributeNameInternal(nsAtom* aName) { + return nsContentUtils::IsEventAttributeName(aName, EventNameType_SVGGraphic); +} + +//---------------------------------------------------------------------- +// SVGElement overrides + +gfxMatrix SVGTransformableElement::PrependLocalTransformsTo( + const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const { + if (aWhich == eChildToUserSpace) { + // We don't have any eUserSpaceToParent transforms. (Sub-classes that do + // must override this function and handle that themselves.) + return aMatrix; + } + return GetUserToParentTransform(mAnimateMotionTransform.get(), + mTransforms.get()) * + aMatrix; +} + +const gfx::Matrix* SVGTransformableElement::GetAnimateMotionTransform() const { + return mAnimateMotionTransform.get(); +} + +void SVGTransformableElement::SetAnimateMotionTransform( + const gfx::Matrix* aMatrix) { + if ((!aMatrix && !mAnimateMotionTransform) || + (aMatrix && mAnimateMotionTransform && + aMatrix->FuzzyEquals(*mAnimateMotionTransform))) { + return; + } + bool transformSet = mTransforms && mTransforms->IsExplicitlySet(); + bool prevSet = mAnimateMotionTransform || transformSet; + mAnimateMotionTransform = + aMatrix ? MakeUnique<gfx::Matrix>(*aMatrix) : nullptr; + bool nowSet = mAnimateMotionTransform || transformSet; + int32_t modType; + if (prevSet && !nowSet) { + modType = MutationEvent_Binding::REMOVAL; + } else if (!prevSet && nowSet) { + modType = MutationEvent_Binding::ADDITION; + } else { + modType = MutationEvent_Binding::MODIFICATION; + } + DidAnimateTransformList(modType); + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + // If the result of this transform and any other transforms on this frame + // is the identity matrix, then DoApplyRenderingChangeToTree won't handle + // our nsChangeHint_UpdateTransformLayer hint since aFrame->IsTransformed() + // will return false. That's fine, but we still need to schedule a repaint, + // and that won't otherwise happen. Since it's cheap to call SchedulePaint, + // we don't bother to check IsTransformed(). + frame->SchedulePaint(); + } +} + +SVGAnimatedTransformList* SVGTransformableElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!mTransforms && (aFlags & DO_ALLOCATE)) { + mTransforms = MakeUnique<SVGAnimatedTransformList>(); + } + return mTransforms.get(); +} + +/* static */ +gfxMatrix SVGTransformableElement::GetUserToParentTransform( + const gfx::Matrix* aAnimateMotionTransform, + const SVGAnimatedTransformList* aTransforms) { + gfxMatrix result; + + if (aAnimateMotionTransform) { + result.PreMultiply(ThebesMatrix(*aAnimateMotionTransform)); + } + + if (aTransforms) { + result.PreMultiply(aTransforms->GetAnimValue().GetConsolidationMatrix()); + } + + return result; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTransformableElement.h b/dom/svg/SVGTransformableElement.h new file mode 100644 index 0000000000..13c3b6cce8 --- /dev/null +++ b/dom/svg/SVGTransformableElement.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMABLEELEMENT_H_ +#define DOM_SVG_SVGTRANSFORMABLEELEMENT_H_ + +#include "gfxMatrix.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGAnimatedTransformList.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedTransformList; +class SVGGraphicsElement; +class SVGMatrix; +class SVGRect; +struct SVGBoundingBoxOptions; + +class SVGTransformableElement : public SVGElement { + public: + explicit SVGTransformableElement(already_AddRefed<dom::NodeInfo>&& aNodeInfo) + : SVGElement(std::move(aNodeInfo)) {} + virtual ~SVGTransformableElement() = default; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override = 0; + + // WebIDL + already_AddRefed<DOMSVGAnimatedTransformList> Transform(); + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute, + int32_t aModType) const override; + + // SVGElement overrides + bool IsEventAttributeNameInternal(nsAtom* aName) override; + + gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; + const gfx::Matrix* GetAnimateMotionTransform() const override; + void SetAnimateMotionTransform(const gfx::Matrix* aMatrix) override; + + SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + nsStaticAtom* GetTransformListAttrName() const override { + return nsGkAtoms::transform; + } + + bool IsTransformable() override { return true; } + + protected: + /** + * Helper for overrides of PrependLocalTransformsTo. If both arguments are + * provided they are multiplied in the order in which the arguments appear, + * and the result is returned. If neither argument is provided, the identity + * matrix is returned. If only one argument is provided its transform is + * returned. + */ + static gfxMatrix GetUserToParentTransform( + const gfx::Matrix* aAnimateMotionTransform, + const SVGAnimatedTransformList* aTransforms); + + UniquePtr<SVGAnimatedTransformList> mTransforms; + + // XXX maybe move this to property table, to save space on un-animated elems? + UniquePtr<gfx::Matrix> mAnimateMotionTransform; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTRANSFORMABLEELEMENT_H_ diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp new file mode 100644 index 0000000000..b2382baef0 --- /dev/null +++ b/dom/svg/SVGUseElement.cpp @@ -0,0 +1,642 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGUseElement.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_svg.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUseFrame.h" +#include "mozilla/URLExtraData.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ShadowIncludingTreeIterator.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGUseElementBinding.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" +#include "nsIURI.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Use) + +namespace mozilla::dom { + +JSObject* SVGUseElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGUseElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//////////////////////////////////////////////////////////////////////// +// implementation + +SVGElement::LengthInfo SVGUseElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +SVGElement::StringInfo SVGUseElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGUseElement) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement, + SVGUseElementBase) + nsAutoScriptBlocker scriptBlocker; + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal) + tmp->UnlinkSource(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement, + SVGUseElementBase) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal) + tmp->mReferencedElementTracker.Traverse(&cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGUseElement, SVGUseElementBase, + nsIMutationObserver) + +//---------------------------------------------------------------------- +// Implementation + +SVGUseElement::SVGUseElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGUseElementBase(std::move(aNodeInfo)), + mReferencedElementTracker(this) {} + +SVGUseElement::~SVGUseElement() { + UnlinkSource(); + MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->SVGUseElementNeedsShadowTreeUpdate(*this), + "Dying without unbinding?"); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +bool SVGUseElement::IsNodeOfType(uint32_t aFlags) const { + return !(aFlags & ~eUSE_TARGET); +} + +void SVGUseElement::ProcessAttributeChange(int32_t aNamespaceID, + nsAtom* aAttribute) { + if (aNamespaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) { + const bool hadValidDimensions = HasValidDimensions(); + const bool isUsed = OurWidthAndHeightAreUsed(); + if (isUsed) { + SyncWidthOrHeight(aAttribute); + } + + if (auto* frame = GetFrame()) { + frame->DimensionAttributeChanged(hadValidDimensions, isUsed); + } + } + } + + if ((aNamespaceID == kNameSpaceID_XLink || + aNamespaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // We're changing our nature, clear out the clone information. + if (auto* frame = GetFrame()) { + frame->HrefChanged(); + } + mOriginal = nullptr; + UnlinkSource(); + TriggerReclone(); + } +} + +nsresult SVGUseElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + ProcessAttributeChange(aNamespaceID, aAttribute); + return SVGUseElementBase::AfterSetAttr(aNamespaceID, aAttribute, aValue, + aOldValue, aSubjectPrincipal, aNotify); +} + +nsresult SVGUseElement::Clone(dom::NodeInfo* aNodeInfo, + nsINode** aResult) const { + *aResult = nullptr; + SVGUseElement* it = + new (aNodeInfo->NodeInfoManager()) SVGUseElement(do_AddRef(aNodeInfo)); + + nsCOMPtr<nsINode> kungFuDeathGrip(it); + nsresult rv1 = it->Init(); + nsresult rv2 = const_cast<SVGUseElement*>(this)->CopyInnerTo(it); + + // SVGUseElement specific portion - record who we cloned from + it->mOriginal = const_cast<SVGUseElement*>(this); + + if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { + kungFuDeathGrip.swap(*aResult); + } + + return NS_FAILED(rv1) ? rv1 : rv2; +} + +nsresult SVGUseElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGUseElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + TriggerReclone(); + return NS_OK; +} + +void SVGUseElement::UnbindFromTree(bool aNullParent) { + SVGUseElementBase::UnbindFromTree(aNullParent); + OwnerDoc()->UnscheduleSVGUseElementShadowTreeUpdate(*this); +} + +already_AddRefed<DOMSVGAnimatedString> SVGUseElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed<DOMSVGAnimatedLength> SVGUseElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// nsIMutationObserver methods + +void SVGUseElement::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aContent)) { + TriggerReclone(); + } +} + +void SVGUseElement::AttributeChanged(Element* aElement, int32_t aNamespaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aElement)) { + TriggerReclone(); + } +} + +void SVGUseElement::ContentAppended(nsIContent* aFirstNewContent) { + // FIXME(emilio, bug 1442336): Why does this check the parent but + // ContentInserted the child? + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aFirstNewContent->GetParent())) { + TriggerReclone(); + } +} + +void SVGUseElement::ContentInserted(nsIContent* aChild) { + // FIXME(emilio, bug 1442336): Why does this check the child but + // ContentAppended the parent? + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aChild)) { + TriggerReclone(); + } +} + +void SVGUseElement::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aChild)) { + TriggerReclone(); + } +} + +void SVGUseElement::NodeWillBeDestroyed(nsINode* aNode) { + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + UnlinkSource(); +} + +// Returns whether this node could ever be displayed. +static bool NodeCouldBeRendered(const nsINode& aNode) { + if (aNode.IsSVGElement(nsGkAtoms::symbol)) { + // Only <symbol> elements in the root of a <svg:use> shadow tree are + // displayed. + auto* shadowRoot = ShadowRoot::FromNodeOrNull(aNode.GetParentNode()); + return shadowRoot && shadowRoot->Host()->IsSVGElement(nsGkAtoms::use); + } + // TODO: Do we have other cases we can optimize out easily? + return true; +} + +// Circular loop detection, plus detection of whether this shadow tree is +// rendered at all. +auto SVGUseElement::ScanAncestors(const Element& aTarget) const -> ScanResult { + if (&aTarget == this) { + return ScanResult::CyclicReference; + } + if (mOriginal && + mOriginal->ScanAncestors(aTarget) == ScanResult::CyclicReference) { + return ScanResult::CyclicReference; + } + auto result = ScanResult::Ok; + for (nsINode* parent = GetParentOrShadowHostNode(); parent; + parent = parent->GetParentOrShadowHostNode()) { + if (parent == &aTarget) { + return ScanResult::CyclicReference; + } + if (auto* use = SVGUseElement::FromNode(*parent)) { + if (mOriginal && use->mOriginal == mOriginal) { + return ScanResult::CyclicReference; + } + } + // Do we have other similar cases we can optimize out easily? + if (!NodeCouldBeRendered(*parent)) { + // NOTE(emilio): We can't just return here. If we're cyclic, we need to + // know. + result = ScanResult::Invisible; + } + } + return result; +} + +//---------------------------------------------------------------------- + +static bool IsForbiddenUseNode(const nsINode& aNode) { + if (!aNode.IsElement()) { + return false; + } + const auto* svg = SVGElement::FromNode(aNode); + return !svg || !svg->IsSVGGraphicsElement(); +} + +static void CollectForbiddenNodes(Element& aRoot, + nsTArray<RefPtr<nsINode>>& aNodes) { + auto iter = dom::ShadowIncludingTreeIterator(aRoot); + while (iter) { + nsINode* node = *iter; + if (IsForbiddenUseNode(*node)) { + aNodes.AppendElement(node); + iter.SkipChildren(); + continue; + } + ++iter; + } +} + +// SVG1 restricted <use> trees to SVGGraphicsElements. +// https://www.w3.org/TR/SVG11/struct.html#UseElement: +// +// Any ‘svg’, ‘symbol’, ‘g’, graphics element or other ‘use’ is potentially a +// template object that can be re-used (i.e., "instanced") in the SVG +// document via a ‘use’ element. The ‘use’ element references another element +// and indicates that the graphical contents of that element is +// included/drawn at that given point in the document. +// +// SVG2 doesn't have that same restriction. +// https://www.w3.org/TR/SVG2/struct.html#UseShadowTree: +// +// Previous versions of SVG restricted the contents of the shadow tree to SVG +// graphics elements. This specification allows any valid SVG document +// subtree to be cloned. Cloning non-graphical content, however, will not +// usually have any visible effect. +// +// But it's pretty ambiguous as to what the behavior should be for some +// elements, because <script> is inert, but <iframe> is not, see: +// https://github.com/w3c/svgwg/issues/876 +// +// So, fairly confusing, all-in-all. +static void RemoveForbiddenNodes(Element& aRoot, bool aIsCrossDocument) { + switch (StaticPrefs::svg_use_element_graphics_element_restrictions()) { + case 0: + return; + case 1: + if (!aIsCrossDocument) { + return; + } + break; + default: + break; + } + + AutoTArray<RefPtr<nsINode>, 10> unsafeNodes; + CollectForbiddenNodes(aRoot, unsafeNodes); + for (auto& unsafeNode : unsafeNodes) { + unsafeNode->Remove(); + } +} + +void SVGUseElement::UpdateShadowTree() { + MOZ_ASSERT(IsInComposedDoc()); + + if (mReferencedElementTracker.get()) { + mReferencedElementTracker.get()->RemoveMutationObserver(this); + } + + LookupHref(); + + RefPtr<ShadowRoot> shadow = GetShadowRoot(); + if (!shadow) { + shadow = AttachShadowWithoutNameChecks(ShadowRootMode::Closed); + } + MOZ_ASSERT(shadow); + + Element* targetElement = mReferencedElementTracker.get(); + RefPtr<Element> newElement; + + auto UpdateShadowTree = mozilla::MakeScopeExit([&]() { + nsIContent* firstChild = shadow->GetFirstChild(); + if (firstChild) { + MOZ_ASSERT(!firstChild->GetNextSibling()); + shadow->RemoveChildNode(firstChild, /* aNotify = */ true); + } + + if (newElement) { + shadow->AppendChildTo(newElement, /* aNotify = */ true, IgnoreErrors()); + } + }); + + // make sure target is valid type for <use> + if (!targetElement || !targetElement->IsNodeOfType(nsINode::eUSE_TARGET)) { + return; + } + + if (ScanAncestors(*targetElement) != ScanResult::Ok) { + return; + } + + nsCOMPtr<nsIURI> baseURI = targetElement->GetBaseURI(); + if (!baseURI) { + return; + } + + { + const bool isCrossDocument = targetElement->OwnerDoc() != OwnerDoc(); + + nsNodeInfoManager* nodeInfoManager = + isCrossDocument ? OwnerDoc()->NodeInfoManager() : nullptr; + + nsCOMPtr<nsINode> newNode = + targetElement->Clone(true, nodeInfoManager, IgnoreErrors()); + if (!newNode) { + return; + } + + MOZ_ASSERT(newNode->IsElement()); + newElement = newNode.forget().downcast<Element>(); + RemoveForbiddenNodes(*newElement, isCrossDocument); + } + + if (newElement->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol)) { + auto* newSVGElement = static_cast<SVGElement*>(newElement.get()); + if (mLengthAttributes[ATTR_WIDTH].IsExplicitlySet()) + newSVGElement->SetLength(nsGkAtoms::width, mLengthAttributes[ATTR_WIDTH]); + if (mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet()) + newSVGElement->SetLength(nsGkAtoms::height, + mLengthAttributes[ATTR_HEIGHT]); + } + + // Bug 1415044 the specs do not say which referrer information we should use. + // This may change if there's any spec comes out. + auto referrerInfo = MakeRefPtr<ReferrerInfo>(*this); + mContentURLData = new URLExtraData(baseURI.forget(), referrerInfo.forget(), + do_AddRef(NodePrincipal())); + + targetElement->AddMutationObserver(this); +} + +nsIURI* SVGUseElement::GetSourceDocURI() { + nsIContent* targetElement = mReferencedElementTracker.get(); + if (!targetElement) { + return nullptr; + } + + return targetElement->OwnerDoc()->GetDocumentURI(); +} + +const Encoding* SVGUseElement::GetSourceDocCharacterSet() { + nsIContent* targetElement = mReferencedElementTracker.get(); + if (!targetElement) { + return nullptr; + } + + return targetElement->OwnerDoc()->GetDocumentCharacterSet(); +} + +static nsINode* GetClonedChild(const SVGUseElement& aUseElement) { + const ShadowRoot* shadow = aUseElement.GetShadowRoot(); + return shadow ? shadow->GetFirstChild() : nullptr; +} + +bool SVGUseElement::OurWidthAndHeightAreUsed() const { + nsINode* clonedChild = GetClonedChild(*this); + return clonedChild && + clonedChild->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol); +} + +//---------------------------------------------------------------------- +// implementation helpers + +void SVGUseElement::SyncWidthOrHeight(nsAtom* aName) { + NS_ASSERTION(aName == nsGkAtoms::width || aName == nsGkAtoms::height, + "The clue is in the function name"); + NS_ASSERTION(OurWidthAndHeightAreUsed(), "Don't call this"); + + if (!OurWidthAndHeightAreUsed()) { + return; + } + + auto* target = SVGElement::FromNode(GetClonedChild(*this)); + uint32_t index = + sLengthInfo[ATTR_WIDTH].mName == aName ? ATTR_WIDTH : ATTR_HEIGHT; + + if (mLengthAttributes[index].IsExplicitlySet()) { + target->SetLength(aName, mLengthAttributes[index]); + return; + } + if (target->IsSVGElement(nsGkAtoms::svg)) { + // Our width/height attribute is now no longer explicitly set, so we + // need to revert the clone's width/height to the width/height of the + // content that's being cloned. + TriggerReclone(); + return; + } + // Our width/height attribute is now no longer explicitly set, so we + // need to set the value to 100% + SVGAnimatedLength length; + length.Init(SVGContentUtils::XY, 0xff, 100, + SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE); + target->SetLength(aName, length); +} + +void SVGUseElement::LookupHref() { + nsAutoString href; + if (mStringAttributes[HREF].IsExplicitlySet()) { + mStringAttributes[HREF].GetAnimValue(href, this); + } else { + mStringAttributes[XLINK_HREF].GetAnimValue(href, this); + } + + if (href.IsEmpty()) { + return; + } + + nsCOMPtr<nsIURI> originURI = + mOriginal ? mOriginal->GetBaseURI() : GetBaseURI(); + nsCOMPtr<nsIURI> baseURI = + nsContentUtils::IsLocalRefURL(href) + ? SVGObserverUtils::GetBaseURLForLocalRef(this, originURI) + : originURI; + + nsCOMPtr<nsIURI> targetURI; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, + GetComposedDoc(), baseURI); + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForSVGResources(OwnerDoc()); + + mReferencedElementTracker.ResetToURIFragmentID(this, targetURI, referrerInfo); +} + +void SVGUseElement::TriggerReclone() { + if (Document* doc = GetComposedDoc()) { + doc->ScheduleSVGUseElementShadowTreeUpdate(*this); + } +} + +void SVGUseElement::UnlinkSource() { + if (mReferencedElementTracker.get()) { + mReferencedElementTracker.get()->RemoveMutationObserver(this); + } + mReferencedElementTracker.Unlink(); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +gfxMatrix SVGUseElement::PrependLocalTransformsTo( + const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const { + // 'transform' attribute: + gfxMatrix userToParent; + + if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) { + userToParent = GetUserToParentTransform(mAnimateMotionTransform.get(), + mTransforms.get()); + if (aWhich == eUserSpaceToParent) { + return userToParent * aMatrix; + } + } + + // our 'x' and 'y' attributes: + float x, y; + if (!SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(this, &x, &y)) { + const_cast<SVGUseElement*>(this)->GetAnimatedLengthValues(&x, &y, nullptr); + } + + gfxMatrix childToUser = gfxMatrix::Translation(x, y); + + if (aWhich == eAllTransforms) { + return childToUser * userToParent * aMatrix; + } + + MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes"); + + // The following may look broken because pre-multiplying our eChildToUserSpace + // transform with another matrix without including our eUserSpaceToParent + // transform between the two wouldn't make sense. We don't expect that to + // ever happen though. We get here either when the identity matrix has been + // passed because our caller just wants our eChildToUserSpace transform, or + // when our eUserSpaceToParent transform has already been multiplied into the + // matrix that our caller passes (such as when we're called from PaintSVG). + return childToUser * aMatrix; +} + +/* virtual */ +bool SVGUseElement::HasValidDimensions() const { + if (!OurWidthAndHeightAreUsed()) { + return true; + } + + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGUseElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::StringAttributesInfo SVGUseElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +SVGUseFrame* SVGUseElement::GetFrame() const { + nsIFrame* frame = GetPrimaryFrame(); + // We might be a plain SVGContainerFrame if we didn't pass the conditional + // processing checks. + if (!frame || !frame->IsSVGUseFrame()) { + MOZ_ASSERT_IF(frame, frame->Type() == LayoutFrameType::None); + return nullptr; + } + return static_cast<SVGUseFrame*>(frame); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGUseElement::IsAttributeMapped(const nsAtom* name) const { + static const MappedAttributeEntry* const map[] = {sFEFloodMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return name == nsGkAtoms::x || name == nsGkAtoms::y || + FindAttributeDependence(name, map) || + SVGUseElementBase::IsAttributeMapped(name); +} + +nsCSSPropertyID SVGUseElement::GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_X: + return eCSSProperty_x; + case ATTR_Y: + return eCSSProperty_y; + default: + // Currently we don't map width or height to style + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGUseElement.h b/dom/svg/SVGUseElement.h new file mode 100644 index 0000000000..9c71a45be0 --- /dev/null +++ b/dom/svg/SVGUseElement.h @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGUSEELEMENT_H_ +#define DOM_SVG_SVGUSEELEMENT_H_ + +#include "mozilla/dom/FromParser.h" +#include "mozilla/dom/IDTracker.h" +#include "mozilla/dom/SVGGraphicsElement.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsStubMutationObserver.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedString.h" +#include "nsTArray.h" + +class nsIContent; + +nsresult NS_NewSVGSVGElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser); +nsresult NS_NewSVGUseElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class Encoding; +class SVGUseFrame; +struct URLExtraData; + +namespace dom { + +using SVGUseElementBase = SVGGraphicsElement; + +class SVGUseElement final : public SVGUseElementBase, + public nsStubMutationObserver { + friend class mozilla::SVGUseFrame; + + protected: + friend nsresult(::NS_NewSVGUseElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + explicit SVGUseElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + virtual ~SVGUseElement(); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + NS_IMPL_FROMNODE_WITH_TAG(SVGUseElement, kNameSpaceID_SVG, use) + + bool IsNodeOfType(uint32_t aFlags) const override; + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent = true) override; + + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGUseElement, SVGUseElementBase) + + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED + + // SVGElement specializations: + gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; + bool HasValidDimensions() const override; + + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed<DOMSVGAnimatedString> Href(); + already_AddRefed<DOMSVGAnimatedLength> X(); + already_AddRefed<DOMSVGAnimatedLength> Y(); + already_AddRefed<DOMSVGAnimatedLength> Width(); + already_AddRefed<DOMSVGAnimatedLength> Height(); + + nsIURI* GetSourceDocURI(); + const Encoding* GetSourceDocCharacterSet(); + URLExtraData* GetContentURLData() const { return mContentURLData; } + + // Updates the internal shadow tree to be an up-to-date clone of the + // referenced element. + void UpdateShadowTree(); + + // Shared code between AfterSetAttr and SVGUseFrame::AttributeChanged. + // + // This is needed because SMIL doesn't go through AfterSetAttr unfortunately. + void ProcessAttributeChange(int32_t aNamespaceID, nsAtom* aAttribute); + + nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) final; + + protected: + // Information from walking our ancestors and a given target. + enum class ScanResult { + // Nothing that should stop us from rendering the shadow tree. + Ok, + // We're never going to be displayed, so no point in updating the shadow + // tree. + // + // However if we're referenced from another tree that tree may need to be + // rendered. + Invisible, + // We're a cyclic reference to either an ancestor or another shadow tree. We + // shouldn't render this <use> element. + CyclicReference, + }; + ScanResult ScanAncestors(const Element& aTarget) const; + + /** + * Helper that provides a reference to the element with the ID that is + * referenced by the 'use' element's 'href' attribute, and that will update + * the 'use' element if the element that that ID identifies changes to a + * different element (or none). + */ + class ElementTracker final : public IDTracker { + public: + explicit ElementTracker(SVGUseElement* aOwningUseElement) + : mOwningUseElement(aOwningUseElement) {} + + private: + void ElementChanged(Element* aFrom, Element* aTo) override { + IDTracker::ElementChanged(aFrom, aTo); + if (aFrom) { + aFrom->RemoveMutationObserver(mOwningUseElement); + } + mOwningUseElement->TriggerReclone(); + } + + SVGUseElement* mOwningUseElement; + }; + + SVGUseFrame* GetFrame() const; + + LengthAttributesInfo GetLengthInfo() override; + StringAttributesInfo GetStringInfo() override; + + /** + * Returns true if our width and height should be used, or false if they + * should be ignored. As per the spec, this depends on the type of the + * element that we're referencing. + */ + bool OurWidthAndHeightAreUsed() const; + void SyncWidthOrHeight(nsAtom* aName); + void LookupHref(); + void TriggerReclone(); + void UnlinkSource(); + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + RefPtr<SVGUseElement> mOriginal; // if we've been cloned, our "real" copy + ElementTracker mReferencedElementTracker; + RefPtr<URLExtraData> mContentURLData; // URL data for its anonymous content +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGUSEELEMENT_H_ diff --git a/dom/svg/SVGViewBoxSMILType.cpp b/dom/svg/SVGViewBoxSMILType.cpp new file mode 100644 index 0000000000..8952308162 --- /dev/null +++ b/dom/svg/SVGViewBoxSMILType.cpp @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGViewBoxSMILType.h" + +#include "mozilla/SMILValue.h" +#include "nsDebug.h" +#include "SVGAnimatedViewBox.h" +#include <math.h> + +namespace mozilla { + +/*static*/ +SVGViewBoxSMILType SVGViewBoxSMILType::sSingleton; + +void SVGViewBoxSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + aValue.mU.mPtr = new SVGViewBox(); + aValue.mType = this; +} + +void SVGViewBoxSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value"); + delete static_cast<SVGViewBox*>(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGViewBoxSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGViewBox* src = static_cast<const SVGViewBox*>(aSrc.mU.mPtr); + SVGViewBox* dst = static_cast<SVGViewBox*>(aDest.mU.mPtr); + *dst = *src; + return NS_OK; +} + +bool SVGViewBoxSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + const SVGViewBox* leftBox = static_cast<const SVGViewBox*>(aLeft.mU.mPtr); + const SVGViewBox* rightBox = static_cast<SVGViewBox*>(aRight.mU.mPtr); + return *leftBox == *rightBox; +} + +nsresult SVGViewBoxSMILType::Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aValueToAdd.mType == aDest.mType, "Trying to add invalid types"); + MOZ_ASSERT(aValueToAdd.mType == this, "Unexpected source type"); + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=541884#c3 and the two + // comments that follow that one for arguments for and against allowing + // viewBox to be additive. + + return NS_ERROR_FAILURE; +} + +nsresult SVGViewBoxSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); + + const SVGViewBox* from = static_cast<const SVGViewBox*>(aFrom.mU.mPtr); + const SVGViewBox* to = static_cast<const SVGViewBox*>(aTo.mU.mPtr); + + if (from->none || to->none) { + return NS_ERROR_FAILURE; + } + + // We use the distances between the edges rather than the difference between + // the x, y, width and height for the "distance". This is necessary in + // order for the "distance" result that we calculate to be the same for a + // given change in the left side as it is for an equal change in the opposite + // side. See https://bugzilla.mozilla.org/show_bug.cgi?id=541884#c12 + + float dLeft = to->x - from->x; + float dTop = to->y - from->y; + float dRight = (to->x + to->width) - (from->x + from->width); + float dBottom = (to->y + to->height) - (from->y + from->height); + + aDistance = std::sqrt(dLeft * dLeft + dTop * dTop + dRight * dRight + + dBottom * dBottom); + + return NS_OK; +} + +nsresult SVGViewBoxSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGViewBox* start = static_cast<const SVGViewBox*>(aStartVal.mU.mPtr); + const SVGViewBox* end = static_cast<const SVGViewBox*>(aEndVal.mU.mPtr); + + if (start->none || end->none) { + return NS_ERROR_FAILURE; + } + + SVGViewBox* current = static_cast<SVGViewBox*>(aResult.mU.mPtr); + + float x = (start->x + (end->x - start->x) * aUnitDistance); + float y = (start->y + (end->y - start->y) * aUnitDistance); + float width = (start->width + (end->width - start->width) * aUnitDistance); + float height = + (start->height + (end->height - start->height) * aUnitDistance); + + *current = SVGViewBox(x, y, width, height); + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGViewBoxSMILType.h b/dom/svg/SVGViewBoxSMILType.h new file mode 100644 index 0000000000..36b71d023b --- /dev/null +++ b/dom/svg/SVGViewBoxSMILType.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGVIEWBOXSMILTYPE_H_ +#define DOM_SVG_SVGVIEWBOXSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +class SVGViewBoxSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGViewBoxSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue&) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGViewBoxSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGVIEWBOXSMILTYPE_H_ diff --git a/dom/svg/SVGViewElement.cpp b/dom/svg/SVGViewElement.cpp new file mode 100644 index 0000000000..b7ea5a7541 --- /dev/null +++ b/dom/svg/SVGViewElement.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGViewElement.h" +#include "mozilla/dom/SVGViewElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(View) + +namespace mozilla::dom { + +using namespace SVGViewElement_Binding; + +JSObject* SVGViewElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return SVGViewElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGEnumMapping SVGViewElement::sZoomAndPanMap[] = { + {nsGkAtoms::disable, SVG_ZOOMANDPAN_DISABLE}, + {nsGkAtoms::magnify, SVG_ZOOMANDPAN_MAGNIFY}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGViewElement::sEnumInfo[1] = { + {nsGkAtoms::zoomAndPan, sZoomAndPanMap, SVG_ZOOMANDPAN_MAGNIFY}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGViewElement::SVGViewElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGViewElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGViewElement) + +void SVGViewElement::SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv) { + if (aZoomAndPan == SVG_ZOOMANDPAN_DISABLE || + aZoomAndPan == SVG_ZOOMANDPAN_MAGNIFY) { + ErrorResult nestedRv; + mEnumAttributes[ZOOMANDPAN].SetBaseValue(aZoomAndPan, this, nestedRv); + MOZ_ASSERT(!nestedRv.Failed(), + "We already validated our aZoomAndPan value!"); + return; + } + + rv.ThrowRangeError<MSG_INVALID_ZOOMANDPAN_VALUE_ERROR>(); +} + +//---------------------------------------------------------------------- + +already_AddRefed<SVGAnimatedRect> SVGViewElement::ViewBox() { + return mViewBox.ToSVGAnimatedRect(this); +} + +already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> +SVGViewElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::EnumAttributesInfo SVGViewElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGAnimatedViewBox* SVGViewElement::GetAnimatedViewBox() { return &mViewBox; } + +SVGAnimatedPreserveAspectRatio* +SVGViewElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGViewElement.h b/dom/svg/SVGViewElement.h new file mode 100644 index 0000000000..ec3578d7fc --- /dev/null +++ b/dom/svg/SVGViewElement.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGVIEWELEMENT_H_ +#define DOM_SVG_SVGVIEWELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedViewBox.h" +#include "SVGStringList.h" +#include "mozilla/dom/SVGElement.h" + +nsresult NS_NewSVGViewElement( + nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + +namespace mozilla { +class SVGFragmentIdentifier; +class SVGOuterSVGFrame; + +namespace dom { +class SVGViewportElement; + +using SVGViewElementBase = SVGElement; + +class SVGViewElement : public SVGViewElementBase { + protected: + friend class mozilla::SVGFragmentIdentifier; + friend class mozilla::SVGOuterSVGFrame; + friend class SVGSVGElement; + friend class SVGViewportElement; + explicit SVGViewElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + friend nsresult(::NS_NewSVGViewElement( + nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)); + JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + uint16_t ZoomAndPan() { return mEnumAttributes[ZOOMANDPAN].GetAnimValue(); } + void SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv); + already_AddRefed<SVGAnimatedRect> ViewBox(); + already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> PreserveAspectRatio(); + + private: + // SVGElement overrides + + EnumAttributesInfo GetEnumInfo() override; + + enum { ZOOMANDPAN }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sZoomAndPanMap[]; + static EnumInfo sEnumInfo[1]; + + SVGAnimatedViewBox* GetAnimatedViewBox() override; + virtual SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() + override; + + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGVIEWELEMENT_H_ diff --git a/dom/svg/SVGViewportElement.cpp b/dom/svg/SVGViewportElement.cpp new file mode 100644 index 0000000000..eb8e66959d --- /dev/null +++ b/dom/svg/SVGViewportElement.cpp @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGViewportElement.h" + +#include <stdint.h> +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "mozilla/SMILTypes.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGViewElement.h" + +#include "DOMSVGLength.h" +#include "DOMSVGPoint.h" +#include "nsContentUtils.h" +#include "nsFrameSelection.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" +#include "nsStyleUtil.h" + +#include <algorithm> +#include "prtime.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +SVGElement::LengthInfo SVGViewportElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGViewportElement::SVGViewportElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGraphicsElement(std::move(aNodeInfo)), + mViewportWidth(0), + mViewportHeight(0), + mHasChildrenOnlyTransform(false) {} + +//---------------------------------------------------------------------- + +already_AddRefed<SVGAnimatedRect> SVGViewportElement::ViewBox() { + return mViewBox.ToSVGAnimatedRect(this); +} + +already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> +SVGViewportElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +bool SVGViewportElement::IsNodeOfType(uint32_t aFlags) const { + return !(aFlags & ~eUSE_TARGET); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGViewportElement::IsAttributeMapped(const nsAtom* name) const { + // We want to map the 'width' and 'height' attributes into style for + // outer-<svg>, except when the attributes aren't set (since their default + // values of '100%' can cause unexpected and undesirable behaviour for SVG + // inline in HTML). We rely on SVGElement::UpdateContentStyleRule() to + // prevent mapping of the default values into style (it only maps attributes + // that are set). We also rely on a check in SVGElement:: + // UpdateContentStyleRule() to prevent us mapping the attributes when they're + // given a <length> value that is not currently recognized by the SVG + // specification. + + if (!IsInner() && (name == nsGkAtoms::width || name == nsGkAtoms::height)) { + return true; + } + + static const MappedAttributeEntry* const map[] = {sColorMap, + sFEFloodMap, + sFillStrokeMap, + sFiltersMap, + sFontSpecificationMap, + sGradientStopMap, + sGraphicsMap, + sLightingEffectsMap, + sMarkersMap, + sTextContentElementsMap, + sViewportsMap}; + + return FindAttributeDependence(name, map) || + SVGGraphicsElement::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement overrides + +// Helper for GetViewBoxTransform on root <svg> node +// * aLength: internal value for our <svg> width or height attribute. +// * aViewportLength: length of the corresponding dimension of the viewport. +// * aSelf: the outermost <svg> node itself. +// NOTE: aSelf is not an ancestor viewport element, so it can't be used to +// resolve percentage lengths. (It can only be used to resolve +// 'em'/'ex'-valued units). +inline float ComputeSynthesizedViewBoxDimension( + const SVGAnimatedLength& aLength, float aViewportLength, + const SVGViewportElement* aSelf) { + if (aLength.IsPercentage()) { + return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f; + } + + return aLength.GetAnimValue(const_cast<SVGViewportElement*>(aSelf)); +} + +//---------------------------------------------------------------------- +// public helpers: + +void SVGViewportElement::UpdateHasChildrenOnlyTransform() { + bool hasChildrenOnlyTransform = + HasViewBoxOrSyntheticViewBox() || + (IsRootSVGSVGElement() && + static_cast<SVGSVGElement*>(this)->IsScaledOrTranslated()); + mHasChildrenOnlyTransform = hasChildrenOnlyTransform; +} + +void SVGViewportElement::ChildrenOnlyTransformChanged(uint32_t aFlags) { + // Avoid wasteful calls: + MOZ_ASSERT(!(GetPrimaryFrame()->GetStateBits() & NS_FRAME_IS_NONDISPLAY), + "Non-display SVG frames don't maintain overflow rects"); + + nsChangeHint changeHint; + + bool hadChildrenOnlyTransform = mHasChildrenOnlyTransform; + + UpdateHasChildrenOnlyTransform(); + + if (hadChildrenOnlyTransform != mHasChildrenOnlyTransform) { + // Reconstruct the frame tree to handle stacking context changes: + // XXXjwatt don't do this for root-<svg> or even outer-<svg>? + changeHint = nsChangeHint_ReconstructFrame; + } else { + // We just assume the old and new transforms are different. + changeHint = nsChangeHint(nsChangeHint_UpdateOverflow | + nsChangeHint_ChildrenOnlyTransform); + } + + // If we're not reconstructing the frame tree, then we only call + // PostRestyleEvent if we're not being called under reflow to avoid recursing + // to death. See bug 767056 comments 10 and 12. Since our SVGOuterSVGFrame + // is being reflowed we're going to invalidate and repaint its entire area + // anyway (which will include our children). + if ((changeHint & nsChangeHint_ReconstructFrame) || + !(aFlags & eDuringReflow)) { + nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, changeHint); + } +} + +gfx::Matrix SVGViewportElement::GetViewBoxTransform() const { + float viewportWidth, viewportHeight; + if (IsInner()) { + SVGElement* self = const_cast<SVGViewportElement*>(this); + viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValue(self); + viewportHeight = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(self); + } else { + viewportWidth = mViewportWidth; + viewportHeight = mViewportHeight; + } + + if (!IsFinite(viewportWidth) || viewportWidth <= 0.0f || + !IsFinite(viewportHeight) || viewportHeight <= 0.0f) { + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + + SVGViewBox viewBox = GetViewBoxWithSynthesis(viewportWidth, viewportHeight); + + if (!IsFinite(viewBox.width) || viewBox.width <= 0.0f || + !IsFinite(viewBox.height) || viewBox.height <= 0.0f) { + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + + return SVGContentUtils::GetViewBoxTransform( + viewportWidth, viewportHeight, viewBox.x, viewBox.y, viewBox.width, + viewBox.height, GetPreserveAspectRatioWithOverride()); +} +//---------------------------------------------------------------------- +// SVGViewportElement + +float SVGViewportElement::GetLength(uint8_t aCtxType) { + const SVGViewBox* viewbox = GetViewBoxInternal().HasRect() + ? &GetViewBoxInternal().GetAnimValue() + : nullptr; + + float h = 0.0f, w = 0.0f; + bool shouldComputeWidth = + (aCtxType == SVGContentUtils::X || aCtxType == SVGContentUtils::XY), + shouldComputeHeight = + (aCtxType == SVGContentUtils::Y || aCtxType == SVGContentUtils::XY); + + if (viewbox) { + w = viewbox->width; + h = viewbox->height; + } else if (IsInner()) { + // Resolving length for inner <svg> is exactly the same as other + // ordinary element. We shouldn't use the SVGViewportElement overload + // of GetAnimValue(). + SVGElement* self = this; + if (shouldComputeWidth) { + w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(self); + } + if (shouldComputeHeight) { + h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(self); + } + } else if (ShouldSynthesizeViewBox()) { + if (shouldComputeWidth) { + w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH], + mViewportWidth, this); + } + if (shouldComputeHeight) { + h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT], + mViewportHeight, this); + } + } else { + w = mViewportWidth; + h = mViewportHeight; + } + + w = std::max(w, 0.0f); + h = std::max(h, 0.0f); + + switch (aCtxType) { + case SVGContentUtils::X: + return w; + case SVGContentUtils::Y: + return h; + case SVGContentUtils::XY: + return float(SVGContentUtils::ComputeNormalizedHypotenuse(w, h)); + } + return 0; +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +gfxMatrix SVGViewportElement::PrependLocalTransformsTo( + const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const { + // 'transform' attribute (or an override from a fragment identifier): + gfxMatrix userToParent; + + if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) { + userToParent = GetUserToParentTransform(mAnimateMotionTransform.get(), + GetTransformInternal()); + if (aWhich == eUserSpaceToParent) { + return userToParent * aMatrix; + } + } + + gfxMatrix childToUser; + + if (IsInner()) { + float x, y; + const_cast<SVGViewportElement*>(this)->GetAnimatedLengthValues(&x, &y, + nullptr); + childToUser = ThebesMatrix(GetViewBoxTransform().PostTranslate(x, y)); + } else if (IsRootSVGSVGElement()) { + const SVGSVGElement* svg = static_cast<const SVGSVGElement*>(this); + const SVGPoint& translate = svg->GetCurrentTranslate(); + float scale = svg->CurrentScale(); + childToUser = + ThebesMatrix(GetViewBoxTransform() + .PostScale(scale, scale) + .PostTranslate(translate.GetX(), translate.GetY())); + } else { + // outer-<svg>, but inline in some other content: + childToUser = ThebesMatrix(GetViewBoxTransform()); + } + + if (aWhich == eAllTransforms) { + return childToUser * userToParent * aMatrix; + } + + MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes"); + + // The following may look broken because pre-multiplying our eChildToUserSpace + // transform with another matrix without including our eUserSpaceToParent + // transform between the two wouldn't make sense. We don't expect that to + // ever happen though. We get here either when the identity matrix has been + // passed because our caller just wants our eChildToUserSpace transform, or + // when our eUserSpaceToParent transform has already been multiplied into the + // matrix that our caller passes (such as when we're called from PaintSVG). + return childToUser * aMatrix; +} + +/* virtual */ +bool SVGViewportElement::HasValidDimensions() const { + return !IsInner() || + ((!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0)); +} + +SVGAnimatedViewBox* SVGViewportElement::GetAnimatedViewBox() { + return &mViewBox; +} + +SVGAnimatedPreserveAspectRatio* +SVGViewportElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +bool SVGViewportElement::ShouldSynthesizeViewBox() const { + MOZ_ASSERT(!HasViewBox(), "Should only be called if we lack a viewBox"); + + return IsRootSVGSVGElement() && OwnerDoc()->IsBeingUsedAsImage(); +} + +//---------------------------------------------------------------------- +// implementation helpers + +SVGViewBox SVGViewportElement::GetViewBoxWithSynthesis( + float aViewportWidth, float aViewportHeight) const { + if (GetViewBoxInternal().HasRect()) { + return GetViewBoxInternal().GetAnimValue(); + } + + if (ShouldSynthesizeViewBox()) { + // Special case -- fake a viewBox, using height & width attrs. + // (Use |this| as context, since if we get here, we're outermost <svg>.) + return SVGViewBox( + 0, 0, + ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH], + mViewportWidth, this), + ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT], + mViewportHeight, this)); + } + + // No viewBox attribute, so we shouldn't auto-scale. This is equivalent + // to having a viewBox that exactly matches our viewport size. + return SVGViewBox(0, 0, aViewportWidth, aViewportHeight); +} + +SVGElement::LengthAttributesInfo SVGViewportElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGViewportElement.h b/dom/svg/SVGViewportElement.h new file mode 100644 index 0000000000..999e649cc3 --- /dev/null +++ b/dom/svg/SVGViewportElement.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGVIEWPORTELEMENT_H_ +#define DOM_SVG_SVGVIEWPORTELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SVGImageContext.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FromParser.h" +#include "nsIContentInlines.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedViewBox.h" +#include "SVGGraphicsElement.h" +#include "SVGPoint.h" +#include "SVGPreserveAspectRatio.h" + +namespace mozilla { +class AutoPreserveAspectRatioOverride; +class SVGOuterSVGFrame; +class SVGViewportFrame; + +namespace dom { +class DOMSVGAnimatedPreserveAspectRatio; +class SVGAnimatedRect; +class SVGViewElement; +class SVGViewportElement; + +class svgFloatSize { + public: + svgFloatSize(float aWidth, float aHeight) : width(aWidth), height(aHeight) {} + bool operator!=(const svgFloatSize& rhs) { + return width != rhs.width || height != rhs.height; + } + float width; + float height; +}; + +class SVGViewportElement : public SVGGraphicsElement { + friend class mozilla::SVGOuterSVGFrame; + friend class mozilla::SVGViewportFrame; + + protected: + explicit SVGViewportElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo); + ~SVGViewportElement() = default; + + public: + bool IsNodeOfType(uint32_t aFlags) const override; + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGElement specializations: + gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; + + bool HasValidDimensions() const override; + + // SVGViewportElement methods: + + float GetLength(uint8_t aCtxType); + + // public helpers: + + /** + * Returns true if this element has a base/anim value for its "viewBox" + * attribute that defines a viewBox rectangle with finite values, or + * if there is a view element overriding this element's viewBox and it + * has a valid viewBox. + * + * Note that this does not check whether we need to synthesize a viewBox, + * so you must call ShouldSynthesizeViewBox() if you need to chck that too. + * + * Note also that this method does not pay attention to whether the width or + * height values of the viewBox rect are positive! + */ + bool HasViewBox() const { return GetViewBoxInternal().HasRect(); } + + /** + * Returns true if we should synthesize a viewBox for ourselves (that is, if + * we're the root element in an image document, and we're not currently being + * painted for an <svg:image> element). + * + * Only call this method if HasViewBox() returns false. + */ + bool ShouldSynthesizeViewBox() const; + + bool HasViewBoxOrSyntheticViewBox() const { + return HasViewBox() || ShouldSynthesizeViewBox(); + } + + bool HasChildrenOnlyTransform() const { return mHasChildrenOnlyTransform; } + + void UpdateHasChildrenOnlyTransform(); + + enum ChildrenOnlyTransformChangedFlags { eDuringReflow = 1 }; + + /** + * This method notifies the style system that the overflow rects of our + * immediate childrens' frames need to be updated. It is called by our own + * frame when changes (e.g. to currentScale) cause our children-only + * transform to change. + * + * The reason we have this method instead of overriding + * GetAttributeChangeHint is because we need to act on non-attribute (e.g. + * currentScale) changes in addition to attribute (e.g. viewBox) changes. + */ + void ChildrenOnlyTransformChanged(uint32_t aFlags = 0); + + gfx::Matrix GetViewBoxTransform() const; + + svgFloatSize GetViewportSize() const { + return svgFloatSize(mViewportWidth, mViewportHeight); + } + + void SetViewportSize(const svgFloatSize& aSize) { + mViewportWidth = aSize.width; + mViewportHeight = aSize.height; + } + + /** + * Returns true if either this is an SVG <svg> element that is the child of + * another non-foreignObject SVG element, or this is a SVG <symbol> element + * that is the root of a use-element shadow tree. + */ + bool IsInner() const { + const nsIContent* parent = GetFlattenedTreeParent(); + return parent && parent->IsSVGElement() && + !parent->IsSVGElement(nsGkAtoms::foreignObject); + } + + // WebIDL + already_AddRefed<SVGAnimatedRect> ViewBox(); + already_AddRefed<DOMSVGAnimatedPreserveAspectRatio> PreserveAspectRatio(); + SVGAnimatedViewBox* GetAnimatedViewBox() override; + + protected: + // implementation helpers: + + bool IsRootSVGSVGElement() const { + NS_ASSERTION((IsInUncomposedDoc() && !GetParent()) == + (OwnerDoc()->GetRootElement() == this), + "Can't determine if we're root"); + return !GetParent() && IsInUncomposedDoc() && IsSVGElement(nsGkAtoms::svg); + } + + /** + * Returns the explicit or default preserveAspectRatio, unless we're + * synthesizing a viewBox, in which case it returns the "none" value. + */ + virtual SVGPreserveAspectRatio GetPreserveAspectRatioWithOverride() const { + return mPreserveAspectRatio.GetAnimValue(); + } + + /** + * Returns the explicit viewBox rect, if specified, or else a synthesized + * viewBox, if appropriate, or else a viewBox matching the dimensions of the + * SVG viewport. + */ + SVGViewBox GetViewBoxWithSynthesis(float aViewportWidth, + float aViewportHeight) const; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + LengthAttributesInfo GetLengthInfo() override; + + SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() override; + + virtual const SVGAnimatedViewBox& GetViewBoxInternal() const { + return mViewBox; + } + virtual SVGAnimatedTransformList* GetTransformInternal() const { + return mTransforms.get(); + } + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + + // The size of the rectangular SVG viewport into which we render. This is + // not (necessarily) the same as the content area. See: + // + // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace + // + // XXXjwatt Currently only used for outer <svg>, but maybe we could use -1 to + // flag this as an inner <svg> to save the overhead of GetCtx calls? + // XXXjwatt our frame should probably reset these when it's destroyed. + float mViewportWidth, mViewportHeight; + + bool mHasChildrenOnlyTransform; +}; + +} // namespace dom + +} // namespace mozilla + +#endif // DOM_SVG_SVGVIEWPORTELEMENT_H_ diff --git a/dom/svg/crashtests/1035248-1.svg b/dom/svg/crashtests/1035248-1.svg new file mode 100644 index 0000000000..125f83ba49 --- /dev/null +++ b/dom/svg/crashtests/1035248-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> + +function boom() +{ + var outer = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); + var inner = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); + inner.setAttributeNS(null, "style", "display: table-row-group;"); + outer.appendChild(inner); + + document.removeChild(document.documentElement); + document.appendChild(outer); +} + +window.addEventListener("load", boom, false); + +</script> +</svg> diff --git a/dom/svg/crashtests/1035248-2.svg b/dom/svg/crashtests/1035248-2.svg new file mode 100644 index 0000000000..019cfda304 --- /dev/null +++ b/dom/svg/crashtests/1035248-2.svg @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<script> + +window.addEventListener("load", function() { + var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); + var tr = document.createElementNS('http://www.w3.org/1999/xhtml', 'tr'); + tr.style.display = "table-row"; + document.removeChild(document.documentElement); + div.appendChild(tr); + document.appendChild(div); +}, false); + +</script> + +</svg> diff --git a/dom/svg/crashtests/1244898-1.xhtml b/dom/svg/crashtests/1244898-1.xhtml new file mode 100644 index 0000000000..9b89c2c8b8 --- /dev/null +++ b/dom/svg/crashtests/1244898-1.xhtml @@ -0,0 +1,14 @@ +<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait"> + +<script> + +window.addEventListener("load", function() { + location.hash = "#a"; + document.documentElement.removeAttribute("class"); +}, false); + +</script> + +<animate xmlns="http://www.w3.org/2000/svg" id="a"/> + +</html> diff --git a/dom/svg/crashtests/1250725.html b/dom/svg/crashtests/1250725.html new file mode 100644 index 0000000000..3edc8d223c --- /dev/null +++ b/dom/svg/crashtests/1250725.html @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <script> + function boom() + { + document.getElementById("u").setAttribute("width", "30"); + document.getElementById("p").remove(); + } + window.addEventListener("load", boom); + </script> + + <symbol id="p" viewBox="0 0 100 20"/> + + <use id="u" xlink:href="#p"/> + +</svg> diff --git a/dom/svg/crashtests/1267272-1.svg b/dom/svg/crashtests/1267272-1.svg new file mode 100644 index 0000000000..67be18f553 --- /dev/null +++ b/dom/svg/crashtests/1267272-1.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <filter id="f"> + <feImage xlink:href="invalid-image.svg"/> + </filter> + <rect filter='url(#f)' width="1" height="1"/> +</svg> diff --git a/dom/svg/crashtests/1282985-1.svg b/dom/svg/crashtests/1282985-1.svg new file mode 100644 index 0000000000..e0d838917b --- /dev/null +++ b/dom/svg/crashtests/1282985-1.svg @@ -0,0 +1,24 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> +<![CDATA[ + +function boom() { + var g = document.createElementNS("http://www.w3.org/2000/svg", "g"); + g.setAttribute("id", "g"); + var iframe = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); + g.appendChild(iframe); + document.documentElement.appendChild(g); + var use = document.createElementNS("http://www.w3.org/2000/svg", "use"); + use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#g"); + document.documentElement.appendChild(use); + setTimeout(function() { + setTimeout(function() { + g.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "video")); + }, 3); + }, 3); +} +window.addEventListener("load", boom, false); + +]]> +</script> +</svg> diff --git a/dom/svg/crashtests/1322286.html b/dom/svg/crashtests/1322286.html new file mode 100644 index 0000000000..aa56c46c23 --- /dev/null +++ b/dom/svg/crashtests/1322286.html @@ -0,0 +1,3 @@ +<svg> +<polyline marker-mid='url(#a)' points='1,0 7,0 4,6'/> +<marker id='a' mask='url()'>
\ No newline at end of file diff --git a/dom/svg/crashtests/1329093-1.html b/dom/svg/crashtests/1329093-1.html new file mode 100644 index 0000000000..b5000c89a6 --- /dev/null +++ b/dom/svg/crashtests/1329093-1.html @@ -0,0 +1,9 @@ +<html> + +<head> +</head> +<body> +Loading the below iframe should not crash Firefox in Stylo mode. +<iframe style="display: none" src='data:text/html,<svg height="100" width="100"><circle cx="50" cy="50" r="40" stroke="yellow" stroke-width="2" fill="green"/> </svg>'></iframe> +</body> +</html>
\ No newline at end of file diff --git a/dom/svg/crashtests/1329093-2.html b/dom/svg/crashtests/1329093-2.html new file mode 100644 index 0000000000..444b7aae8e --- /dev/null +++ b/dom/svg/crashtests/1329093-2.html @@ -0,0 +1,28 @@ +<html class="reftest-wait"> + +<head> +</head> +<body> + +Loading the below iframe should not crash Firefox in Stylo mode. +<svg height="100" width="100" id="svgElement"> + <circle cx="50" cy="50" r="40" stroke="yellow" stroke-width="2" fill="green"/> +</svg> + +<iframe src="" id="myFrame"></iframe> +<div style="display: none" id="triggerRestyle"></div> +<script type="text/javascript"> +let frame = document.getElementById("myFrame"); +frame.onload = function() { + let baz = frame.contentDocument.adoptNode(document.getElementById("svgElement")); + frame.contentDocument.body.appendChild(baz); + baz = null; + frame.remove(); + frame = null; + SpecialPowers.gc(); + let color = getComputedStyle(document.getElementById('triggerRestyle')).color; + document.documentElement.className = ""; +} +</script> +</body> +</html> diff --git a/dom/svg/crashtests/1329849-1.svg b/dom/svg/crashtests/1329849-1.svg new file mode 100644 index 0000000000..350e549efd --- /dev/null +++ b/dom/svg/crashtests/1329849-1.svg @@ -0,0 +1,13 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id='f1'> +<feSpecularLighting kernelUnitLength='-82'> +<feSpotLight/> +</feSpecularLighting> +</filter> +<filter id='f2' xlink:href='#f1'></filter> +<polyline filter='url(#f2)' points='10,59 293,88 18,289'/> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1329849-2.svg b/dom/svg/crashtests/1329849-2.svg new file mode 100644 index 0000000000..ec340e2316 --- /dev/null +++ b/dom/svg/crashtests/1329849-2.svg @@ -0,0 +1,13 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id='f1'> +<feSpecularLighting kernelUnitLength='-80 10'> +<feSpotLight/> +</feSpecularLighting> +</filter> +<filter id='f2' xlink:href='#f1'></filter> +<polyline filter='url(#f2)' points='10,59 293,88 18,289'/> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1329849-3.svg b/dom/svg/crashtests/1329849-3.svg new file mode 100644 index 0000000000..a263e083fe --- /dev/null +++ b/dom/svg/crashtests/1329849-3.svg @@ -0,0 +1,13 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id='f1'> +<feSpecularLighting kernelUnitLength='80 -10'> +<feSpotLight/> +</feSpecularLighting> +</filter> +<filter id='f2' xlink:href='#f1'></filter> +<polyline filter='url(#f2)' points='10,59 293,88 18,289'/> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1329849-4.svg b/dom/svg/crashtests/1329849-4.svg new file mode 100644 index 0000000000..b8bc061c5f --- /dev/null +++ b/dom/svg/crashtests/1329849-4.svg @@ -0,0 +1,13 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id='f1'> +<feDiffuseLighting kernelUnitLength='-80'> +<feSpotLight/> +</feDiffuseLighting> +</filter> +<filter id='f2' xlink:href='#f1'></filter> +<polyline filter='url(#f2)' points='10,59 293,88 18,289'/> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1329849-5.svg b/dom/svg/crashtests/1329849-5.svg new file mode 100644 index 0000000000..16390ccb04 --- /dev/null +++ b/dom/svg/crashtests/1329849-5.svg @@ -0,0 +1,13 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id='f1'> +<feDiffuseLighting kernelUnitLength='-80 10'> +<feSpotLight/> +</feDiffuseLighting> +</filter> +<filter id='f2' xlink:href='#f1'></filter> +<polyline filter='url(#f2)' points='10,59 293,88 18,289'/> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1329849-6.svg b/dom/svg/crashtests/1329849-6.svg new file mode 100644 index 0000000000..0a928393c7 --- /dev/null +++ b/dom/svg/crashtests/1329849-6.svg @@ -0,0 +1,13 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id='f1'> +<feDiffuseLighting kernelUnitLength='80 -10'> +<feSpotLight/> +</feDiffuseLighting> +</filter> +<filter id='f2' xlink:href='#f1'></filter> +<polyline filter='url(#f2)' points='10,59 293,88 18,289'/> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1343147.svg b/dom/svg/crashtests/1343147.svg new file mode 100644 index 0000000000..d9c2611ca8 --- /dev/null +++ b/dom/svg/crashtests/1343147.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<style> +<![CDATA[ + svg { + background-image: linear-gradient(lime, lime); + background-clip: text; + } + text { transform: skewy(30grad); } + g { perspective: 0; } +]]> +</style> + <g><text>hello</text></g> +</svg> diff --git a/dom/svg/crashtests/1347617-1.svg b/dom/svg/crashtests/1347617-1.svg new file mode 100644 index 0000000000..b65c63fbf8 --- /dev/null +++ b/dom/svg/crashtests/1347617-1.svg @@ -0,0 +1,10 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + filter="url(#f)"> + +<filter id="f" filterRes="19" filterUnits="userSpaceOnUse"> +<feConvolveMatrix kernelMatrix="1 1 1 1 1 1 1 1 1" kernelUnitLength="1 -1" /> +</filter> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1347617-2.svg b/dom/svg/crashtests/1347617-2.svg new file mode 100644 index 0000000000..8355ff6ade --- /dev/null +++ b/dom/svg/crashtests/1347617-2.svg @@ -0,0 +1,10 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + filter="url(#f)"> + +<filter id="f" filterRes="19" filterUnits="userSpaceOnUse"> +<feConvolveMatrix kernelMatrix="1 1 1 1 1 1 1 1 1" kernelUnitLength="-1 1" /> +</filter> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1347617-3.svg b/dom/svg/crashtests/1347617-3.svg new file mode 100644 index 0000000000..2dd32cde32 --- /dev/null +++ b/dom/svg/crashtests/1347617-3.svg @@ -0,0 +1,10 @@ +<svg height='600' + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + filter="url(#f)"> + +<filter id="f" filterRes="19" filterUnits="userSpaceOnUse"> +<feConvolveMatrix kernelMatrix="1 1 1 1 1 1 1 1 1" kernelUnitLength="-1" /> +</filter> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/1402798.html b/dom/svg/crashtests/1402798.html new file mode 100644 index 0000000000..43d7a77e86 --- /dev/null +++ b/dom/svg/crashtests/1402798.html @@ -0,0 +1,11 @@ +<style> + text::first-letter {} +</style> +<script> + function js() { + a.setAttribute("fill", "url()"); + } +</script> +<body onload=js()> +<svg> +<text id="a">aa</text> diff --git a/dom/svg/crashtests/1419250-1.html b/dom/svg/crashtests/1419250-1.html new file mode 100644 index 0000000000..fb0d0ea22c --- /dev/null +++ b/dom/svg/crashtests/1419250-1.html @@ -0,0 +1 @@ +<svg font-size="0px" height="-1em"> diff --git a/dom/svg/crashtests/1420492.html b/dom/svg/crashtests/1420492.html new file mode 100644 index 0000000000..3c5b22ff3e --- /dev/null +++ b/dom/svg/crashtests/1420492.html @@ -0,0 +1,3 @@ +<svg> +<animateMotion path='m4,5a0,3 6,012,1'> + diff --git a/dom/svg/crashtests/1477853.html b/dom/svg/crashtests/1477853.html new file mode 100644 index 0000000000..f2a1d37657 --- /dev/null +++ b/dom/svg/crashtests/1477853.html @@ -0,0 +1,10 @@ +<html id="a"> +<script> +window.onload=function(){ + c.getRootNode({composed: true}).replaceChild(b, a); +} +</script> +<body> +<svg> +<animate id="b" href="" /> +<animateTransform id="c" /> diff --git a/dom/svg/crashtests/1486488.html b/dom/svg/crashtests/1486488.html new file mode 100644 index 0000000000..ba61cb277b --- /dev/null +++ b/dom/svg/crashtests/1486488.html @@ -0,0 +1,11 @@ +<script> +function start() { + document.replaceChild(id_0, document.childNodes[0]); +} +</script> +<body onload="start()"> + <svg> + <animate id="id_0" begin="s" /> + <ellipse id="id_1" /> + </svg> +</body> diff --git a/dom/svg/crashtests/1493447.html b/dom/svg/crashtests/1493447.html new file mode 100644 index 0000000000..648f6149b9 --- /dev/null +++ b/dom/svg/crashtests/1493447.html @@ -0,0 +1,12 @@ +<svg> + <defs> + <filter id="F8" > + <feComponentTransfer> + <feFuncB type="discrete"> + </feFuncB> + </feComponentTransfer> + </filter> + </defs> + <image width="10" height="10" filter="url(#F8)"> + </image> +</svg> diff --git a/dom/svg/crashtests/1507961-1.html b/dom/svg/crashtests/1507961-1.html new file mode 100644 index 0000000000..a94d5872fb --- /dev/null +++ b/dom/svg/crashtests/1507961-1.html @@ -0,0 +1,4066 @@ +<!-- saved from url=(0014)about:internet -->
+<html>
+<head>
+<style>
+/*begincss*/
+#htmlvar00005 { border-right-style: hidden; transform-style: inherit; -webkit-border-bottom-left-radius: 1px; overflow-y: scroll; -webkit-transform-origin: center center 0px; margin: auto 1; -webkit-animation-timing-function: linear, ease-in-out; -webkit-appearance: square-button; mso-number-format: General; caption-side: right; font-face: Arial; background-color: black; border-top-style: none; -webkit-backface-visibility: hidden; -webkit-box-flex: -1; padding: inherit; font-size: 59%; font-variant: normal; -webkit-border-before-style: dashed; overflow-wrap: normal }
+input:disabled { -webkit-mask-box-image-outset: 34px; padding-right: 54px; offset-position: inherit; -webkit-text-stroke-width: 4px; list-style-position: outside; box-direction: reverse; border-width: 0px; border-size: 1px; scroll-snap-type: proximity; table-layout: auto; stop-color: transparent; font-size: 0.433513056126rem; motion-path: path('M 0 0 H 0 V 0 H -1 L 2 0'); padding-left: 1vmax; flex-flow: row-reverse nowrap; -webkit-box-sizing: content-box; animation-fill-mode: both; box-orient: vertical; -webkit-animation-iteration-count: infinite; stroke-linecap: round }
+font_face_format { background-clip: padding-box; -webkit-min-logical-height: 62px; -webkit-margin-before: 3px; animation-fill-mode: forwards; stroke-dasharray: 1; -webkit-padding-after: 0px; unicode-bidi: -webkit-plaintext; visibility: none; zoom: auto; box-decoration-break: slice; flex-shrink: 8; -webkit-transition-timing-function: ease-in-out; padding-bottom: -1vw; mso-border-alt: solid .0pt; grid-row: auto / 1; border-spacing: 0 0; text-decoration: line-through garbage; grid-column-end: span 1; -webkit-animation-timing-function: ease-in-out; flex-grow: 0.607083962617 }
+#htmlvar00001 { -webkit-border-start: 5px solid red; box-direction: reverse; column-break-before: always; orphans: 1; transition-timing-function: initial; float: right garbage; border-bottom: 1px solid; fonty-family: Times; css-float: left; marker: inherit; text-indent: 65cm; border-image-slice: 2%; position: -1px; border-top-width: thick; flex-flow: wrap-reverse; offset: path('M 52 0 l 0 1') 13% auto 1deg; column-break-before: always; right: 1p; margin: auto auto; animation-name: anim }
+:not(keygen) { -webkit-box-lines: multiple; outline-style: double; rotate: 0deg; mso-background-source: auto; -webkit-nbsp-mode: space; -webkit-perspective-origin-y: 4px; border-bottom: rgb(184,64,198) auto; -webkit-mask: below url(#svgvar00003); resize: both; display: list-item; border-right: red 0px solid; box-sizing: border-box; transform-origin: left top; outline-bottom: none; position: relative; position: sticky; cellpadding: 1; marker: initial; -webkit-mask-box-image-repeat: stretch; box-reflect: right }
+script:last-of-type { unicode-bidi: normal; text-decoration: overline underline line-through; -ms-text-combine-horizontal: all; counter-reset: c; grid-template-rows: fit-content(47); -webkit-animation-name: anim; -webkit-box-pack: end; -ms-text-combine-horizontal: all; min-width: fill-available; text-shadow: 40px 1px; border-image-repeat: repeat repeat; column-rule: -1em solid black; -webkit-perspective-origin: 0px 1px; -webkit-column-gap: 3px; shape-inside: polygon(0px -1px); -webkit-user-modify: read-write; -webkit-nbsp-mode: space; -webkit-mask-composite: copy; backface-visibility: visible; columns: 0 }
+#htmlvar00006, input:disabled, #htmlvar00003 { user-zoom: zoom; -webkit-box-reflect: left 99px; -webkit-border-vertical-spacing: 1px; -webkit-line-clamp: 0; -webkit-border-before-width: 1px; -webkit-highlight: 'green'; -webkit-mask-box-image-source: url(#htmlvar00008); cx: 0px; snap-height: 1px; text-decoration: inherit; mso-border-left-alt: var(--cssvarb); -webkit-text-emphasis: open sesame; animation-name: anim; border-left-color: green; -webkit-column-count: 1; grid-row-gap: 1em; rotation: 8deg; -webkit-box-orient: block-axis; -webkit-transition-delay: inherit; offset-path: path('M 1 0 L 0 1 Z') }
+.class6, .class5 { margin: 74px 6px -1px 1px; grid-template-areas: 'a'; -webkit-clip-path: circle(1px, -1px, 19px); postion: relative; scroll-snap-type: none; -webkit-locale: 'zh_CN'; -webkit-box-sizing: padding-box; -webkit-border-bottom-right-radius: -1px; prince-hyphens: auto; -webkit-background-origin: border-box; mso-border-alt: solid black .1pt; -webkit-perspective-origin: 0px 0px; border-width: 3px; line-break: before-white-space; stop-color: red; border-left-style: none; overflow-wrap: normal; -webkit-border-start: 1px solid purple; -webkit-background-origin: content-box; font-variant-caps: petite-caps }
+.class8 { text-rendering: optimizelegibility; -webkit-border-start-color: rgb(146,123,109); perspective-origin: 16% 81%; image-rendering: auto; outline-bottom: none; mso-border-alt: solid white .-1pt; lighting-color: white; --cssvarb: discard; -webkit-marquee-speed: 1; -webkit-column-fill: auto; border-right-width: medium; stroke-width: 30%; caption-side: left; -webkit-column-width: auto; box-pack: justify; font-size-adjust: none; font-stretch: ultra-expanded; text-shadow: 0 0px 0px; list-style: inherit; -webkit-text-stroke-color: black }
+.class2 { -webkit-animation: anim 1s linear infinite backwards; word-space: nowrap; -webkit-font-smoothing: none; -webkit-text-stroke: 0px ; flex: 3; -webkit-column-break-inside: avoid; font-size-adjust: none; scroll-behavior: auto; -webkit-writing-mode: vertical-lr; -webkit-margin-before: 1px; empty-cells: show; border-right: hidden; user-zoom: zoom; word-wrap: normal; backdrop-filter: hue-rotate(1deg); -webkit-column-rule-style: double; text-decoration-style: solid; text-underline-position: under; transform-style: initial; -webkit-shape-margin: -1px }
+aside>feMergeNode { -webkit-text-orientation: sideways; max-width: 81%; -webkit-box-pack: justify; stroke-linecap: round; -webkit-logical-height: 1px; cellpadding: 0; border-bottom-width: 22px; border-collapse: collapse; -webkit-background-size: auto; -webkit-perspective-origin-x: 0px; -webkit-filter: opacity(0.24308191092); -webkit-mask-repeat-y: inherit; mask-source-type: luminance; column-gap: 0em; transition-timing-function: ease; white: fuchsia; user-zoom: zoom; resize: vertical; letter-spacing: -1px auto; border-top-left-radius: 0vmax }
+div, #htmlvar00001, .class7 { border-top-left-radius: 0vmin; background-clip: border-box; background-color: white; background-position-x: initial; clear: left; -webkit-padding-start: -1px; -webkit-text-security: square; -ms-user-select: none; align-items: auto; -webkit-user-select: all; -webkit-border-end-color: black; font-variant: small-caps auto; stroke: rgb(253,148,132); -webkit-margin-collapse: discard; background-blend-mode: screen, normal; -webkit-flex-direction: column; mso-background-source: auto; -ms-flex-align: center; border-top-left-radius: 72px 1px; resize: inherit }
+sub { mso-font-charset: 1; break-before: page; mask-source-type: luminance; content: counter(c, disc); background-color: red; text-underline: single; grid-row-gap: 7px; oxverflow: hidden; cx: 0px; -webkit-locale: 'zh_CN'; -webkit-column-break-after: always; -webkit-tap-highlight-color: red; -webkit-text-emphasis-color: green; font-style: inherit; scroll-snap-points-x: repeat(75%); -webkit-color-correction: sRGB; transform-origin: right; min-zoom: auto; transition-properties: transform; vertical-align: 0.262430241068em }
+blink:empty { -webkit-columns: 0; page-break-before: right; will-change: scale; text-shadow: 1px 84px 0px green; -webkit-padding-start: 23px; -webkit-columns: initial; -webkit-mask-box-image-source: url(#svgvar00005); grid-template: none/30px; font-style: normal; offset-position: 1px 0px; -webkit-perspective-origin: 0px 1px; box-align: center; background-position: center right; text-orientation: sideways; margin-right: 96%; content: counter(c, lower-alpha); dominant-baseline: no-change; -webkit-font-feature-settings: 'dlig'; animation-fill-mode: forwards; transition-delay: 1s }
+altGlyphDef { -webkit-opacity: 0.593801076767; margin-left: 0px; cellpadding: 0; whitespace: nowrap; -webkit-marquee-speed: 78; border-top-left-radius: 0vw; -webkit-animation-play-state: paused; -webkit-padding-start: 1px; background-repeat-x: 10px; -webkit-text-emphasis: dot; overflow-wrap: normal; float: center; page-break-before: auto; transform: translateZ(0px); direction: ltr; -webkit-column-rule-width: 6px; -webkit-column-rule-style: dotted; text-indent: 1; -webkit-line-break: after-white-space; -webkit-hyphenate-character: auto }
+:root { -webkit-perspective-origin-y: 1px; justify-content: right safe unsafe; min-width: max-content; shape-inside: polygon(7px 1px); -webkit-border-end: 0px dashed rgb(146,118,94); -webkit-border-before-color: rgb(184,179,123); weight: *; ry: 10px; will-change: -webkit-filter; -webkit-mask-box-image: none; grid-template-rows: auto; overflow-anchor: none; text-shadow: inherit; -webkit-padding-after: -1px; -webkit-border-image: url(#htmlvar00004) 63 21 4 82 repeat; flex-wrap: damer; page-break-before: auto; margin-left: 1vmin; -webkit-column-rule-width: 0px; shape-image-threshold: 0 }
+foreignObject { user-zoom: zoom; font-weight: bolder; animation-delay: initial; animation-duration: initial; font-vendor: any; stroke-dasharray: none; background-repeat-y: repeat; motion-offset: 0; -webkit-column-break-after: always; min-height: 2vmax; border-spacing: 0 0; quotes: 'a' 'b' 'c' 'd'; padding-right: 0px; image-orientation: from-image; empty-cells: hide; page-break-after: always; -webkit-column-break-inside: avoid; -webkit-box-lines: multiple; mso-fareast-font-family: 'Times New Roman'; overflow-y: -webkit-paged-y }
+#htmlvar00006 { animation-direction: reverse; -webkit-transition-duration: 95s; text-anchor: middle; -webkit-transition: width 0s; outline-color: inherit; font-kerning: none; -webkit-animation-duration: 1s; -webkit-margin-after: 21px; flex: 0; table-layout: fixed; animation-play-state: inherit; border-bottom-width: 1px; image-rendering: -webkit-optimize-contrast; -webkit-text-decorations-in-effect: underline; -webkit-writing-mode: horizontal-tb; -webkit-column-break-after: always; outline-color: invert; -webkit-transition-timing-function: ease-out; grid-row-gap: 84%; border-image: url(#htmlvar00001) 24% 0 1 round }
+#htmlvar00002 { -webkit-border-end: 0px solid white; scroll-behavior: auto; grid-row-gap: 0em; -webkit-hyphenate-character: auto; -webkit-padding-before: 1px; -webkit-transition-delay: 1s; offset-path: path('M 0 0 H 0 V 1 H 0 L 0 0'); -webkit-background-clip: padding-box; writing-mode: inherit; min-height: 1px; text-indent: -1vh; -webkit-mask-repeat-y: inherit; border-color: red; scale: initial; -webkit-column-rule-width: 0px; column-span: all; column-fill: balance; justify-items: left; table-layout: auto; -webkit-logical-width: 0px }
+.class4 { shape-image-threshold: -1; page: 6cm; -webkit-border-end: 1px dashed ; mso-generic-font-family: auto; -webkit-user-modify: read-write; -webkit-mask-origin: content-box; object-position: 66%; -webkit-perspective-origin: 0px 0px; grid-row-start: span last; scroll-snap-coordinate: left; transition-timing-function: inherit; mso-border-left-alt: solid green .1pt; background-blend-mode: multiply; line-break: after-white-space; -webkit-box-shadow: red 0px 0px 0px; unicode-bidi: isolate-override; css-float: left; height: fill-available; text-rendering: auto; margin-bottom: 0em }
+.class8 { overflow: -webkit-paged-y; -webkit-animation-delay: 2s; vector-effect: non-scaling-stroke; align-self: stretch; align-content: right safe unsafe; contain: size; font-size: 54%; overflow-wrap: normal; -webkit-animation-fill-mode: forwards, both; border-top: dashed; scroll-snap-type: proximity; -webkit-box-flex-group: 2; transform: scaley(0.79312031554); -webkit-animation: anim 0s linear; isolation: isolate; flood-color: rgb(56,206,167); -webkit-text-stroke: 0px red; border-right-color: inherit; -webkit-animation-name: anim; outline: solid transparent }
+metadata, #htmlvar00003, .class2, .class0 { offset-path: path('M 11 -1 L 1 2'); outline-color: white; user-zoom: zoom; overflow-anchor: none; rotation: -1deg; border-bottom-width: 0px; mso-generic-font-family: auto; mso-pagination: widow-orphan; -webkit-font-smoothing: none; word-break: normal; border-right-width: 2px; border-left: white 0px dashed; mso-font-kerning: 0pt; background-image: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7); shape-image-threshold: -1; object-fit: contain; -webkit-line-clamp: 56; -webkit-flow-from: flow1; -webkit-mask-composite: copy; quotes: none }
+set::first-letter { table-layout: auto; counter-reset: c; oxverflow: hidden; -webkit-column-fill: auto; shape-inside: polygon(1px 1px); line-break: after-white-space; text-decoration: underline solid red; -webkit-padding-end: 1px; background-repeat-x: repeat; border-top-right-radius: 0vh; transition: opacity 0.569156021741s; break-before: column; -webkit-border-before: 0px solid rgb(241,215,133); -webkit-text-emphasis: dot; -webkit-padding-end: 0px; -webkit-mask-box-image-source: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7); -webkit-margin-start: 1px; border-image-source: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7); -webkit-writing-mode: vertical-tb; -webkit-border-image: url(#svgvar00001) 0 42 0 0 stretch }
+text+basefont, .class0 { margin-bottom: -1vmax; grid-auto-rows: fit-content(66%); grid-column-gap: 18%; clip: rect(auto, auto, auto, auto); orientation: auto; transform-origin: inherit; -webkit-border-start: 1px solid purple; min-zoom: auto; column-fill: auto; widows: -1; page-break-before: avoid; border-top: rgb(225,125,237) solid 0px; orientation: auto; mso-border-top-alt: solid white .2pt; unicode-bidi: -webkit-plaintext; -webkit-logical-height: 1px; -webkit-rtl-ordering: visual; border-bottom-right-radius: 99px 6px; --cssvarb: 'frac' 1, 'dlig' 1; -webkit-padding-end: 85px }
+#htmlvar00008 { transition-properties: transform; -webkit-box-align: stretch; -webkit-locale: 'zh_CN'; -webkit-mask-composite: copy; z-index: 1; -webkit-column-break-before: always; text-anchor: end; -webkit-wrap-flow: auto; box-direction: reverse; font-kerning: normal; grid-column-end: 36; transition-duration: initial; -webkit-ruby-position: after; background-repeat-x: no-repeat; flex-wrap: damer; -webkit-color-correction: default; outline-bottom: none; mso-rotate: 1; grid-column-end: auto; mso-displayed-decimal-separator: '\.' }
+.class7 { border-color: green; -webkit-column-rule-style: groove; background-blend-mode: normal, normal; border-right-color: transparent; dominant-baseline: no-change; -webkit-background-origin: content-box; -ms-user-select: none; -webkit-print-color-adjust: exact; text-overflow: clip; transform: translatez(10); cellpadding: 0; mso-rotate: 0; text-decoration: underline solid red; alignx: left; animation-fill-mode: forwards; -webkit-align-self: stretch; -webkit-column-span: all; -webkit-border-before-style: dashed; marker: initial; border-style: dotted }
+input:checked { border-left-color: green; border-collapse: separate; -webkit-user-modify: read-write-plaintext-only; animation-duration: inherit; transition-duration: 5s; mso-style-parent: ''; -webkit-column-gap: initial; mso-font-charset: 0; -webkit-box-ordinal-group: 2; touch-action: pan-x pan-y; mso-generic-font-family: auto; -webkit-border-end-color: ; -webkit-margin-top-collapse: separate; scroll-snap-points-x: repeat(1px); -webkit-perspective-origin: 0px 95px; -webkit-margin-bottom-collapse: discard; mso-generic-font-family: auto; prince-hyphens: auto; -webkit-animation-direction: normal; line-height: 46vmax }
+text:first-child { animation-direction: initial; flood-opacity: 0; list-style-position: inside; -webkit-text-combine: horizontal; -webkit-border-after-width: 0px; border-top-left-radius: 0em 1em; -webkit-border-end-color: ; border-bottom-color: white; flood-opacity: 0.455470827901; -webkit-margin-after-collapse: separate; break-inside: avoid-page; grid-template-columns: minmax(auto, max-content); -webkit-background-size: auto; transition-timing-function: initial; -webkit-shape-outside: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7); -webkit-border-bottom-right-radius: 0px 1px; max-zoom: auto; -webkit-border-before: 8px solid; -webkit-column-gap: initial; border-top-right-radius: 90% }
+#htmlvar00005 { background-repeat: black; flex-wrap: nowrap; -webkit-margin-collapse: discard; -webkit-print-color-adjust: exact; width: 72mm; white: fuchsia; -webkit-transition-delay: 1s; -webkit-marquee-speed: 0; word-space: nowrap; grid-row-start: last 1 span; -webkit-margin-before-collapse: collapse; text-underline: single; border-size: 1px; -ms-text-combine-horizontal: all; cellpadding: 0; mso-style-name: Normal; justify-items: baseline; -webkit-text-decorations-in-effect: underline; text-combine-upright: initial; -webkit-padding-before: -1px }
+kbd { background-position-x: 68%; -webkit-overflow-scrolling: touch; fill-rule: evenodd; -webkit-flow-from: flow1; unicode-bidi: embed; text-decoration-upright: none digits; touch-action: bogus; -webkit-box-sizing: padding-box; -webkit-text-orientation: sideways-right; -webkit-flex: 1 1 1; fill-rule: evenodd; animation-iteration-count: initial; translate: inherit; overflow: none; counter-reset: c; font-vendor: any; -webkit-border-start: 2px solid red; -webkit-box-lines: multiple; column-break-before: always; motion-offset: inherit }
+#htmlvar00007 { background-attachment: initial; -webkit-margin-after: 55px; mso-fareast-font-family: 'Times New Roman'; offset-anchor: inherit; animation-duration: 0s; font-size-adjust: none; grid-row-start: last; -webkit-margin-collapse: collapse separate; word-spacing: -1pt; -webkit-column-count: 0; -webkit-clip-path: polygon(1px 0px, 0px 1px, 1px 0px, 2px 0px); text-transform: capitalize auto; -webkit-transition-delay: inherit; cellpadding: -1; whitespace: nowrap; clear: both; padding-bottom: 1px; padding-left: 0vmin; -webkit-justify-content: flex-start; shape-margin: 16% }
+#htmlvar00007, #htmlvar00001 { border-top-style: double; border-image-repeat: inherit; -webkit-animation-name: anim; mso-ignore: padding; -webkit-padding-end: 1px; stroke-dashoffset: 0; mso-displayed-decimal-separator: '\.'; -webkit-rtl-ordering: visual; background: red url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7); -webkit-column-break-before: always; border: dotted; postion: relative; line-height: 80vmin; -webkit-box-orient: vertical; background-image: url(#htmlvar00009); mso-style-parent: style8; visibility: inherit; justify-content: end unsafe; border-bottom-left-radius: 1px 1px; grid-auto-rows: fit-content(29%) }
+sub { -webkit-mask-box-image-source: url(#svgvar00004); -webkit-align-items: flex-end; -webkit-box-flex-group: 7; column-count: var(--cssvard); list-style-type: hangul; whitespace: nowrap; -webkit-column-rule-width: 35px; border-top-color: black; snap-height: inherit; -webkit-align-items: flex-start; transition-timing-function: cubic-bezier(0.893008682131, 0.240031092305, 0.908415341392, 0.420616706526); -webkit-border-before-width: 1px; grid-row-start: 9 span; overflow-anchor: none; -webkit-align-self: center; transition-property: ry; padding-bottom: -1vmax; -webkit-column-break-inside: avoid; background-repeat-y: repeat; -webkit-font-smoothing: none }
+.class4 { -webkit-font-smoothing: subpixel-antialiased; -webkit-border-before-width: -1px; mso-pattern: auto; counter-increment: c; grid-row-start: span last; -webkit-box-shadow: 100px 2px; -webkit-border-after-width: -1px; -webkit-text-fill-color: white; ry: 0px; offset-rotation: auto; border-bottom-width: 1px; justify-self: center; order: inherit; mso-displayed-decimal-separator: '\.'; stroke-dasharray: none; min-height: max-content; mso-width-source: auto; background-image: url(#htmlvar00003); text-decoration-style: dotted; animation-fill-mode: forwards }
+abbr:nth-of-type(2) { mso-border-alt: solid .0pt; border-left-style: none; border-image-source: url(#svgvar00003); isolation: auto; motion-path: path('m 0 1 v 19'); border-image: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7) 0 0 -1 -1/8px stretch stretch; -webkit-animation-delay: -1s; -webkit-mask-composite: copy; -webkit-border-before-style: dashed; flex-direction: column; text-decoration-upright: digits bar; grid-column-gap: -1rad; animation-play-state: running; font-vendor: any; border-top-color: initial; stroke: black; break-before: page; -webkit-line-clamp: 0; marker-mid: url(#svgvar00002); -webkit-backface-visibility: visible }
+ruby, h1:empty, #htmlvar00003 { -webkit-columns: initial; mso-font-kerning: 0pt; border-bottom-width: 31px; break-after: avoid; backface-visibility: hidden; text-decoration-color: rgb(19,141,99); scroll-snap-points-x: repeat(5%); motion: path('M 1 -1 h 0 v 1') 4rad 17px; prince-hyphens: auto; mso-style-name: Normal; background-image: url(#svgvar00005); -webkit-filter: blur(5px); rx: 0px; rotation-code: '}'; column-rule: 43em solid red; offset-anchor: 54% 22%; -webkit-align-items: baseline; -webkit-background-origin: padding-box; rotation-code: '}'; text-decoration-style: solid }
+.class7 { transition-timing-function: ease; -webkit-animation: anim -1s linear infinite; -webkit-hyphens: auto; bottom: 6vh; outline-width: 0; -webkit-transition: all 5s linear; border: none; border-image: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7) 27% 0 0 round; -ms-text-combine-horizontal: all; border-bottom-color: black; overflow-x: auto; border-right-width: medium; motion-offset: 78%; min-height: 68%; -webkit-border-before-color: green; mso-generic-font-family: auto; -webkit-rtl-ordering: visual; direction: rtl; -webkit-print-color-adjust: exact; grid-auto-rows: auto }
+.class0 { text-transform: none; mso-style-name: Normal; rotation: 1deg; border-top-width: 1; -webkit-user-select: none; -webkit-opacity: 0.771616478014; stroke-dashoffset: 3; -webkit-appearance: textarea; offset-distance: 0; -webkit-transform: skew(-1deg); flood-color: green; border-top-style: dotted; text-decoration-style: dashed; background-position-x: 8px; white: fuchsia; -webkit-column-fill: auto; overflow-y: -webkit-paged-x; letter-spacing: 0.495240832818em; -webkit-border-bottom-left-radius: -1px; padding: 1px 0 0 0 }
+legend { min-height: 1vmin; background: url(#htmlvar00007) repeat-y 52% 510%; height: inherit; font-variant-caps: small-caps; -webkit-box-decoration-break: slice; column-fill: balance; -webkit-rtl-ordering: visual; border-size: 17px; -webkit-padding-before: 1px; -webkit-box-flex-group: 2; -webkit-transition: opacity 1s; motion-path: inherit; -webkit-column-rule-width: 1px; rotation: 1deg; mso-pattern: auto; -webkit-text-security: none; scale: 0; perspective: 0px; border-bottom-right-radius: 0px 9px; column-fill: balance }
+#htmlvar00004, strike:nth-of-type(2), .class7 { resize: vertical; -webkit-border-top-right-radius: 1px 1px; orientation: auto; -webkit-transition-delay: inherit; fill: black; offset-position: inherit; pointer-events: none; transition: opacity 0s linear; -webkit-mask-box-image-width: 0px; -webkit-highlight: 'green'; animation-direction: inherit; mso-border-top-alt: solid green .28pt; outline-width: 4px; border-spacing: -1 0; grid-area: 61; -webkit-align-self: stretch; flood-opacity: 0.40739840512; transition-delay: 0s; mso-style-parent: ''; position: absolute }
+script { -webkit-mask: below none; list-style-image: url(#svgvar00001); border-width: thin; mso-displayed-decimal-separator: '\.'; quotes: '-' '-'; transition-delay: initial; -webkit-margin-after-collapse: separate; postion: relative; flex-wrap: nowrap; box-reflect: right; -webkit-color-correction: default; font-style: none; -webkit-mask: below url(#svgvar00006); image-rendering: pixelated; -webkit-line-break: after-white-space; border-top-style: none; -webkit-transform-origin: top right; -webkit-box-align: center; box-direction: reverse; border-top: 1px solid }
+.class1, #htmlvar00004 { font-variant-caps: all-petite-caps; animation-iteration-count: infinite; text-align-last: justify; grid-column-gap: 3%; mso-displayed-decimal-separator: '\.'; border-bottom-color: red; shape-inside: polygon(); -webkit-animation-play-state: paused; -ms-text-combine-horizontal: all; -webkit-perspective: 1px; -webkit-writing-mode: vertical-lr; -webkit-shape-margin: 0px; -webkit-margin-top-collapse: separate; column-break-before: always; border-left-style: solid; box-pack: justify; box-direction: reverse; stroke-linejoin: miter; grid-column-start: 1; -ms-flex-align: center }
+a:active { -webkit-flex: auto; -webkit-mask-box-image-width: 0px; mso-rotate: 20; color: green; list-style-position: inside; background-clip: border-box; font-vendor: any; border-bottom-width: 0px; border-image-outset: 7px; -webkit-perspective-origin-y: 1px; -webkit-box-shadow: red 1px 1px; -webkit-user-select: auto; flood-color: rgb(141,212,67); -webkit-border-start-width: 1px; background-attachment: initial; -webkit-background-size: 73%; margin: auto auto; transform: scale(0); -webkit-text-orientation: upright; isolation: isolate }
+#htmlvar00001 { -webkit-column-rule: solid black; text-transform: uppercase; min-zoom: auto; -webkit-border-before-color: white; text-combine-upright: none; padding-bottom: -1pt; -webkit-transition-timing-function: steps(1, end); -webkit-perspective: -1px; float: -o-bottom; -webkit-border-after: 1px solid white; -webkit-animation-delay: -1s; scroll-snap-destination: 47 1px; mso-rotate: 1; -webkit-padding-before: -1px; margin-bottom: 45em; -webkit-text-emphasis-style: sesame; border-color: rgb(53,174,232); -webkit-border-end: solid; stroke: rgb(133,251,208); -webkit-writing-mode: vertical-tb }
+#htmlvar00008, :root, mark:nth-of-type(2) { border-top-style: groove; max-width: 1cm; -webkit-text-stroke: 4px ; -webkit-color-correction: sRGB; -webkit-border-before-width: 1px; box-decoration-break: clone; stroke: red; border-image: var(--cssvarb); grid-column-gap: -1rem; -webkit-column-break-inside: avoid; -webkit-mask-box-image-outset: 0px; -webkit-flex: none; transform-style: preserve-3d; flex-shrink: 0.148443463581; mso-padding-alt: 0in 0pt 1in 7pt; background-size: auto 0px; stroke-miterlimit: 0; -ms-flex-align: center; background-blend-mode: color, normal; padding-left: 1vmin }
+#htmlvar00005 { -webkit-perspective: 0px; css-float: left; grid-row: 0/auto; -webkit-transition-property: top, background; border-right-width: thin; -webkit-border-image: url(#svgvar00005) 1 0 1 -1; empty-cells: hide; box-direction: reverse; stroke-linecap: round; offset-rotation: auto 1deg; visibility: none; -webkit-animation-fill-mode: backwards; motion-rotation: auto 0deg; mso-font-kerning: -1pt; background-attachment: initial; -webkit-app-region: drag; -webkit-flow-from: flow1; -webkit-padding-end: 7px; margin-bottom: 0vw; border-top-width: 0px }
+input:focus { border-bottom-color: black; animation-direction: reverse; stroke-opacity: 1; rx: 16px; motion-offset: inherit; overflow: none; -webkit-flow-from: flow1; -webkit-align-self: flex-end; counter-reset: c; grid-column: span middle / middle; border-left-color: green; grid-auto-columns: 1px; lighting-color: red; background-position-x: -1px; background: url(#htmlvar00004) repeat-x left top; font-feature-settings: 'liga'; list-style: outside; -webkit-padding-end: 0px; box-flex-group: 0; border-style: inset }
+.class1 { stop-opacity: 0; -webkit-shape-outside: inset(99px 48px 0px 39px); -webkit-mask-repeat-y: inherit; word-wrap: break-word; -webkit-mask-box-image-width: 1px; transition-duration: initial; mso-width-alt: 1; mso-height-source: auto; -webkit-padding-end: 16px; -webkit-border-before: 18px solid black; stop-color: rgb(180,199,110); -webkit-color-correction: sRGB; -webkit-box-reflect: below 1px; src: local(8); -webkit-border-end: 1px dashed white; border-left: double 0px; marker-mid: url(#htmlvar00001); min-width: 0vmin; offset-anchor: inherit; text-decoration-upright: digits 95 }
+#htmlvar00007 { column-rule: 1em solid black; -webkit-tap-highlight-color: white; -webkit-highlight: 'green'; -webkit-background-size: contain; break-before: page; -webkit-column-break-inside: avoid; offset-path: path('M -1 1 H 0 V -1 H 0 L 0 13'); scroll-snap-points-y: initial; -webkit-hyphens: none; border-left-style: none; -webkit-mask-box-image-slice: 0 fill; mso-displayed-decimal-separator: '\.'; outline-color: invert; font-variant: small-caps auto; -webkit-text-stroke: 74px red; whitespace: nowrap; offset-rotation: inherit; -webkit-mask-box-image: none; -webkit-filter: grayscale(0); border-bottom-style: dashed }
+feFuncR::first-line { mask-source-type: alpha; box-sizing: border-box; -webkit-border-end-color: ; translate: 1px 0px 1px; background-position-x: 0px; -webkit-padding-after: 0px; letter-spacing: 0px; mso-width-source: userset; -webkit-column-count: -1; object-fit: cover; -webkit-border-before: -1px solid; box-reflect: right; height: 57vh; rotation: 61deg; line-break: after-white-space; background-color: white; -webkit-line-break: normal; transform-origin: top left; outline-bottom: none; shape-inside: polygon(9px 0px) }
+
+/*endcss*/
+</style>
+<script>
+
+function freememory() {
+ try { CollectGarbage(); } catch(err) { }
+ try { window.gc(); } catch(err) { }
+}
+
+var runcount = {'jsfuzzer':0, 'eventhandler1':0, 'eventhandler2':0, 'eventhandler3':0, 'eventhandler4':0, 'eventhandler5':0}
+
+function GetVariable(fuzzervars, var_type) { if(fuzzervars[var_type]) { return fuzzervars[var_type]; } else { return null; }}
+
+function SetVariable(fuzzervars, var_name, var_type) { fuzzervars[var_type] = var_name; }
+
+function jsfuzzer() {
+
+runcount["jsfuzzer"]++; if(runcount["jsfuzzer"] > 2) { return; }
+
+var fuzzervars = {};
+
+SetVariable(fuzzervars, window, 'Window');
+SetVariable(fuzzervars, document, 'Document');
+SetVariable(fuzzervars, document.body.firstChild, 'Element');
+
+//beginjs
+/* newvar{htmlvar00001:HTMLVideoElement} */ var htmlvar00001 = document.getElementById("htmlvar00001"); //HTMLVideoElement
+/* newvar{htmlvar00002:HTMLTrackElement} */ var htmlvar00002 = document.getElementById("htmlvar00002"); //HTMLTrackElement
+/* newvar{htmlvar00003:HTMLFormElement} */ var htmlvar00003 = document.getElementById("htmlvar00003"); //HTMLFormElement
+/* newvar{htmlvar00004:HTMLLegendElement} */ var htmlvar00004 = document.getElementById("htmlvar00004"); //HTMLLegendElement
+/* newvar{htmlvar00005:HTMLInputElement} */ var htmlvar00005 = document.getElementById("htmlvar00005"); //HTMLInputElement
+/* newvar{htmlvar00006:HTMLVideoElement} */ var htmlvar00006 = document.getElementById("htmlvar00006"); //HTMLVideoElement
+/* newvar{htmlvar00007:HTMLLinkElement} */ var htmlvar00007 = document.getElementById("htmlvar00007"); //HTMLLinkElement
+/* newvar{htmlvar00008:HTMLMapElement} */ var htmlvar00008 = document.getElementById("htmlvar00008"); //HTMLMapElement
+/* newvar{htmlvar00009:HTMLTextAreaElement} */ var htmlvar00009 = document.getElementById("htmlvar00009"); //HTMLTextAreaElement
+/* newvar{svgvar00001:SVGSVGElement} */ var svgvar00001 = document.getElementById("svgvar00001"); //SVGSVGElement
+/* newvar{svgvar00002:SVGForeignObjectElement} */ var svgvar00002 = document.getElementById("svgvar00002"); //SVGForeignObjectElement
+/* newvar{htmlvar00010:HTMMLUnknownElement} */ var htmlvar00010 = document.getElementById("htmlvar00010"); //HTMMLUnknownElement
+/* newvar{htmlvar00011:HTMLDialogElement} */ var htmlvar00011 = document.getElementById("htmlvar00011"); //HTMLDialogElement
+/* newvar{svgvar00003:SVGGElement} */ var svgvar00003 = document.getElementById("svgvar00003"); //SVGGElement
+/* newvar{svgvar00004:SVGElement} */ var svgvar00004 = document.getElementById("svgvar00004"); //SVGElement
+/* newvar{htmlvar00012:HTMLAnchorElement} */ var htmlvar00012 = document.getElementById("htmlvar00012"); //HTMLAnchorElement
+/* newvar{svgvar00005:SVGFEMergeElement} */ var svgvar00005 = document.getElementById("svgvar00005"); //SVGFEMergeElement
+/* newvar{svgvar00006:SVGAnimateTransformElement} */ var svgvar00006 = document.getElementById("svgvar00006"); //SVGAnimateTransformElement
+/* newvar{svgvar00007:SVGTSpanElement} */ var svgvar00007 = document.getElementById("svgvar00007"); //SVGTSpanElement
+/* newvar{svgvar00008:SVGCircleElement} */ var svgvar00008 = document.getElementById("svgvar00008"); //SVGCircleElement
+/* newvar{svgvar00009:SVGAnimateElement} */ var svgvar00009 = document.getElementById("svgvar00009"); //SVGAnimateElement
+/* newvar{svgvar00010:SVGAnimateTransformElement} */ var svgvar00010 = document.getElementById("svgvar00010"); //SVGAnimateTransformElement
+/* newvar{svgvar00011:SVGFEConvolveMatrixElement} */ var svgvar00011 = document.getElementById("svgvar00011"); //SVGFEConvolveMatrixElement
+/* newvar{svgvar00012:SVGElement} */ var svgvar00012 = document.getElementById("svgvar00012"); //SVGElement
+/* newvar{htmlvar00013:HTMLMarqueeElement} */ var htmlvar00013 = document.getElementById("htmlvar00013"); //HTMLMarqueeElement
+/* newvar{htmlvar00014:HTMLUListElement} */ var htmlvar00014 = document.getElementById("htmlvar00014"); //HTMLUListElement
+/* newvar{htmlvar00015:HTMLLIElement} */ var htmlvar00015 = document.getElementById("htmlvar00015"); //HTMLLIElement
+/* newvar{htmlvar00016:HTMLBaseElement} */ var htmlvar00016 = document.getElementById("htmlvar00016"); //HTMLBaseElement
+/* newvar{htmlvar00017:HTMLOListElement} */ var htmlvar00017 = document.getElementById("htmlvar00017"); //HTMLOListElement
+/* newvar{htmlvar00018:HTMLLIElement} */ var htmlvar00018 = document.getElementById("htmlvar00018"); //HTMLLIElement
+/* newvar{htmlvar00019:HTMLDirectoryElement} */ var htmlvar00019 = document.getElementById("htmlvar00019"); //HTMLDirectoryElement
+/* newvar{htmlvar00020:HTMLShadowElement} */ var htmlvar00020 = document.getElementById("htmlvar00020"); //HTMLShadowElement
+/* newvar{htmlvar00021:HTMLUnknownElement} */ var htmlvar00021 = document.getElementById("htmlvar00021"); //HTMLUnknownElement
+/* newvar{htmlvar00022:HTMLLIElement} */ var htmlvar00022 = document.getElementById("htmlvar00022"); //HTMLLIElement
+/* newvar{htmlvar00023:HTMLDetailsElement} */ var htmlvar00023 = document.getElementById("htmlvar00023"); //HTMLDetailsElement
+/* newvar{htmlvar00024:HTMLVideoElement} */ var htmlvar00024 = document.getElementById("htmlvar00024"); //HTMLVideoElement
+/* newvar{htmlvar00025:HTMLTimeElement} */ var htmlvar00025 = document.getElementById("htmlvar00025"); //HTMLTimeElement
+/* newvar{htmlvar00026:HTMLUnknownElement} */ var htmlvar00026 = document.getElementById("htmlvar00026"); //HTMLUnknownElement
+/* newvar{htmlvar00027:HTMLTimeElement} */ var htmlvar00027 = document.getElementById("htmlvar00027"); //HTMLTimeElement
+/* newvar{htmlvar00028:HTMLUnknownElement} */ var htmlvar00028 = document.createElement("noembed"); //HTMLUnknownElement
+/* newvar{htmlvar00029:HTMLProgressElement} */ var htmlvar00029 = document.createElement("progress"); //HTMLProgressElement
+/* newvar{htmlvar00030:HTMLMapElement} */ var htmlvar00030 = document.createElement("map"); //HTMLMapElement
+/* newvar{htmlvar00031:HTMLTextAreaElement} */ var htmlvar00031 = document.createElement("textarea"); //HTMLTextAreaElement
+/* newvar{htmlvar00032:HTMLUnknownElement} */ var htmlvar00032 = document.createElement("html"); //HTMLUnknownElement
+try { htmlvar00005.pattern = "" + String.fromCharCode(47, 68, 71, 89, 104, 54, 96, 97, 92, 121, 34, 52, 126, 44, 99, 64, 64, 111, 86, 51) + ""; } catch(e) { }
+try { /* newvar{var00001:EventHandler} */ var var00001 = svgvar00001.onplaying; } catch(e) { }
+try { if (!var00001) { var00001 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00001, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00004:XPathEvaluator} */ var var00004 = new XPathEvaluator(); } catch(e) { }
+try { if (!var00004) { var00004 = GetVariable(fuzzervars, 'XPathEvaluator'); } else { SetVariable(var00004, 'XPathEvaluator'); } } catch(e) { }
+try { /* newvar{var00003:XPathNSResolver} */ var var00003 = var00004.createNSResolver(htmlvar00004); } catch(e) { }
+try { if (!var00003) { var00003 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00003, 'XPathNSResolver'); } } catch(e) { }
+try { /* newvar{var00005:XPathResult} */ var var00005 = var00004.evaluate("//menu",htmlvar00029); } catch(e) { }
+try { if (!var00005) { var00005 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00005, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00002:XPathResult} */ var var00002 = document.evaluate("//strike",htmlvar00018,var00003,9,var00005); } catch(e) { }
+try { if (!var00002) { var00002 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00002, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00006:EventHandler} */ var var00006 = htmlvar00009.onsuspend; } catch(e) { }
+try { if (!var00006) { var00006 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00006, 'EventHandler'); } } catch(e) { }
+try { document.all[21%document.all.length].appendChild(htmlvar00022); } catch(e) { }
+try { svgvar00007.setAttribute("lengthAdjust", "spacingAndGlyphs"); } catch(e) { }
+try { /* newvar{var00007:EventHandler} */ var var00007 = window.ononline; } catch(e) { }
+try { if (!var00007) { var00007 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00007, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00008:long} */ var var00008 = htmlvar00025.clientWidth; } catch(e) { }
+try { svgvar00004.setAttribute("points", "-1 0 0 0 1 0"); } catch(e) { }
+try { /* newvar{var00009:SVGAnimatedLength} */ var var00009 = svgvar00008.cx; } catch(e) { }
+try { if (!var00009) { var00009 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00009, 'SVGAnimatedLength'); } } catch(e) { }
+try { /* newvar{var00010:SVGMatrix} */ var var00010 = svgvar00001.getCTM(); } catch(e) { }
+try { if (!var00010) { var00010 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00010, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00011:HTMLSlotElement} */ var var00011 = svgvar00009.assignedSlot; } catch(e) { }
+try { if (!var00011) { var00011 = GetVariable(fuzzervars, 'HTMLSlotElement'); } else { SetVariable(var00011, 'HTMLSlotElement'); SetVariable(var00011, 'Element'); SetVariable(var00011, 'GlobalEventHandlers'); SetVariable(var00011, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00012:long} */ var var00012 = htmlvar00024.width; } catch(e) { }
+try { /* newvar{var00014:XSLTProcessor} */ var var00014 = new XSLTProcessor(); } catch(e) { }
+try { if (!var00014) { var00014 = GetVariable(fuzzervars, 'XSLTProcessor'); } else { SetVariable(var00014, 'XSLTProcessor'); } } catch(e) { }
+try { /* newvar{var00013:DocumentFragment} */ var var00013 = var00014.transformToFragment(htmlvar00006,document); } catch(e) { }
+try { if (!var00013) { var00013 = GetVariable(fuzzervars, 'DocumentFragment'); } else { SetVariable(var00013, 'DocumentFragment'); SetVariable(var00013, 'Element'); SetVariable(var00013, 'GlobalEventHandlers'); SetVariable(var00013, 'EventTarget'); } } catch(e) { }
+try { svgvar00010.setAttribute("name", "Font"); } catch(e) { }
+try { /* newvar{var00017:PromiseRejectionEvent} */ var var00017 = document.createEvent("PromiseRejectionEvent"); } catch(e) { }
+try { if (!var00017) { var00017 = GetVariable(fuzzervars, 'PromiseRejectionEvent'); } else { SetVariable(var00017, 'PromiseRejectionEvent'); SetVariable(var00017, 'Event'); } } catch(e) { }
+try { /* newvar{var00016:Event} */ var var00016 = var00017; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00016) { var00016 = GetVariable(fuzzervars, 'Event'); } else { SetVariable(var00016, 'Event'); } } catch(e) { }
+try { /* newvar{var00015:boolean} */ var var00015 = var00016.cancelBubble; } catch(e) { }
+try { /* newvar{var00018:EventHandler} */ var var00018 = htmlvar00001.ontouchend; } catch(e) { }
+try { if (!var00018) { var00018 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00018, 'EventHandler'); } } catch(e) { }
+try { svgvar00008.onlostpointercapture = var00018; } catch(e) { }
+try { htmlvar00029.setAttribute("open", "true"); } catch(e) { }
+try { /* newvar{var00020:HTMLMediaElement} */ var var00020 = htmlvar00006; } catch(e) { }
+try { if (!var00020) { var00020 = GetVariable(fuzzervars, 'HTMLMediaElement'); } else { SetVariable(var00020, 'HTMLMediaElement'); SetVariable(var00020, 'Element'); SetVariable(var00020, 'GlobalEventHandlers'); SetVariable(var00020, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00019:TimeRanges} */ var var00019 = var00020.played; } catch(e) { }
+try { if (!var00019) { var00019 = GetVariable(fuzzervars, 'TimeRanges'); } else { SetVariable(var00019, 'TimeRanges'); } } catch(e) { }
+try { svgvar00003.setAttribute("descent", "1"); } catch(e) { }
+try { /* newvar{var00021:SVGAnimationElement} */ var var00021 = svgvar00006; } catch(e) { }
+try { if (!var00021) { var00021 = GetVariable(fuzzervars, 'SVGAnimationElement'); } else { SetVariable(var00021, 'SVGAnimationElement'); SetVariable(var00021, 'SVGTests'); SetVariable(var00021, 'SVGElement'); SetVariable(var00021, 'GlobalEventHandlers'); SetVariable(var00021, 'EventTarget'); SetVariable(var00021, 'GlobalEventHandlers'); } } catch(e) { }
+try { svgvar00005.insertAdjacentText("beforeBegin","1"); } catch(e) { }
+try { /* newvar{var00022:EventHandler} */ var var00022 = document.onkeyup; } catch(e) { }
+try { if (!var00022) { var00022 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00022, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00023:double} */ var var00023 = var00019.end(0); } catch(e) { }
+try { /* newvar{var00024:CSSStyleDeclaration} */ var var00024 = htmlvar00005.style; } catch(e) { }
+try { if (!var00024) { var00024 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00024, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00024.setProperty("-webkit-logical-width", "0px"); } catch(e) { }
+try { /* newvar{var00025:FileList} */ var var00025 = htmlvar00005.files; } catch(e) { }
+try { if (!var00025) { var00025 = GetVariable(fuzzervars, 'FileList'); } else { SetVariable(var00025, 'FileList'); } } catch(e) { }
+try { /* newvar{var00026:Range} */ var var00026 = document.caretRangeFromPoint(476,486); } catch(e) { }
+try { if (!var00026) { var00026 = GetVariable(fuzzervars, 'Range'); } else { SetVariable(var00026, 'Range'); } } catch(e) { }
+try { var00026.selectNode(htmlvar00018); } catch(e) { }
+try { htmlvar00018.setAttribute("onmousewheel", "eventhandler4()"); } catch(e) { }
+try { document.all[32%document.all.length].appendChild(htmlvar00008); } catch(e) { }
+try { svgvar00007.replaceWith(svgvar00007); } catch(e) { }
+try { /* newvar{var00027:Selection} */ var var00027 = document.getSelection(); } catch(e) { }
+try { if (!var00027) { var00027 = GetVariable(fuzzervars, 'Selection'); } else { SetVariable(var00027, 'Selection'); } } catch(e) { }
+try { var00027.addRange(var00026); } catch(e) { }
+try { var00024.setProperty("image-rendering", "pixelated"); } catch(e) { }
+try { window.resizeTo(10,0); } catch(e) { }
+try { /* newvar{var00028:DOMString} */ var var00028 = var00024.item(93%var00024.length); } catch(e) { }
+try { htmlvar00028.setAttribute("onbeforecut", "eventhandler1()"); } catch(e) { }
+try { var00024.setProperty("break-after", "column"); } catch(e) { }
+try { var00024.setProperty("motion-offset", "0"); } catch(e) { }
+try { var00024.setProperty("-webkit-mask-size", "-1px 1px"); } catch(e) { }
+try { /* newvar{var00030:eventhandler} */ var var00030 = eventhandler2; } catch(e) { }
+try { if (!var00030) { var00030 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00030, 'eventhandler'); } } catch(e) { }
+try { /* newvar{var00029:EventListener} */ var var00029 = var00030; } catch(e) { }
+try { if (!var00029) { var00029 = GetVariable(fuzzervars, 'EventListener'); } else { SetVariable(var00029, 'EventListener'); } } catch(e) { }
+try { svgvar00004.removeEventListener("webkitTransitionEnd",var00029,true); } catch(e) { }
+try { /* newvar{var00031:DOMString} */ var var00031 = htmlvar00005.selectionDirection; } catch(e) { }
+try { /* newvar{var00032:EventHandler} */ var var00032 = document.onfullscreenchange; } catch(e) { }
+try { if (!var00032) { var00032 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00032, 'EventHandler'); } } catch(e) { }
+try { htmlvar00031.setAttribute("hidden", "hidden"); } catch(e) { }
+try { htmlvar00013.detachedCallback(); } catch(e) { }
+try { htmlvar00005.alt = "" + String.fromCharCode(71, 59, 60, 89, 103, 114, 60, 40, 96, 102, 65, 74, 32, 114, 70, 110, 36, 81, 94, 47) + ""; } catch(e) { }
+try { /* newvar{var00033:ValidityState} */ var var00033 = htmlvar00031.validity; } catch(e) { }
+try { if (!var00033) { var00033 = GetVariable(fuzzervars, 'ValidityState'); } else { SetVariable(var00033, 'ValidityState'); } } catch(e) { }
+try { window.resizeBy(0,1); } catch(e) { }
+try { /* newvar{var00035:FrameRequestCallback} */ var var00035 = var00030; } catch(e) { }
+try { if (!var00035) { var00035 = GetVariable(fuzzervars, 'FrameRequestCallback'); } else { SetVariable(var00035, 'FrameRequestCallback'); } } catch(e) { }
+try { /* newvar{var00034:long} */ var var00034 = window.requestAnimationFrame(var00035); } catch(e) { }
+try { /* newvar{var00036:DOMString} */ var var00036 = var00020.tagName; } catch(e) { }
+try { var00024.setProperty("text-overflow", "clip"); } catch(e) { }
+try { var00024.setProperty("object-fit", "fill"); } catch(e) { }
+try { /* newvar{var00037:long} */ var var00037 = htmlvar00031.selectionStart; } catch(e) { }
+try { svgvar00005.onended = var00018; } catch(e) { }
+try { /* newvar{var00039:SVGTextPathElement} */ var var00039 = document.createElementNS("http://www.w3.org/2000/svg", "textPath"); } catch(e) { }
+try { if (!var00039) { var00039 = GetVariable(fuzzervars, 'SVGTextPathElement'); } else { SetVariable(var00039, 'SVGTextPathElement'); SetVariable(var00039, 'SVGURIReference'); SetVariable(var00039, 'SVGTextContentElement'); SetVariable(var00039, 'SVGGraphicsElement'); SetVariable(var00039, 'SVGElement'); SetVariable(var00039, 'GlobalEventHandlers'); SetVariable(var00039, 'EventTarget'); SetVariable(var00039, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00038:SVGURIReference} */ var var00038 = var00039; } catch(e) { }
+try { if (!var00038) { var00038 = GetVariable(fuzzervars, 'SVGURIReference'); } else { SetVariable(var00038, 'SVGURIReference'); } } catch(e) { }
+try { htmlvar00016.setAttribute("margin", "0px -1px"); } catch(e) { }
+try { /* newvar{var00041:SVGMarkerElement} */ var var00041 = document.createElementNS("http://www.w3.org/2000/svg", "marker"); } catch(e) { }
+try { if (!var00041) { var00041 = GetVariable(fuzzervars, 'SVGMarkerElement'); } else { SetVariable(var00041, 'SVGMarkerElement'); SetVariable(var00041, 'SVGFitToViewBox'); SetVariable(var00041, 'SVGElement'); SetVariable(var00041, 'GlobalEventHandlers'); SetVariable(var00041, 'EventTarget'); SetVariable(var00041, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00040:svg_url_marker} */ var var00040 = "url(#" + var00041.id + ")"; } catch(e) { }
+try { if (!var00040) { var00040 = GetVariable(fuzzervars, 'svg_url_marker'); } else { SetVariable(var00040, 'svg_url_marker'); } } catch(e) { }
+try { svgvar00004.setAttribute("marker-start", var00040); } catch(e) { }
+try { svgvar00007.setAttribute("scale", "0"); } catch(e) { }
+try { /* newvar{var00042:AudioTrackList} */ var var00042 = htmlvar00024.audioTracks; } catch(e) { }
+try { if (!var00042) { var00042 = GetVariable(fuzzervars, 'AudioTrackList'); } else { SetVariable(var00042, 'AudioTrackList'); SetVariable(var00042, 'EventTarget'); } } catch(e) { }
+try { var00042.onaddtrack = var00006; } catch(e) { }
+try { var00024.setProperty("letter-spacing", "0pt"); } catch(e) { }
+try { /* newvar{var00043:MutationEvent} */ var var00043 = document.createEvent("MutationEvents"); } catch(e) { }
+try { if (!var00043) { var00043 = GetVariable(fuzzervars, 'MutationEvent'); } else { SetVariable(var00043, 'MutationEvent'); SetVariable(var00043, 'Event'); } } catch(e) { }
+try { var00043.initMutationEvent("htmlvar00003",false,false,htmlvar00011,String.fromCodePoint(11508, 233662, 869800, 295739, 609468, 86038, 828545, 944467, 569880, 1088774, 645057, 392037, 928068, 1086215, 80943, 441709, 644175, 552998, 254441, 352387),String.fromCharCode(67, 125, 64, 45, 112, 65, 107, 36, 40, 115, 117, 82, 73, 89, 37, 96, 39, 48, 68, 102),String.fromCodePoint(200931, 232195, 204444, 549986, 577109, 26190, 687618, 176078, 859439, 2318, 346141, 730189, 652297, 861399, 477817, 403938, 1008438, 228471, 151159, 949977)); } catch(e) { }
+try { /* newvar{var00044:DOMStringMap} */ var var00044 = htmlvar00008.dataset; } catch(e) { }
+try { if (!var00044) { var00044 = GetVariable(fuzzervars, 'DOMStringMap'); } else { SetVariable(var00044, 'DOMStringMap'); } } catch(e) { }
+try { /* newvar{var00046:DocumentOrShadowRoot} */ var var00046 = document; } catch(e) { }
+try { if (!var00046) { var00046 = GetVariable(fuzzervars, 'DocumentOrShadowRoot'); } else { SetVariable(var00046, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00045:Selection} */ var var00045 = var00046.getSelection(); } catch(e) { }
+try { if (!var00045) { var00045 = GetVariable(fuzzervars, 'Selection'); } else { SetVariable(var00045, 'Selection'); } } catch(e) { }
+try { /* newvar{var00047:long} */ var var00047 = var00045.extentOffset; } catch(e) { }
+try { var00024.setProperty("-webkit-print-color-adjust", "economy"); } catch(e) { }
+try { svgvar00001.currentScale = 0.268559076327; } catch(e) { }
+try { /* newvar{var00048:DOMString} */ var var00048 = htmlvar00012.target; } catch(e) { }
+try { /* newvar{var00050:sequence_Dictionary_} */ var var00050 = { "rendering_intent": [0, 0] }; } catch(e) { }
+try { if (!var00050) { var00050 = GetVariable(fuzzervars, 'sequence_Dictionary_'); } else { SetVariable(var00050, 'sequence_Dictionary_'); } } catch(e) { }
+try { /* newvar{var00051:KeyframeEffectOptions} */ var var00051 = { delay: 65, direction: String.fromCharCode(81, 70, 68, 62, 57, 43, 82, 50, 36, 74, 107, 33, 32, 121, 56, 112, 72, 53, 93, 107), duration: 0, easing: "htmlvar00008", endDelay: 0, fill: "htmlvar00006", iterationStart: 0.169680220767, iterations: Infinity }; } catch(e) { }
+try { if (!var00051) { var00051 = GetVariable(fuzzervars, 'KeyframeEffectOptions'); } else { SetVariable(var00051, 'KeyframeEffectOptions'); } } catch(e) { }
+try { /* newvar{var00049:Animation} */ var var00049 = htmlvar00030.animate(var00050,var00051); } catch(e) { }
+try { if (!var00049) { var00049 = GetVariable(fuzzervars, 'Animation'); } else { SetVariable(var00049, 'Animation'); SetVariable(var00049, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00052:SVGAnimatedNumber} */ var var00052 = svgvar00011.divisor; } catch(e) { }
+try { if (!var00052) { var00052 = GetVariable(fuzzervars, 'SVGAnimatedNumber'); } else { SetVariable(var00052, 'SVGAnimatedNumber'); } } catch(e) { }
+try { /* newvar{var00053:MouseEvent} */ var var00053 = document.createEvent("MouseEvents"); } catch(e) { }
+try { if (!var00053) { var00053 = GetVariable(fuzzervars, 'MouseEvent'); } else { SetVariable(var00053, 'MouseEvent'); SetVariable(var00053, 'UIEvent'); SetVariable(var00053, 'Event'); } } catch(e) { }
+try { var00053.initMouseEvent(String.fromCharCode(101, 123, 118, 118, 74, 93, 46, 55, 40, 37, 43, 114, 47, 87, 106, 62, 81, 113, 90, 53),true,true,window); } catch(e) { }
+try { /* newvar{var00054:ScrollToOptions} */ var var00054 = {left: 48, top: 0}; } catch(e) { }
+try { if (!var00054) { var00054 = GetVariable(fuzzervars, 'ScrollToOptions'); } else { SetVariable(var00054, 'ScrollToOptions'); } } catch(e) { }
+try { htmlvar00030.scroll(var00054); } catch(e) { }
+try { /* newvar{var00055:TextEvent} */ var var00055 = document.createEvent("TextEvent"); } catch(e) { }
+try { if (!var00055) { var00055 = GetVariable(fuzzervars, 'TextEvent'); } else { SetVariable(var00055, 'TextEvent'); SetVariable(var00055, 'UIEvent'); SetVariable(var00055, 'Event'); } } catch(e) { }
+try { var00055.initTextEvent(String.fromCharCode(97, 70, 46, 42, 66, 51, 61, 47, 118, 125, 52, 64, 109, 98, 45, 61, 118, 59, 58, 75),true,true,window); } catch(e) { }
+try { document.fgColor = String.fromCharCode(68, 85, 52, 102, 125, 117, 94, 99, 44, 112, 63, 104, 58, 77, 111, 121, 58, 64, 101, 111); } catch(e) { }
+try { /* newvar{var00056:HTMLCollection} */ var var00056 = document.getElementsByClassName("class7"); } catch(e) { }
+try { if (!var00056) { var00056 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00056, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00057:DOMString} */ var var00057 = document.fgColor; } catch(e) { }
+try { htmlvar00001.defaultMuted = false; } catch(e) { }
+try { svgvar00005.prepend(String.fromCharCode(102, 78, 33, 65, 109, 125, 73, 62, 62, 87, 100, 80, 121, 94, 48, 94, 70, 56, 105, 63)); } catch(e) { }
+try { var00024.setProperty("border-style", "groove"); } catch(e) { }
+try { /* newvar{var00060:DragEvent} */ var var00060 = document.createEvent("DragEvent"); } catch(e) { }
+try { if (!var00060) { var00060 = GetVariable(fuzzervars, 'DragEvent'); } else { SetVariable(var00060, 'DragEvent'); SetVariable(var00060, 'MouseEvent'); SetVariable(var00060, 'UIEvent'); SetVariable(var00060, 'Event'); } } catch(e) { }
+try { /* newvar{var00059:DataTransfer} */ var var00059 = var00060.dataTransfer; } catch(e) { }
+try { if (!var00059) { var00059 = GetVariable(fuzzervars, 'DataTransfer'); } else { SetVariable(var00059, 'DataTransfer'); } } catch(e) { }
+try { /* newvar{var00058:FileList} */ var var00058 = var00059.files; } catch(e) { }
+try { if (!var00058) { var00058 = GetVariable(fuzzervars, 'FileList'); } else { SetVariable(var00058, 'FileList'); } } catch(e) { }
+try { htmlvar00005.files = var00058; } catch(e) { }
+try { var00024.setProperty("-webkit-animation-delay", "28s"); } catch(e) { }
+try { var00024.setProperty("-webkit-font-smoothing", "auto"); } catch(e) { }
+try { htmlvar00013.setAttribute("onwebkitkeymessage", "eventhandler1()"); } catch(e) { }
+try { htmlvar00009.maxLength = 1; } catch(e) { }
+try { svgvar00007.addEventListener("DOMSubtreeModified", var00029); } catch(e) { }
+try { /* newvar{var00061:HTMLCollection} */ var var00061 = document.getElementsByClassName("class9"); } catch(e) { }
+try { if (!var00061) { var00061 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00061, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00062:boolean} */ var var00062 = htmlvar00001.webkitDisplayingFullscreen; } catch(e) { }
+try { svgvar00012.tabIndex = 7; } catch(e) { }
+try { var00020.setAttribute("nowrap", "nowrap"); } catch(e) { }
+try { /* newvar{var00063:DocumentFragment} */ var var00063 = document.createDocumentFragment(); } catch(e) { }
+try { if (!var00063) { var00063 = GetVariable(fuzzervars, 'DocumentFragment'); } else { SetVariable(var00063, 'DocumentFragment'); SetVariable(var00063, 'Element'); SetVariable(var00063, 'GlobalEventHandlers'); SetVariable(var00063, 'EventTarget'); } } catch(e) { }
+try { svgvar00004.setAttribute("panose-1", "0 0 0 -1 1 2 1 1 -1 0"); } catch(e) { }
+try { /* newvar{var00064:StaticRange} */ var var00064 = sequence_StaticRange[0]; } catch(e) { }
+try { if (!var00064) { var00064 = GetVariable(fuzzervars, 'StaticRange'); } else { SetVariable(var00064, 'StaticRange'); } } catch(e) { }
+try { var00064.setStart(htmlvar00031,1); } catch(e) { }
+try { /* newvar{var00065:DOMString} */ var var00065 = htmlvar00003.prefix; } catch(e) { }
+try { var00024.setProperty("cellpadding", "0"); } catch(e) { }
+try { /* newvar{var00066:FontFaceSet} */ var var00066 = document.fonts; } catch(e) { }
+try { if (!var00066) { var00066 = GetVariable(fuzzervars, 'FontFaceSet'); } else { SetVariable(var00066, 'FontFaceSet'); SetVariable(var00066, 'EventTarget'); } } catch(e) { }
+try { var00066.onloading = var00007; } catch(e) { }
+try { var00055.initTextEvent(); } catch(e) { }
+try { htmlvar00016.setAttribute("onsuspend", "eventhandler3()"); } catch(e) { }
+try { svgvar00008.setAttribute("targetY", "0"); } catch(e) { }
+try { svgvar00007.setAttribute("clip-rule", "nonzero"); } catch(e) { }
+try { /* newvar{var00067:StyleSheet} */ var var00067 = htmlvar00007.sheet; } catch(e) { }
+try { if (!var00067) { var00067 = GetVariable(fuzzervars, 'StyleSheet'); } else { SetVariable(var00067, 'StyleSheet'); } } catch(e) { }
+try { htmlvar00012.download = "" + String.fromCharCode(79, 100, 44, 114, 85, 52, 123, 65, 47, 124, 81, 82, 43, 60, 123, 69, 92, 36, 51, 117) + ""; } catch(e) { }
+try { /* newvar{var00068:XPathResult} */ var var00068 = var00004.evaluate("//figcaption",document); } catch(e) { }
+try { if (!var00068) { var00068 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00068, 'XPathResult'); } } catch(e) { }
+try { htmlvar00025.setAttribute("checked", "checked"); } catch(e) { }
+try { /* newvar{var00069:SVGStyleElement} */ var var00069 = document.createElementNS("http://www.w3.org/2000/svg", "style"); } catch(e) { }
+try { if (!var00069) { var00069 = GetVariable(fuzzervars, 'SVGStyleElement'); } else { SetVariable(var00069, 'SVGStyleElement'); SetVariable(var00069, 'SVGElement'); SetVariable(var00069, 'GlobalEventHandlers'); SetVariable(var00069, 'EventTarget'); SetVariable(var00069, 'GlobalEventHandlers'); } } catch(e) { }
+try { var00069.media = String.fromCharCode(120, 40, 38, 95, 85, 83, 123, 68, 100, 92, 46, 40, 75, 87, 75, 74, 77, 109, 77, 60); } catch(e) { }
+try { var00011.translate = true; } catch(e) { }
+try { var00021.setAttribute("role", "button"); } catch(e) { }
+try { var00024.setProperty("list-style-image", "url(#svgvar00005)"); } catch(e) { }
+try { /* newvar{var00070:SVGAnimatedInteger} */ var var00070 = svgvar00011.targetX; } catch(e) { }
+try { if (!var00070) { var00070 = GetVariable(fuzzervars, 'SVGAnimatedInteger'); } else { SetVariable(var00070, 'SVGAnimatedInteger'); } } catch(e) { }
+try { htmlvar00021.setAttribute("onstalled", "eventhandler4()"); } catch(e) { }
+try { htmlvar00012.setAttribute("ontouchend", "eventhandler4()"); } catch(e) { }
+try { var00024.setProperty("mso-pagination", "widow-orphan"); } catch(e) { }
+try { /* newvar{var00071:BarProp} */ var var00071 = window.toolbar; } catch(e) { }
+try { if (!var00071) { var00071 = GetVariable(fuzzervars, 'BarProp'); } else { SetVariable(var00071, 'BarProp'); } } catch(e) { }
+try { /* newvar{var00072:SVGAnimatedLengthList} */ var var00072 = svgvar00007.dy; } catch(e) { }
+try { if (!var00072) { var00072 = GetVariable(fuzzervars, 'SVGAnimatedLengthList'); } else { SetVariable(var00072, 'SVGAnimatedLengthList'); } } catch(e) { }
+try { /* newvar{var00073:NodeList} */ var var00073 = svgvar00005.getDestinationInsertionPoints(); } catch(e) { }
+try { if (!var00073) { var00073 = GetVariable(fuzzervars, 'NodeList'); } else { SetVariable(var00073, 'NodeList'); } } catch(e) { }
+try { /* newvar{var00074:EventHandler} */ var var00074 = htmlvar00032.onended; } catch(e) { }
+try { if (!var00074) { var00074 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00074, 'EventHandler'); } } catch(e) { }
+try { htmlvar00012.text = "green"; } catch(e) { }
+try { document.exitFullscreen(); } catch(e) { }
+try { htmlvar00030.name = "" + String.fromCharCode(86, 114, 47, 69, 124, 100, 37, 81, 62, 58, 69, 36, 42, 55, 104, 92, 48, 126, 83, 86) + ""; } catch(e) { }
+try { /* newvar{var00075:DOMString} */ var var00075 = htmlvar00031.type; } catch(e) { }
+try { /* newvar{var00076:long} */ var var00076 = htmlvar00001.videoWidth; } catch(e) { }
+try { var00024.setProperty("scale", "92"); } catch(e) { }
+try { /* newvar{var00077:CSSStyleDeclaration} */ var var00077 = svgvar00004.style; } catch(e) { }
+try { if (!var00077) { var00077 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00077, 'CSSStyleDeclaration'); } } catch(e) { }
+try { svgvar00004.setAttribute("baseline-shift", "0"); } catch(e) { }
+try { /* newvar{var00078:long} */ var var00078 = htmlvar00017.start; } catch(e) { }
+try { /* newvar{var00079:Element} */ var var00079 = var00027.baseNode; } catch(e) { }
+try { if (!var00079) { var00079 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00079, 'Element'); SetVariable(var00079, 'GlobalEventHandlers'); SetVariable(var00079, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00080:USVString} */ var var00080 = htmlvar00012.pathname; } catch(e) { }
+try { if (!var00080) { var00080 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00080, 'USVString'); } } catch(e) { }
+try { htmlvar00012.protocol = var00080; } catch(e) { }
+try { /* newvar{var00081:HTMLSlotElement} */ var var00081 = htmlvar00017.assignedSlot; } catch(e) { }
+try { if (!var00081) { var00081 = GetVariable(fuzzervars, 'HTMLSlotElement'); } else { SetVariable(var00081, 'HTMLSlotElement'); SetVariable(var00081, 'Element'); SetVariable(var00081, 'GlobalEventHandlers'); SetVariable(var00081, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00082:Promise_void_} */ var var00082 = var00020.play(); } catch(e) { }
+try { if (!var00082) { var00082 = GetVariable(fuzzervars, 'Promise_void_'); } else { SetVariable(var00082, 'Promise_void_'); } } catch(e) { }
+try { htmlvar00002.setAttribute("onanimationstart", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00083:SVGMatrix} */ var var00083 = svgvar00002.getScreenCTM(); } catch(e) { }
+try { if (!var00083) { var00083 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00083, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00084:htmlstring} */ var var00084 = svgvar00008.outerHTML; } catch(e) { }
+try { if (!var00084) { var00084 = GetVariable(fuzzervars, 'htmlstring'); } else { SetVariable(var00084, 'htmlstring'); } } catch(e) { }
+try { var00021.setAttribute("by", "-1 -1 0 7"); } catch(e) { }
+try { /* newvar{var00085:DOMString} */ var var00085 = htmlvar00005.src; } catch(e) { }
+try { /* newvar{var00086:SVGMatrix} */ var var00086 = var00010.skewY(0.411695886616); } catch(e) { }
+try { if (!var00086) { var00086 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00086, 'SVGMatrix'); } } catch(e) { }
+try { htmlvar00002.srclang = "ur"; } catch(e) { }
+try { svgvar00002.setAttribute("flood-opacity", "28"); } catch(e) { }
+try { var00041.outerHTML = var00084; } catch(e) { }
+try { htmlvar00012.setAttribute("onseeking", "eventhandler5()"); } catch(e) { }
+try { var00024.setProperty("grid-row-gap", "inherit"); } catch(e) { }
+try { svgvar00012.normalize(); } catch(e) { }
+try { /* newvar{var00087:EventHandler} */ var var00087 = document.onpaste; } catch(e) { }
+try { if (!var00087) { var00087 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00087, 'EventHandler'); } } catch(e) { }
+try { var00041.setAttribute("font-weight", "bold"); } catch(e) { }
+try { htmlvar00030.setAttribute("aria-invalid", "false"); } catch(e) { }
+try { htmlvar00005.width = 0; } catch(e) { }
+try { var00024.setProperty("flex-direction", "horizontal"); } catch(e) { }
+try { var00024.setProperty("-webkit-transition-timing-function", "ease-in"); } catch(e) { }
+try { /* newvar{var00088:EventHandler} */ var var00088 = window.onstalled; } catch(e) { }
+try { if (!var00088) { var00088 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00088, 'EventHandler'); } } catch(e) { }
+try { htmlvar00030.setAttribute("onselectstart", "eventhandler3()"); } catch(e) { }
+try { /* newvar{var00089:DOMString} */ var var00089 = svgvar00006.getAttribute("codebase"); } catch(e) { }
+try { /* newvar{var00090:Element} */ var var00090 = htmlvar00029; } catch(e) { }
+try { if (!var00090) { var00090 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00090, 'Element'); SetVariable(var00090, 'GlobalEventHandlers'); SetVariable(var00090, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00092:SVGMaskElement} */ var var00092 = document.createElementNS("http://www.w3.org/2000/svg", "mask"); } catch(e) { }
+try { if (!var00092) { var00092 = GetVariable(fuzzervars, 'SVGMaskElement'); } else { SetVariable(var00092, 'SVGMaskElement'); SetVariable(var00092, 'SVGTests'); SetVariable(var00092, 'SVGElement'); SetVariable(var00092, 'GlobalEventHandlers'); SetVariable(var00092, 'EventTarget'); SetVariable(var00092, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00091:SVGAnimatedLength} */ var var00091 = var00092.width; } catch(e) { }
+try { if (!var00091) { var00091 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00091, 'SVGAnimatedLength'); } } catch(e) { }
+try { /* newvar{var00093:DataTransfer} */ var var00093 = var00060.dataTransfer; } catch(e) { }
+try { if (!var00093) { var00093 = GetVariable(fuzzervars, 'DataTransfer'); } else { SetVariable(var00093, 'DataTransfer'); } } catch(e) { }
+try { /* newvar{var00095:HTMLAreaElement} */ var var00095 = document.createElement("area"); } catch(e) { }
+try { if (!var00095) { var00095 = GetVariable(fuzzervars, 'HTMLAreaElement'); } else { SetVariable(var00095, 'HTMLAreaElement'); SetVariable(var00095, 'HTMLHyperlinkElementUtils'); SetVariable(var00095, 'Element'); SetVariable(var00095, 'GlobalEventHandlers'); SetVariable(var00095, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00094:HTMLHyperlinkElementUtils} */ var var00094 = var00095; } catch(e) { }
+try { if (!var00094) { var00094 = GetVariable(fuzzervars, 'HTMLHyperlinkElementUtils'); } else { SetVariable(var00094, 'HTMLHyperlinkElementUtils'); } } catch(e) { }
+try { htmlvar00005.useMap = "#htmlvar00005"; } catch(e) { }
+try { htmlvar00003.autocomplete = "on"; } catch(e) { }
+try { /* newvar{var00096:DOMString} */ var var00096 = window.name; } catch(e) { }
+try { htmlvar00030.oncut = var00001; } catch(e) { }
+try { /* newvar{var00097:EventHandler} */ var var00097 = document.onfullscreenchange; } catch(e) { }
+try { if (!var00097) { var00097 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00097, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00098:Event} */ var var00098 = var00043; } catch(e) { }
+try { if (!var00098) { var00098 = GetVariable(fuzzervars, 'Event'); } else { SetVariable(var00098, 'Event'); } } catch(e) { }
+try { /* newvar{var00099:SVGTransform} */ var var00099 = svgvar00001.createSVGTransform(); } catch(e) { }
+try { if (!var00099) { var00099 = GetVariable(fuzzervars, 'SVGTransform'); } else { SetVariable(var00099, 'SVGTransform'); } } catch(e) { }
+try { svgvar00004.setAttribute("k2", "1"); } catch(e) { }
+try { document.all[98%document.all.length].appendChild(htmlvar00018); } catch(e) { }
+try { htmlvar00004.scrollIntoView(true); } catch(e) { }
+try { document.cookie = "1"; } catch(e) { }
+try { /* newvar{var00100:CSSStyleDeclaration} */ var var00100 = htmlvar00028.style; } catch(e) { }
+try { if (!var00100) { var00100 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00100, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00102:URL} */ var var00102 = new URL("http://foo/bar"); } catch(e) { }
+try { if (!var00102) { var00102 = GetVariable(fuzzervars, 'URL'); } else { SetVariable(var00102, 'URL'); } } catch(e) { }
+try { /* newvar{var00101:URLSearchParams} */ var var00101 = var00102.searchParams; } catch(e) { }
+try { if (!var00101) { var00101 = GetVariable(fuzzervars, 'URLSearchParams'); } else { SetVariable(var00101, 'URLSearchParams'); } } catch(e) { }
+try { var00101.delete(var00080); } catch(e) { }
+try { var00043.initMutationEvent(String.fromCharCode(65, 33, 106, 83, 39, 43, 68, 108, 88, 78, 79, 73, 42, 125, 109, 32, 82, 106, 88, 34)); } catch(e) { }
+try { var00024.setProperty("-webkit-box-decoration-break", "slice"); } catch(e) { }
+try { htmlvar00032.scrollTo(); } catch(e) { }
+try { htmlvar00017.setAttribute("onselectstart", "eventhandler3()"); } catch(e) { }
+try { htmlvar00012.shape = "poly"; } catch(e) { }
+try { var00077.setProperty("min-width", "auto"); } catch(e) { }
+try { /* newvar{var00103:SVGPoint} */ var var00103 = var00039.getStartPositionOfChar(76); } catch(e) { }
+try { if (!var00103) { var00103 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00103, 'SVGPoint'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { var00069.setAttribute("alt", "icon"); } catch(e) { }
+try { var00095.hash = var00080; } catch(e) { }
+try { var00014.importStylesheet(htmlvar00004); } catch(e) { }
+try { var00049.finish(); } catch(e) { }
+try { /* newvar{var00104:boolean} */ var var00104 = document.execCommand("outdent", false); } catch(e) { }
+try { svgvar00003.setAttribute("markerHeight", "0"); } catch(e) { }
+try { /* newvar{var00105:short} */ var var00105 = htmlvar00001.readyState; } catch(e) { }
+try { var00100.setProperty("-webkit-transform-style", "preserve-3d"); } catch(e) { }
+try { /* newvar{var00106:XPathExpression} */ var var00106 = document.createExpression("//header"); } catch(e) { }
+try { if (!var00106) { var00106 = GetVariable(fuzzervars, 'XPathExpression'); } else { SetVariable(var00106, 'XPathExpression'); } } catch(e) { }
+try { var00083.a = 0.0952367827563; } catch(e) { }
+try { /* newvar{var00109:NodeIterator} */ var var00109 = document.createNodeIterator(var00013,33); } catch(e) { }
+try { if (!var00109) { var00109 = GetVariable(fuzzervars, 'NodeIterator'); } else { SetVariable(var00109, 'NodeIterator'); } } catch(e) { }
+try { /* newvar{var00108:NodeFilter} */ var var00108 = var00109.filter; } catch(e) { }
+try { if (!var00108) { var00108 = GetVariable(fuzzervars, 'NodeFilter'); } else { SetVariable(var00108, 'NodeFilter'); } } catch(e) { }
+try { /* newvar{var00107:NodeIterator} */ var var00107 = document.createNodeIterator(htmlvar00019,31,var00108); } catch(e) { }
+try { if (!var00107) { var00107 = GetVariable(fuzzervars, 'NodeIterator'); } else { SetVariable(var00107, 'NodeIterator'); } } catch(e) { }
+try { htmlvar00028.setAttribute("frameborder", "0"); } catch(e) { }
+try { htmlvar00006.setAttribute("ondragleave", "eventhandler1()"); } catch(e) { }
+try { htmlvar00018.setAttribute("autocomplete", "off"); } catch(e) { }
+try { var00066.onloading = var00007; } catch(e) { }
+try { /* newvar{var00110:HTMLCollection} */ var var00110 = document.plugins; } catch(e) { }
+try { if (!var00110) { var00110 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00110, 'HTMLCollection'); } } catch(e) { }
+try { var00081.setAttribute("aria-describedby", "htmlvar00007"); } catch(e) { }
+try { /* newvar{var00111:XPathResult} */ var var00111 = var00004.evaluate("//bdo",htmlvar00019,var00003,0,var00002); } catch(e) { }
+try { if (!var00111) { var00111 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00111, 'XPathResult'); } } catch(e) { }
+try { var00024.setProperty("outline", "green solid 41px"); } catch(e) { }
+try { var00081.ontimeupdate = var00007; } catch(e) { }
+try { /* newvar{var00112:EventHandler} */ var var00112 = htmlvar00002.onwebkitfullscreenchange; } catch(e) { }
+try { if (!var00112) { var00112 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00112, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00113:float} */ var var00113 = svgvar00007.getComputedTextLength(); } catch(e) { }
+try { /* newvar{var00114:HTMLBodyElement} */ var var00114 = document.createElement("body"); } catch(e) { }
+try { if (!var00114) { var00114 = GetVariable(fuzzervars, 'HTMLBodyElement'); } else { SetVariable(var00114, 'HTMLBodyElement'); SetVariable(var00114, 'WindowEventHandlers'); SetVariable(var00114, 'Element'); SetVariable(var00114, 'GlobalEventHandlers'); SetVariable(var00114, 'EventTarget'); } } catch(e) { }
+try { var00114.onscroll = var00032; } catch(e) { }
+try { /* newvar{var00115:short} */ var var00115 = var00002.resultType; } catch(e) { }
+try { htmlvar00018.setPointerCapture(1); } catch(e) { }
+try { svgvar00004.setAttribute("click", "none"); } catch(e) { }
+try { var00024.setProperty("rotation-code", "'\'}\''"); } catch(e) { }
+try { svgvar00009.removeAttribute(svgvar00009.attributes[80%svgvar00009.attributes.length].name); } catch(e) { }
+try { var00077.setProperty("user-select", "none"); } catch(e) { }
+try { /* newvar{var00116:double} */ var var00116 = htmlvar00029.max; } catch(e) { }
+try { htmlvar00031.placeholder = "" + String.fromCharCode(115, 102, 99, 47, 75, 89, 119, 95, 113, 37, 118, 126, 66, 110, 58, 42, 53, 51, 61, 33) + ""; } catch(e) { }
+try { svgvar00011.setAttribute("values", "1 -1; 0 1; 0"); } catch(e) { }
+try { htmlvar00015.setAttribute("baseprofile", "full"); } catch(e) { }
+try { /* newvar{var00117:boolean} */ var var00117 = document.execCommand("decreaseFontSize", false); } catch(e) { }
+try { var00092.setAttribute("min", "media"); } catch(e) { }
+try { var00024.setProperty("min-width", "max-content"); } catch(e) { }
+try { var00039.setAttribute("ry", "0"); } catch(e) { }
+try { htmlvar00024.setAttribute("height", "1"); } catch(e) { }
+try { htmlvar00019.setAttribute("async", "true"); } catch(e) { }
+try { var00024.setProperty("shape-image-threshold", "0"); } catch(e) { }
+try { htmlvar00017.setAttribute("onpause", "eventhandler5()"); } catch(e) { }
+try { var00100.setProperty("column-break-before", "always"); } catch(e) { }
+try { /* newvar{var00118:NodeIterator} */ var var00118 = document.createNodeIterator(htmlvar00025,0,var00108); } catch(e) { }
+try { if (!var00118) { var00118 = GetVariable(fuzzervars, 'NodeIterator'); } else { SetVariable(var00118, 'NodeIterator'); } } catch(e) { }
+try { var00100.setProperty("fonty-family", "Times"); } catch(e) { }
+try { svgvar00001.setAttribute("version", "78"); } catch(e) { }
+try { htmlvar00023.scroll(0.582886145768,0.727133938636); } catch(e) { }
+try { /* newvar{var00119:HTMLHyperlinkElementUtils} */ var var00119 = htmlvar00012; } catch(e) { }
+try { if (!var00119) { var00119 = GetVariable(fuzzervars, 'HTMLHyperlinkElementUtils'); } else { SetVariable(var00119, 'HTMLHyperlinkElementUtils'); } } catch(e) { }
+try { /* newvar{var00120:DOMString} */ var var00120 = htmlvar00005.namespaceURI; } catch(e) { }
+try { /* newvar{var00121:EventHandler} */ var var00121 = htmlvar00030.onpointermove; } catch(e) { }
+try { if (!var00121) { var00121 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00121, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00124:SVGAnimatedTransformList} */ var var00124 = svgvar00002.transform; } catch(e) { }
+try { if (!var00124) { var00124 = GetVariable(fuzzervars, 'SVGAnimatedTransformList'); } else { SetVariable(var00124, 'SVGAnimatedTransformList'); } } catch(e) { }
+try { /* newvar{var00123:SVGTransformList} */ var var00123 = var00124.animVal; } catch(e) { }
+try { if (!var00123) { var00123 = GetVariable(fuzzervars, 'SVGTransformList'); } else { SetVariable(var00123, 'SVGTransformList'); } } catch(e) { }
+try { /* newvar{var00122:SVGTransform} */ var var00122 = var00123.replaceItem(var00099,1); } catch(e) { }
+try { if (!var00122) { var00122 = GetVariable(fuzzervars, 'SVGTransform'); } else { SetVariable(var00122, 'SVGTransform'); } } catch(e) { }
+try { /* newvar{var00125:SVGTransform} */ var var00125 = svgvar00001.createSVGTransformFromMatrix(var00010); } catch(e) { }
+try { if (!var00125) { var00125 = GetVariable(fuzzervars, 'SVGTransform'); } else { SetVariable(var00125, 'SVGTransform'); } } catch(e) { }
+try { /* newvar{var00126:DOMString} */ var var00126 = htmlvar00005.value; } catch(e) { }
+try { /* newvar{var00127:MouseEvent} */ var var00127 = var00060; } catch(e) { }
+try { if (!var00127) { var00127 = GetVariable(fuzzervars, 'MouseEvent'); } else { SetVariable(var00127, 'MouseEvent'); SetVariable(var00127, 'UIEvent'); SetVariable(var00127, 'Event'); } } catch(e) { }
+try { /* newvar{var00129:HTMLEmbedElement} */ var var00129 = document.createElement("embed"); } catch(e) { }
+try { if (!var00129) { var00129 = GetVariable(fuzzervars, 'HTMLEmbedElement'); } else { SetVariable(var00129, 'HTMLEmbedElement'); SetVariable(var00129, 'Element'); SetVariable(var00129, 'GlobalEventHandlers'); SetVariable(var00129, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00128:Document} */ var var00128 = var00129.getSVGDocument(); } catch(e) { }
+try { if (!var00128) { var00128 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00128, 'Document'); SetVariable(var00128, 'GlobalEventHandlers'); SetVariable(var00128, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { var00128.onbeforecut = var00018; } catch(e) { }
+try { svgvar00012.setAttribute("contentScriptType", "text/ecmascript"); } catch(e) { }
+try { svgvar00001.setAttribute("stroke-join", "round"); } catch(e) { }
+try { /* newvar{var00130:DOMString} */ var var00130 = var00024.getPropertyValue("-webkit-animation-name"); } catch(e) { }
+try { /* newvar{var00131:boolean} */ var var00131 = htmlvar00013.draggable; } catch(e) { }
+try { var00100.setProperty("border-style", "dotted double dashed solid"); } catch(e) { }
+try { /* newvar{var00134:DOMRect} */ var var00134 = new DOMRect(1, 93, 50, 0); } catch(e) { }
+try { if (!var00134) { var00134 = GetVariable(fuzzervars, 'DOMRect'); } else { SetVariable(var00134, 'DOMRect'); SetVariable(var00134, 'DOMRectReadOnly'); } } catch(e) { }
+try { /* newvar{var00133:DOMRectReadOnly} */ var var00133 = var00134; } catch(e) { }
+try { if (!var00133) { var00133 = GetVariable(fuzzervars, 'DOMRectReadOnly'); } else { SetVariable(var00133, 'DOMRectReadOnly'); } } catch(e) { }
+try { /* newvar{var00132:double} */ var var00132 = var00133.width; } catch(e) { }
+try { var00100.setProperty("animation-delay", "initial"); } catch(e) { }
+try { var00024.setProperty("mso-fareast-font-family", "'Times New Roman'"); } catch(e) { }
+try { /* newvar{var00135:FormData} */ var var00135 = new FormData(); } catch(e) { }
+try { if (!var00135) { var00135 = GetVariable(fuzzervars, 'FormData'); } else { SetVariable(var00135, 'FormData'); } } catch(e) { }
+try { /* newvar{var00138:DataTransferItemList} */ var var00138 = var00093.items; } catch(e) { }
+try { if (!var00138) { var00138 = GetVariable(fuzzervars, 'DataTransferItemList'); } else { SetVariable(var00138, 'DataTransferItemList'); } } catch(e) { }
+try { /* newvar{var00137:DataTransferItem} */ var var00137 = var00138.add("1","foo"); } catch(e) { }
+try { if (!var00137) { var00137 = GetVariable(fuzzervars, 'DataTransferItem'); } else { SetVariable(var00137, 'DataTransferItem'); } } catch(e) { }
+try { /* newvar{var00136:Blob} */ var var00136 = var00137.getAsFile(); } catch(e) { }
+try { if (!var00136) { var00136 = GetVariable(fuzzervars, 'Blob'); } else { SetVariable(var00136, 'Blob'); } } catch(e) { }
+try { var00135.set(var00080,var00136); } catch(e) { }
+try { htmlvar00005.setAttribute("defer", "defer"); } catch(e) { }
+try { var00095.alt = "" + String.fromCharCode(102, 83, 112, 55, 99, 121, 55, 120, 90, 59, 93, 114, 47, 76, 114, 109, 91, 121, 69, 42) + ""; } catch(e) { }
+try { htmlvar00009.setSelectionRange(-1,7); } catch(e) { }
+try { var00095.noHref = false; } catch(e) { }
+try { /* newvar{var00139:boolean} */ var var00139 = htmlvar00020.draggable; } catch(e) { }
+try { /* newvar{var00140:EventHandler} */ var var00140 = var00114.onbeforeunload; } catch(e) { }
+try { if (!var00140) { var00140 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00140, 'EventHandler'); } } catch(e) { }
+try { var00024.setProperty("outline-width", "25px"); } catch(e) { }
+try { /* newvar{var00142:HTMLMapElement} */ var var00142 = document.createElement("map"); } catch(e) { }
+try { if (!var00142) { var00142 = GetVariable(fuzzervars, 'HTMLMapElement'); } else { SetVariable(var00142, 'HTMLMapElement'); SetVariable(var00142, 'Element'); SetVariable(var00142, 'GlobalEventHandlers'); SetVariable(var00142, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00141:DOMString} */ var var00141 = var00142.name; } catch(e) { }
+try { var00024.setProperty("justify-content", "space-evenly flex-start safe"); } catch(e) { }
+try { var00024.setProperty("direction", "rtl auto"); } catch(e) { }
+try { /* newvar{var00143:ApplicationCache} */ var var00143 = window.applicationCache; } catch(e) { }
+try { if (!var00143) { var00143 = GetVariable(fuzzervars, 'ApplicationCache'); } else { SetVariable(var00143, 'ApplicationCache'); SetVariable(var00143, 'EventTarget'); } } catch(e) { }
+try { var00143.onerror = var00121; } catch(e) { }
+try { svgvar00006.setAttribute("onmouseover", "var00030"); } catch(e) { }
+try { /* newvar{var00144:SVGFilterPrimitiveStandardAttributes} */ var var00144 = svgvar00011; } catch(e) { }
+try { if (!var00144) { var00144 = GetVariable(fuzzervars, 'SVGFilterPrimitiveStandardAttributes'); } else { SetVariable(var00144, 'SVGFilterPrimitiveStandardAttributes'); } } catch(e) { }
+try { /* newvar{var00145:DOMString} */ var var00145 = var00142.name; } catch(e) { }
+try { /* newvar{var00146:DOMString} */ var var00146 = htmlvar00012.download; } catch(e) { }
+try { /* newvar{var00149:HTMLStyleElement} */ var var00149 = document.createElement("style"); } catch(e) { }
+try { if (!var00149) { var00149 = GetVariable(fuzzervars, 'HTMLStyleElement'); } else { SetVariable(var00149, 'HTMLStyleElement'); SetVariable(var00149, 'Element'); SetVariable(var00149, 'GlobalEventHandlers'); SetVariable(var00149, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00148:StyleSheet} */ var var00148 = var00149.sheet; } catch(e) { }
+try { if (!var00148) { var00148 = GetVariable(fuzzervars, 'StyleSheet'); } else { SetVariable(var00148, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00147:MediaList} */ var var00147 = var00148.media; } catch(e) { }
+try { if (!var00147) { var00147 = GetVariable(fuzzervars, 'MediaList'); } else { SetVariable(var00147, 'MediaList'); } } catch(e) { }
+try { /* newvar{var00150:DOMString} */ var var00150 = var00081.localName; } catch(e) { }
+try { /* newvar{var00152:HTMLInputElement} */ var var00152 = document.createElement("input"); } catch(e) { }
+try { if (!var00152) { var00152 = GetVariable(fuzzervars, 'HTMLInputElement'); } else { SetVariable(var00152, 'HTMLInputElement'); SetVariable(var00152, 'Element'); SetVariable(var00152, 'GlobalEventHandlers'); SetVariable(var00152, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00151:boolean} */ var var00151 = var00152.required; } catch(e) { }
+try { var00100.setProperty("border-image", "url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7) 1 round"); } catch(e) { }
+try { var00021.setAttribute("edgeMode", "duplicate"); } catch(e) { }
+try { var00128.onpointerlockchange = var00006; } catch(e) { }
+try { svgvar00009.requestPointerLock(); } catch(e) { }
+try { htmlvar00001.scrollTop = 0.565240663854; } catch(e) { }
+try { var00123[0] = var00125; } catch(e) { }
+try { var00021.setAttribute("restart", "always"); } catch(e) { }
+try { /* newvar{var00153:Element} */ var var00153 = var00073.item(16%var00073.length); } catch(e) { }
+try { if (!var00153) { var00153 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00153, 'Element'); SetVariable(var00153, 'GlobalEventHandlers'); SetVariable(var00153, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00154:double} */ var var00154 = var00002.numberValue; } catch(e) { }
+try { /* newvar{var00155:DOMString} */ var var00155 = var00055.data; } catch(e) { }
+try { var00077.setProperty("animation", "anim 96s linear"); } catch(e) { }
+try { htmlvar00018.setAttribute("minlength", "8"); } catch(e) { }
+try { var00095.referrerPolicy = "no-referrer-when-downgrade"; } catch(e) { }
+try { /* newvar{var00156:long} */ var var00156 = htmlvar00005.minLength; } catch(e) { }
+try { /* newvar{var00157:boolean} */ var var00157 = var00013.isDefaultNamespace("htmlvar00001"); } catch(e) { }
+try { htmlvar00013.setAttribute("as", "font"); } catch(e) { }
+try { var00041.onsearch = var00121; } catch(e) { }
+try { htmlvar00009.setAttribute("http-equiv", "Refresh"); } catch(e) { }
+try { /* newvar{var00158:EventHandler} */ var var00158 = svgvar00010.ontoggle; } catch(e) { }
+try { if (!var00158) { var00158 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00158, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00159:SVGMatrix} */ var var00159 = var00083.skewY(0.266875931028); } catch(e) { }
+try { if (!var00159) { var00159 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00159, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00161:SVGFEMergeNodeElement} */ var var00161 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode"); } catch(e) { }
+try { if (!var00161) { var00161 = GetVariable(fuzzervars, 'SVGFEMergeNodeElement'); } else { SetVariable(var00161, 'SVGFEMergeNodeElement'); SetVariable(var00161, 'SVGElement'); SetVariable(var00161, 'GlobalEventHandlers'); SetVariable(var00161, 'EventTarget'); SetVariable(var00161, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00160:SVGAnimatedString} */ var var00160 = var00161.in1; } catch(e) { }
+try { if (!var00160) { var00160 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00160, 'SVGAnimatedString'); } } catch(e) { }
+try { var00026.expand(); } catch(e) { }
+try { var00026.setStart(var00095,-1); } catch(e) { }
+try { svgvar00011.before(svgvar00007); } catch(e) { }
+try { /* newvar{var00162:StyleSheet} */ var var00162 = var00069.sheet; } catch(e) { }
+try { if (!var00162) { var00162 = GetVariable(fuzzervars, 'StyleSheet'); } else { SetVariable(var00162, 'StyleSheet'); } } catch(e) { }
+try { htmlvar00021.click(); } catch(e) { }
+try { /* newvar{var00163:KeyboardEvent} */ var var00163 = document.createEvent("KeyboardEvents"); } catch(e) { }
+try { if (!var00163) { var00163 = GetVariable(fuzzervars, 'KeyboardEvent'); } else { SetVariable(var00163, 'KeyboardEvent'); SetVariable(var00163, 'UIEvent'); SetVariable(var00163, 'Event'); } } catch(e) { }
+try { var00163.initKeyboardEvent(String.fromCharCode(32, 121, 56, 48, 121, 94, 44, 121, 79, 52, 121, 60, 113, 105, 72, 81, 112, 36, 53, 87),false,false,window,String.fromCodePoint(264699, 219003, 112949, 868307, 855349, 681845, 21560, 271259, 649466, 1092409, 437330, 918229, 844313, 145453, 644713, 211317, 785444, 3084, 331522, 31531)); } catch(e) { }
+try { var00045.selectAllChildren(htmlvar00002); } catch(e) { }
+try { htmlvar00011.returnValue = String.fromCodePoint(967446, 250472, 731820, 828451, 197034, 844652, 210931, 522718, 14273, 503212, 694183, 738152, 248961, 1089283, 393807, 1036379, 201119, 707601, 1038541, 872171); } catch(e) { }
+try { /* newvar{var00164:EventHandler} */ var var00164 = svgvar00009.onmousedown; } catch(e) { }
+try { if (!var00164) { var00164 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00164, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00165:Element} */ var var00165 = htmlvar00025.removeChild(htmlvar00025.childNodes[78%htmlvar00025.childNodes.length]); } catch(e) { }
+try { if (!var00165) { var00165 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00165, 'Element'); SetVariable(var00165, 'GlobalEventHandlers'); SetVariable(var00165, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00167:HTMLSelectElement} */ var var00167 = document.createElement("select"); } catch(e) { }
+try { if (!var00167) { var00167 = GetVariable(fuzzervars, 'HTMLSelectElement'); } else { SetVariable(var00167, 'HTMLSelectElement'); SetVariable(var00167, 'Element'); SetVariable(var00167, 'GlobalEventHandlers'); SetVariable(var00167, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00166:HTMLOptionsCollection} */ var var00166 = var00167.options; } catch(e) { }
+try { if (!var00166) { var00166 = GetVariable(fuzzervars, 'HTMLOptionsCollection'); } else { SetVariable(var00166, 'HTMLOptionsCollection'); SetVariable(var00166, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00168:HTMLOptGroupElement} */ var var00168 = document.createElement("optgroup"); } catch(e) { }
+try { if (!var00168) { var00168 = GetVariable(fuzzervars, 'HTMLOptGroupElement'); } else { SetVariable(var00168, 'HTMLOptGroupElement'); SetVariable(var00168, 'Element'); SetVariable(var00168, 'GlobalEventHandlers'); SetVariable(var00168, 'EventTarget'); } } catch(e) { }
+try { var00166.add(var00168,htmlvar00006); } catch(e) { }
+try { var00024.setProperty("empty-cells", "show"); } catch(e) { }
+try { var00024.setProperty("border-image-outset", "-1"); } catch(e) { }
+try { var00024.setProperty("border-spacing", "-1px"); } catch(e) { }
+try { /* newvar{var00169:DOMString} */ var var00169 = htmlvar00007.as; } catch(e) { }
+try { /* newvar{var00170:Element} */ var var00170 = var00027.baseNode; } catch(e) { }
+try { if (!var00170) { var00170 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00170, 'Element'); SetVariable(var00170, 'GlobalEventHandlers'); SetVariable(var00170, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00171:Window} */ var var00171 = window.top; } catch(e) { }
+try { if (!var00171) { var00171 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00171, 'Window'); SetVariable(var00171, 'GlobalEventHandlers'); SetVariable(var00171, 'WindowBase64'); SetVariable(var00171, 'WindowEventHandlers'); SetVariable(var00171, 'WindowTimers'); SetVariable(var00171, 'EventTarget'); } } catch(e) { }
+try { var00055.initTextEvent(String.fromCodePoint(1027865, 384949, 370779, 565239, 87078, 397176, 610807, 248099, 541645, 75571, 433574, 1008884, 439735, 179928, 981686, 1008733, 1057728, 16615, 988193, 1071747),true,true,var00171); } catch(e) { }
+try { /* newvar{var00172:Element} */ var var00172 = var00128.adoptNode(htmlvar00007); } catch(e) { }
+try { if (!var00172) { var00172 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00172, 'Element'); SetVariable(var00172, 'GlobalEventHandlers'); SetVariable(var00172, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00173:HTMLFrameSetElement} */ var var00173 = document.createElement("frameset"); } catch(e) { }
+try { if (!var00173) { var00173 = GetVariable(fuzzervars, 'HTMLFrameSetElement'); } else { SetVariable(var00173, 'HTMLFrameSetElement'); SetVariable(var00173, 'WindowEventHandlers'); SetVariable(var00173, 'Element'); SetVariable(var00173, 'GlobalEventHandlers'); SetVariable(var00173, 'EventTarget'); } } catch(e) { }
+try { var00173.onorientationchange = var00022; } catch(e) { }
+try { /* newvar{var00174:double} */ var var00174 = var00010.f; } catch(e) { }
+try { /* newvar{var00175:long} */ var var00175 = window.screenY; } catch(e) { }
+try { /* newvar{var00176:Element} */ var var00176 = htmlvar00026; } catch(e) { }
+try { if (!var00176) { var00176 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00176, 'Element'); SetVariable(var00176, 'GlobalEventHandlers'); SetVariable(var00176, 'EventTarget'); } } catch(e) { }
+try { htmlvar00002.setAttribute("selected", "selected"); } catch(e) { }
+try { var00077.setProperty("object-fit", "fill"); } catch(e) { }
+try { htmlvar00017.setAttribute("autofocus", "autofocus"); } catch(e) { }
+try { /* newvar{var00177:boolean} */ var var00177 = var00163.altKey; } catch(e) { }
+try { svgvar00002.setAttribute("glyph-orientation-horizontal", "0"); } catch(e) { }
+try { htmlvar00008.setAttribute("is", "x-g"); } catch(e) { }
+try { /* newvar{var00194:CSSRule} */ var var00194 = var00100.parentRule; } catch(e) { }
+try { if (!var00194) { var00194 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00194, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00193:CSSKeyframesRule} */ var var00193 = var00194; } catch(e) { }
+try { if (!var00193) { var00193 = GetVariable(fuzzervars, 'CSSKeyframesRule'); } else { SetVariable(var00193, 'CSSKeyframesRule'); SetVariable(var00193, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00192:CSSKeyframeRule} */ var var00192 = var00193.findRule("htmlvar00007"); } catch(e) { }
+try { if (!var00192) { var00192 = GetVariable(fuzzervars, 'CSSKeyframeRule'); } else { SetVariable(var00192, 'CSSKeyframeRule'); SetVariable(var00192, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00191:CSSRule} */ var var00191 = var00192; } catch(e) { }
+try { if (!var00191) { var00191 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00191, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00190:CSSSupportsRule} */ var var00190 = var00191; } catch(e) { }
+try { if (!var00190) { var00190 = GetVariable(fuzzervars, 'CSSSupportsRule'); } else { SetVariable(var00190, 'CSSSupportsRule'); SetVariable(var00190, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00189:CSSRule} */ var var00189 = var00190; } catch(e) { }
+try { if (!var00189) { var00189 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00189, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00188:CSSMediaRule} */ var var00188 = var00189; } catch(e) { }
+try { if (!var00188) { var00188 = GetVariable(fuzzervars, 'CSSMediaRule'); } else { SetVariable(var00188, 'CSSMediaRule'); SetVariable(var00188, 'CSSGroupingRule'); SetVariable(var00188, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00187:CSSGroupingRule} */ var var00187 = var00188; } catch(e) { }
+try { if (!var00187) { var00187 = GetVariable(fuzzervars, 'CSSGroupingRule'); } else { SetVariable(var00187, 'CSSGroupingRule'); SetVariable(var00187, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00186:CSSRuleList} */ var var00186 = var00187.cssRules; } catch(e) { }
+try { if (!var00186) { var00186 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00186, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00185:CSSRule} */ var var00185 = var00186.item(85%var00186.length); } catch(e) { }
+try { if (!var00185) { var00185 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00185, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00184:CSSStyleRule} */ var var00184 = var00185; } catch(e) { }
+try { if (!var00184) { var00184 = GetVariable(fuzzervars, 'CSSStyleRule'); } else { SetVariable(var00184, 'CSSStyleRule'); SetVariable(var00184, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00183:CSSRule} */ var var00183 = var00184; } catch(e) { }
+try { if (!var00183) { var00183 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00183, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00182:CSSSupportsRule} */ var var00182 = var00183; } catch(e) { }
+try { if (!var00182) { var00182 = GetVariable(fuzzervars, 'CSSSupportsRule'); } else { SetVariable(var00182, 'CSSSupportsRule'); SetVariable(var00182, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00181:CSSRuleList} */ var var00181 = var00182.cssRules; } catch(e) { }
+try { if (!var00181) { var00181 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00181, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00180:CSSRule} */ var var00180 = var00181.item(93%var00181.length); } catch(e) { }
+try { if (!var00180) { var00180 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00180, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00179:CSSPageRule} */ var var00179 = var00180; } catch(e) { }
+try { if (!var00179) { var00179 = GetVariable(fuzzervars, 'CSSPageRule'); } else { SetVariable(var00179, 'CSSPageRule'); SetVariable(var00179, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00178:CSSRule} */ var var00178 = var00179; } catch(e) { }
+try { if (!var00178) { var00178 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00178, 'CSSRule'); } } catch(e) { }
+try { var00077.setProperty("direction", "rtl auto"); } catch(e) { }
+try { var00152.stepDown(); } catch(e) { }
+try { /* newvar{var00195:SVGAnimatedLength} */ var var00195 = var00041.refX; } catch(e) { }
+try { if (!var00195) { var00195 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00195, 'SVGAnimatedLength'); } } catch(e) { }
+try { /* newvar{var00196:DOMString} */ var var00196 = var00100.cssText; } catch(e) { }
+try { var00192.keyText = String.fromCharCode(49, 62, 111, 59, 60, 72, 85, 45, 95, 82, 113, 47, 110, 85, 106, 49, 121, 124, 78, 36); } catch(e) { }
+try { var00024.setProperty("text-indent", "1em each-line hanging"); } catch(e) { }
+try { /* newvar{var00197:Selection} */ var var00197 = var00046.getSelection(); } catch(e) { }
+try { if (!var00197) { var00197 = GetVariable(fuzzervars, 'Selection'); } else { SetVariable(var00197, 'Selection'); } } catch(e) { }
+try { htmlvar00008.scrollTo(); } catch(e) { }
+try { /* newvar{var00198:DOMString} */ var var00198 = htmlvar00012.referrerPolicy; } catch(e) { }
+try { /* newvar{var00199:CSSRule} */ var var00199 = var00190; } catch(e) { }
+try { if (!var00199) { var00199 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00199, 'CSSRule'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00200:EventHandler} */ var var00200 = htmlvar00006.onerror; } catch(e) { }
+try { if (!var00200) { var00200 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00200, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00201:boolean} */ var var00201 = var00002.booleanValue; } catch(e) { }
+try { /* newvar{var00202:DOMString} */ var var00202 = htmlvar00002.src; } catch(e) { }
+try { /* newvar{var00203:EventTarget} */ var var00203 = var00172; } catch(e) { }
+try { if (!var00203) { var00203 = GetVariable(fuzzervars, 'EventTarget'); } else { SetVariable(var00203, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00204:DOMString} */ var var00204 = var00171.status; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { htmlvar00020.setAttribute("enctype", "multipart/form-data"); } catch(e) { }
+try { var00026.selectNode(htmlvar00018); } catch(e) { }
+try { /* newvar{var00205:long} */ var var00205 = window.innerWidth; } catch(e) { }
+try { var00024.setProperty("-webkit-align-items", "flex-end"); } catch(e) { }
+try { /* newvar{var00206:DOMString} */ var var00206 = var00147.item(6%var00147.length); } catch(e) { }
+try { svgvar00007.scrollTo(var00054); } catch(e) { }
+try { /* newvar{var00208:SVGFEImageElement} */ var var00208 = document.createElementNS("http://www.w3.org/2000/svg", "feImage"); } catch(e) { }
+try { if (!var00208) { var00208 = GetVariable(fuzzervars, 'SVGFEImageElement'); } else { SetVariable(var00208, 'SVGFEImageElement'); SetVariable(var00208, 'SVGFilterPrimitiveStandardAttributes'); SetVariable(var00208, 'SVGURIReference'); SetVariable(var00208, 'SVGElement'); SetVariable(var00208, 'GlobalEventHandlers'); SetVariable(var00208, 'EventTarget'); SetVariable(var00208, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00207:SVGURIReference} */ var var00207 = var00208; } catch(e) { }
+try { if (!var00207) { var00207 = GetVariable(fuzzervars, 'SVGURIReference'); } else { SetVariable(var00207, 'SVGURIReference'); } } catch(e) { }
+try { var00026.collapse(true); } catch(e) { }
+try { /* newvar{var00209:boolean} */ var var00209 = var00167.autofocus; } catch(e) { }
+try { /* newvar{var00210:boolean} */ var var00210 = htmlvar00001.ended; } catch(e) { }
+try { svgvar00001.unsuspendRedraw(0); } catch(e) { }
+try { /* newvar{var00211:boolean} */ var var00211 = var00167.reportValidity(); } catch(e) { }
+try { svgvar00005.setAttribute("edgeMode", "duplicate"); } catch(e) { }
+try { /* newvar{var00212:SVGAnimatedInteger} */ var var00212 = svgvar00011.orderY; } catch(e) { }
+try { if (!var00212) { var00212 = GetVariable(fuzzervars, 'SVGAnimatedInteger'); } else { SetVariable(var00212, 'SVGAnimatedInteger'); } } catch(e) { }
+try { /* newvar{var00213:DOMString} */ var var00213 = svgvar00008.localName; } catch(e) { }
+try { /* newvar{var00214:DOMRectReadOnly} */ var var00214 = var00134; } catch(e) { }
+try { if (!var00214) { var00214 = GetVariable(fuzzervars, 'DOMRectReadOnly'); } else { SetVariable(var00214, 'DOMRectReadOnly'); } } catch(e) { }
+try { /* newvar{var00216:DOMRectReadOnly} */ var var00216 = var00134; } catch(e) { }
+try { if (!var00216) { var00216 = GetVariable(fuzzervars, 'DOMRectReadOnly'); } else { SetVariable(var00216, 'DOMRectReadOnly'); } } catch(e) { }
+try { /* newvar{var00215:double} */ var var00215 = var00216.width; } catch(e) { }
+try { /* newvar{var00217:EventHandler} */ var var00217 = var00128.onpointerlockchange; } catch(e) { }
+try { if (!var00217) { var00217 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00217, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00218:SVGElement} */ var var00218 = var00021.lastChild; } catch(e) { }
+try { if (!var00218) { var00218 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00218, 'SVGElement'); SetVariable(var00218, 'GlobalEventHandlers'); SetVariable(var00218, 'EventTarget'); SetVariable(var00218, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00219:SVGMatrix} */ var var00219 = var00010.scale(0.948353783924); } catch(e) { }
+try { if (!var00219) { var00219 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00219, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00220:long} */ var var00220 = var00066.size; } catch(e) { }
+try { /* newvar{var00221:boolean} */ var var00221 = window.find(); } catch(e) { }
+try { var00059.setDragImage(htmlvar00019,1,6); } catch(e) { }
+try { htmlvar00016.setAttribute("left", "0"); } catch(e) { }
+try { /* newvar{var00222:long} */ var var00222 = htmlvar00017.start; } catch(e) { }
+try { var00077.setProperty("grid-auto-columns", "26%"); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { var00143.swapCache(); } catch(e) { }
+try { htmlvar00016.onpointerleave = var00158; } catch(e) { }
+try { var00100.setProperty("-webkit-box-orient", "vertical"); } catch(e) { }
+try { /* newvar{var00223:SVGAnimatedAngle} */ var var00223 = var00041.orientAngle; } catch(e) { }
+try { if (!var00223) { var00223 = GetVariable(fuzzervars, 'SVGAnimatedAngle'); } else { SetVariable(var00223, 'SVGAnimatedAngle'); } } catch(e) { }
+try { /* newvar{var00226:HTMLIFrameElement} */ var var00226 = document.createElement("iframe"); } catch(e) { }
+try { if (!var00226) { var00226 = GetVariable(fuzzervars, 'HTMLIFrameElement'); } else { SetVariable(var00226, 'HTMLIFrameElement'); SetVariable(var00226, 'Element'); SetVariable(var00226, 'GlobalEventHandlers'); SetVariable(var00226, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00225:Document} */ var var00225 = var00226.contentDocument; } catch(e) { }
+try { if (!var00225) { var00225 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00225, 'Document'); SetVariable(var00225, 'GlobalEventHandlers'); SetVariable(var00225, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00224:boolean} */ var var00224 = var00225.execCommand("insertBrOnReturn", false); } catch(e) { }
+try { var00077.setProperty("mso-rotate", "0"); } catch(e) { }
+try { /* newvar{var00227:DOMString} */ var var00227 = htmlvar00031.type; } catch(e) { }
+try { /* newvar{var00228:boolean} */ var var00228 = var00225.execCommand("decreaseFontSize", false); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { htmlvar00005.multiple = false; } catch(e) { }
+try { var00171.onanimationiteration = var00001; } catch(e) { }
+try { /* newvar{var00229:EventHandler} */ var var00229 = var00114.onfocus; } catch(e) { }
+try { if (!var00229) { var00229 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00229, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00230:SVGAnimatedString} */ var var00230 = svgvar00011.in1; } catch(e) { }
+try { if (!var00230) { var00230 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00230, 'SVGAnimatedString'); } } catch(e) { }
+try { htmlvar00003.reset(); } catch(e) { }
+try { htmlvar00005.formNoValidate = true; } catch(e) { }
+try { var00171.defaultStatus = "1"; } catch(e) { }
+try { /* newvar{var00231:SVGAnimatedString} */ var var00231 = var00161.in1; } catch(e) { }
+try { if (!var00231) { var00231 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00231, 'SVGAnimatedString'); } } catch(e) { }
+try { var00027.collapseToEnd(); } catch(e) { }
+try { var00041.setOrientToAuto(); } catch(e) { }
+try { /* newvar{var00232:boolean} */ var var00232 = var00152.reportValidity(); } catch(e) { }
+try { var00129.setAttribute("standby", "" + String.fromCharCode(93, 79, 50, 68, 120, 96, 91, 89, 72, 90, 97, 80, 39, 101, 54, 32, 126, 66, 68, 74) + ""); } catch(e) { }
+try { /* newvar{var00233:boolean} */ var var00233 = var00066.check("foo"); } catch(e) { }
+try { htmlvar00025.setAttribute("oncut", "eventhandler4()"); } catch(e) { }
+try { var00049.id = "foo"; } catch(e) { }
+try { svgvar00007.setAttribute("textLength", "46"); } catch(e) { }
+try { /* newvar{var00235:HTMLTitleElement} */ var var00235 = document.createElement("title"); } catch(e) { }
+try { if (!var00235) { var00235 = GetVariable(fuzzervars, 'HTMLTitleElement'); } else { SetVariable(var00235, 'HTMLTitleElement'); SetVariable(var00235, 'Element'); SetVariable(var00235, 'GlobalEventHandlers'); SetVariable(var00235, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00234:Element} */ var var00234 = var00235; } catch(e) { }
+try { if (!var00234) { var00234 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00234, 'Element'); SetVariable(var00234, 'GlobalEventHandlers'); SetVariable(var00234, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00236:EventTarget} */ var var00236 = var00053.relatedTarget; } catch(e) { }
+try { if (!var00236) { var00236 = GetVariable(fuzzervars, 'EventTarget'); } else { SetVariable(var00236, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00237:DOMString} */ var var00237 = var00152.value; } catch(e) { }
+try { htmlvar00031.setAttribute("aria-labelledby", "htmlvar00001"); } catch(e) { }
+try { /* newvar{var00238:EventHandler} */ var var00238 = svgvar00009.onwheel; } catch(e) { }
+try { if (!var00238) { var00238 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00238, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00239:boolean} */ var var00239 = htmlvar00009.autofocus; } catch(e) { }
+try { /* newvar{var00240:XPathNSResolver} */ var var00240 = document.createNSResolver(var00167); } catch(e) { }
+try { if (!var00240) { var00240 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00240, 'XPathNSResolver'); } } catch(e) { }
+try { var00024.setProperty("line-break", "before-white-space"); } catch(e) { }
+try { var00128.oncut = var00097; } catch(e) { }
+try { htmlvar00030.setAttribute("translate", "yes"); } catch(e) { }
+try { htmlvar00017.setAttribute("face", "Courier"); } catch(e) { }
+try { var00045.modify("move","backward"); } catch(e) { }
+try { htmlvar00023.setAttribute("allowfullscreen", "false"); } catch(e) { }
+try { var00197.collapse(htmlvar00004); } catch(e) { }
+try { var00218.setAttribute("underline-thickness", "36"); } catch(e) { }
+try { /* newvar{var00241:long} */ var var00241 = var00056.length; } catch(e) { }
+try { /* newvar{var00242:DOMString} */ var var00242 = var00226.frameBorder; } catch(e) { }
+try { svgvar00004.setAttribute("baseProfile", "tiny"); } catch(e) { }
+try { /* newvar{var00243:EventHandler} */ var var00243 = var00235.onerror; } catch(e) { }
+try { if (!var00243) { var00243 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00243, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00244:NodeList} */ var var00244 = htmlvar00019.querySelectorAll("legend .class7 #htmlvar00005 noscript #htmlvar00006"); } catch(e) { }
+try { if (!var00244) { var00244 = GetVariable(fuzzervars, 'NodeList'); } else { SetVariable(var00244, 'NodeList'); } } catch(e) { }
+try { /* newvar{var00245:long} */ var var00245 = htmlvar00001.videoHeight; } catch(e) { }
+try { htmlvar00016.setAttribute("rel", "noreferrer"); } catch(e) { }
+try { htmlvar00023.onpointerup = var00121; } catch(e) { }
+try { var00226.scrolling = "no"; } catch(e) { }
+try { var00024.setProperty("prince-hyphens", "auto"); } catch(e) { }
+try { var00114.onerror = var00229; } catch(e) { }
+try { var00161.setAttribute("unicode-bidi", "bidi-override"); } catch(e) { }
+try { htmlvar00031.setSelectionRange(); } catch(e) { }
+try { /* newvar{var00247:HTMLFrameElement} */ var var00247 = document.createElement("frame"); } catch(e) { }
+try { if (!var00247) { var00247 = GetVariable(fuzzervars, 'HTMLFrameElement'); } else { SetVariable(var00247, 'HTMLFrameElement'); SetVariable(var00247, 'Element'); SetVariable(var00247, 'GlobalEventHandlers'); SetVariable(var00247, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00246:DOMString} */ var var00246 = var00247.frameBorder; } catch(e) { }
+try { /* newvar{var00248:boolean} */ var var00248 = var00152.defaultChecked; } catch(e) { }
+try { var00077.setProperty("-webkit-hyphens", "none"); } catch(e) { }
+try { htmlvar00006.onmouseout = var00088; } catch(e) { }
+try { svgvar00012.setAttribute("display", "inline"); } catch(e) { }
+try { htmlvar00019.setAttribute("startval", "" + String.fromCharCode(50, 36, 116, 91, 48, 64, 112, 76, 72, 52, 107, 106, 108, 100, 94, 96, 92, 94, 113, 115) + ""); } catch(e) { }
+try { /* newvar{var00249:EventHandler} */ var var00249 = var00171.onanimationstart; } catch(e) { }
+try { if (!var00249) { var00249 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00249, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00250:boolean} */ var var00250 = var00163.altKey; } catch(e) { }
+try { var00100.setProperty("-webkit-column-span", "1"); } catch(e) { }
+try { htmlvar00005.onerror = var00112; } catch(e) { }
+try { /* newvar{var00251:EventHandler} */ var var00251 = var00114.onstorage; } catch(e) { }
+try { if (!var00251) { var00251 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00251, 'EventHandler'); } } catch(e) { }
+try { var00102.pathname = var00080; } catch(e) { }
+try { htmlvar00009.setAttribute("color", "black"); } catch(e) { }
+try { /* newvar{var00259:HTMLMediaElement} */ var var00259 = htmlvar00001; } catch(e) { }
+try { if (!var00259) { var00259 = GetVariable(fuzzervars, 'HTMLMediaElement'); } else { SetVariable(var00259, 'HTMLMediaElement'); SetVariable(var00259, 'Element'); SetVariable(var00259, 'GlobalEventHandlers'); SetVariable(var00259, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00264:TextTrackList} */ var var00264 = htmlvar00006.textTracks; } catch(e) { }
+try { if (!var00264) { var00264 = GetVariable(fuzzervars, 'TextTrackList'); } else { SetVariable(var00264, 'TextTrackList'); SetVariable(var00264, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00263:TextTrack} */ var var00263 = var00264[55%var00264.length]; } catch(e) { }
+try { if (!var00263) { var00263 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00263, 'TextTrack'); SetVariable(var00263, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00262:TextTrackKind} */ var var00262 = var00263.kind; } catch(e) { }
+try { if (!var00262) { var00262 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00262, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00261:TextTrack} */ var var00261 = var00259.addTextTrack(var00262); } catch(e) { }
+try { if (!var00261) { var00261 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00261, 'TextTrack'); SetVariable(var00261, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00260:TextTrackKind} */ var var00260 = var00261.kind; } catch(e) { }
+try { if (!var00260) { var00260 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00260, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00258:TextTrack} */ var var00258 = var00259.addTextTrack(var00260,"htmlvar00009","htmlvar00006"); } catch(e) { }
+try { if (!var00258) { var00258 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00258, 'TextTrack'); SetVariable(var00258, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00257:TextTrackKind} */ var var00257 = var00258.kind; } catch(e) { }
+try { if (!var00257) { var00257 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00257, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00256:TextTrack} */ var var00256 = htmlvar00006.addTextTrack(var00257); } catch(e) { }
+try { if (!var00256) { var00256 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00256, 'TextTrack'); SetVariable(var00256, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00255:TextTrackKind} */ var var00255 = var00256.kind; } catch(e) { }
+try { if (!var00255) { var00255 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00255, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00254:TextTrack} */ var var00254 = var00020.addTextTrack(var00255,String.fromCharCode(51, 78, 53, 43, 74, 122, 102, 61, 88, 75, 103, 32, 47, 64, 98, 92, 37, 41, 91, 126),String.fromCharCode(92, 90, 73, 52, 46, 94, 92, 80, 105, 102, 100, 119, 123, 90, 121, 71, 66, 35, 32, 122)); } catch(e) { }
+try { if (!var00254) { var00254 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00254, 'TextTrack'); SetVariable(var00254, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00253:VTTRegionList} */ var var00253 = var00254.regions; } catch(e) { }
+try { if (!var00253) { var00253 = GetVariable(fuzzervars, 'VTTRegionList'); } else { SetVariable(var00253, 'VTTRegionList'); } } catch(e) { }
+try { /* newvar{var00252:VTTRegion} */ var var00252 = var00253.item(96%var00253.length); } catch(e) { }
+try { if (!var00252) { var00252 = GetVariable(fuzzervars, 'VTTRegion'); } else { SetVariable(var00252, 'VTTRegion'); } } catch(e) { }
+try { var00252.regionAnchorY = 0.590279050564; } catch(e) { }
+try { var00100.setProperty("box-flex-group", "36"); } catch(e) { }
+try { /* newvar{var00265:Element} */ var var00265 = var00225.pointerLockElement; } catch(e) { }
+try { if (!var00265) { var00265 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00265, 'Element'); SetVariable(var00265, 'GlobalEventHandlers'); SetVariable(var00265, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00266:Window} */ var var00266 = var00247.contentWindow; } catch(e) { }
+try { if (!var00266) { var00266 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00266, 'Window'); SetVariable(var00266, 'GlobalEventHandlers'); SetVariable(var00266, 'WindowBase64'); SetVariable(var00266, 'WindowEventHandlers'); SetVariable(var00266, 'WindowTimers'); SetVariable(var00266, 'EventTarget'); } } catch(e) { }
+try { htmlvar00006.onmouseout = var00158; } catch(e) { }
+try { /* newvar{var00267:double} */ var var00267 = htmlvar00029.value; } catch(e) { }
+try { var00147.deleteMedium("foo"); } catch(e) { }
+try { var00152.stepUp(1); } catch(e) { }
+try { var00100.setProperty("border-top-color", "initial"); } catch(e) { }
+try { htmlvar00031.setCustomValidity(String.fromCodePoint(900493, 131354, 511387, 430543, 583798, 161051, 191171, 138615, 751979, 405211, 10230, 86705, 781640, 484665, 145255, 373566, 328279, 753946, 794099, 755258)); } catch(e) { }
+try { /* newvar{var00268:DOMString} */ var var00268 = htmlvar00009.inputMode; } catch(e) { }
+try { /* newvar{var00270:XPathNSResolver} */ var var00270 = var00128.createNSResolver(htmlvar00013); } catch(e) { }
+try { if (!var00270) { var00270 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00270, 'XPathNSResolver'); } } catch(e) { }
+try { /* newvar{var00269:XPathResult} */ var var00269 = var00004.evaluate("//rp",htmlvar00023,var00270,21,var00005); } catch(e) { }
+try { if (!var00269) { var00269 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00269, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00271:boolean} */ var var00271 = document.xmlStandalone; } catch(e) { }
+try { /* newvar{var00272:HTMLMarqueeElement} */ var var00272 = document.createElement("marquee"); } catch(e) { }
+try { if (!var00272) { var00272 = GetVariable(fuzzervars, 'HTMLMarqueeElement'); } else { SetVariable(var00272, 'HTMLMarqueeElement'); SetVariable(var00272, 'Element'); SetVariable(var00272, 'GlobalEventHandlers'); SetVariable(var00272, 'EventTarget'); } } catch(e) { }
+try { var00272.loop = 0; } catch(e) { }
+try { /* newvar{var00273:DOMString} */ var var00273 = var00184.cssText; } catch(e) { }
+try { /* newvar{var00274:Element} */ var var00274 = htmlvar00020.insertBefore(htmlvar00031,htmlvar00020.childNodes[32%htmlvar00020.childNodes.length]); } catch(e) { }
+try { if (!var00274) { var00274 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00274, 'Element'); SetVariable(var00274, 'GlobalEventHandlers'); SetVariable(var00274, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00275:boolean} */ var var00275 = svgvar00008.isPointInStroke(var00103); } catch(e) { }
+try { htmlvar00005.capture = true; } catch(e) { }
+try { var00092.setAttribute("version", "0"); } catch(e) { }
+try { /* newvar{var00277:SVGPolygonElement} */ var var00277 = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); } catch(e) { }
+try { if (!var00277) { var00277 = GetVariable(fuzzervars, 'SVGPolygonElement'); } else { SetVariable(var00277, 'SVGPolygonElement'); SetVariable(var00277, 'SVGGeometryElement'); SetVariable(var00277, 'SVGGraphicsElement'); SetVariable(var00277, 'SVGElement'); SetVariable(var00277, 'GlobalEventHandlers'); SetVariable(var00277, 'EventTarget'); SetVariable(var00277, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00276:SVGPointList} */ var var00276 = var00277.points; } catch(e) { }
+try { if (!var00276) { var00276 = GetVariable(fuzzervars, 'SVGPointList'); } else { SetVariable(var00276, 'SVGPointList'); } } catch(e) { }
+try { var00276[0] = var00103; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00278:EventHandler} */ var var00278 = var00128.onselectionchange; } catch(e) { }
+try { if (!var00278) { var00278 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00278, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00279:CSSStyleDeclaration} */ var var00279 = htmlvar00021.style; } catch(e) { }
+try { if (!var00279) { var00279 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00279, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00280:USVString} */ var var00280 = var00102.port; } catch(e) { }
+try { if (!var00280) { var00280 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00280, 'USVString'); } } catch(e) { }
+try { document.all[50%document.all.length].appendChild(var00168); } catch(e) { }
+try { var00077.setProperty("overflow", "auto"); } catch(e) { }
+try { var00279.setProperty("-webkit-text-stroke", "10px red"); } catch(e) { }
+try { /* newvar{var00281:long} */ var var00281 = var00060.clientX; } catch(e) { }
+try { /* newvar{var00283:Document} */ var var00283 = var00226.getSVGDocument(); } catch(e) { }
+try { if (!var00283) { var00283 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00283, 'Document'); SetVariable(var00283, 'GlobalEventHandlers'); SetVariable(var00283, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00282:boolean} */ var var00282 = var00283.execCommand("foreColor", false, "red"); } catch(e) { }
+try { var00024.setProperty("-webkit-app-region", "drag"); } catch(e) { }
+try { var00152.indeterminate = true; } catch(e) { }
+try { var00092.setAttribute("specularConstant", "27"); } catch(e) { }
+try { var00247.name = "" + String.fromCharCode(99, 103, 38, 35, 38, 120, 101, 123, 65, 89, 96, 36, 86, 45, 41, 108, 113, 79, 62, 98) + ""; } catch(e) { }
+try { /* newvar{var00284:DOMString} */ var var00284 = var00149.type; } catch(e) { }
+try { /* newvar{var00285:HTMLCollection} */ var var00285 = document.embeds; } catch(e) { }
+try { if (!var00285) { var00285 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00285, 'HTMLCollection'); } } catch(e) { }
+try { var00279.setProperty("left", "0px"); } catch(e) { }
+try { /* newvar{var00286:short} */ var var00286 = var00143.status; } catch(e) { }
+try { /* newvar{var00287:Element} */ var var00287 = htmlvar00030; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00287) { var00287 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00287, 'Element'); SetVariable(var00287, 'GlobalEventHandlers'); SetVariable(var00287, 'EventTarget'); } } catch(e) { }
+try { var00283.onwebkitfullscreenerror = var00001; } catch(e) { }
+try { var00170.setAttribute("inner", "1"); } catch(e) { }
+try { var00069.title = "1"; } catch(e) { }
+try { /* newvar{var00288:SVGPoint} */ var var00288 = svgvar00007.getStartPositionOfChar(0); } catch(e) { }
+try { if (!var00288) { var00288 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00288, 'SVGPoint'); } } catch(e) { }
+try { var00092.setAttribute("role", "button"); } catch(e) { }
+try { /* newvar{var00289:ApplicationCache} */ var var00289 = window.applicationCache; } catch(e) { }
+try { if (!var00289) { var00289 = GetVariable(fuzzervars, 'ApplicationCache'); } else { SetVariable(var00289, 'ApplicationCache'); SetVariable(var00289, 'EventTarget'); } } catch(e) { }
+try { svgvar00008.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:actuate", "onRequest"); } catch(e) { }
+try { /* newvar{var00290:long} */ var var00290 = var00167.selectedIndex; } catch(e) { }
+try { /* newvar{var00292:SVGFETileElement} */ var var00292 = document.createElementNS("http://www.w3.org/2000/svg", "feTile"); } catch(e) { }
+try { if (!var00292) { var00292 = GetVariable(fuzzervars, 'SVGFETileElement'); } else { SetVariable(var00292, 'SVGFETileElement'); SetVariable(var00292, 'SVGFilterPrimitiveStandardAttributes'); SetVariable(var00292, 'SVGElement'); SetVariable(var00292, 'GlobalEventHandlers'); SetVariable(var00292, 'EventTarget'); SetVariable(var00292, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00291:SVGFilterPrimitiveStandardAttributes} */ var var00291 = var00292; } catch(e) { }
+try { if (!var00291) { var00291 = GetVariable(fuzzervars, 'SVGFilterPrimitiveStandardAttributes'); } else { SetVariable(var00291, 'SVGFilterPrimitiveStandardAttributes'); } } catch(e) { }
+try { var00279.setProperty("-webkit-border-start-color", "green"); } catch(e) { }
+try { var00226.csp = String.fromCharCode(43, 71, 75, 87, 114, 68, 103, 41, 81, 99, 106, 99, 100, 58, 70, 107, 126, 37, 82, 83); } catch(e) { }
+try { /* newvar{var00294:ElementRegistrationOptions} */ var var00294 = {prototype: htmlvar00009.prototype, extends: "font_face_name"}; } catch(e) { }
+try { if (!var00294) { var00294 = GetVariable(fuzzervars, 'ElementRegistrationOptions'); } else { SetVariable(var00294, 'ElementRegistrationOptions'); } } catch(e) { }
+try { /* newvar{var00293:CustomElementConstructor} */ var var00293 = var00128.registerElement(String.fromCodePoint(470226, 1042040, 1055542, 867646, 75098, 447662, 1009616, 824672, 100221, 157491, 561336, 949954, 89347, 173481, 517939, 819517, 830287, 978946, 50710, 952392),var00294); } catch(e) { }
+try { if (!var00293) { var00293 = GetVariable(fuzzervars, 'CustomElementConstructor'); } else { SetVariable(var00293, 'CustomElementConstructor'); } } catch(e) { }
+try { /* newvar{var00295:boolean} */ var var00295 = var00152.required; } catch(e) { }
+try { /* newvar{var00296:EventHandler} */ var var00296 = var00289.onchecking; } catch(e) { }
+try { if (!var00296) { var00296 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00296, 'EventHandler'); } } catch(e) { }
+try { var00100.setProperty("-webkit-border-radius", "0px"); } catch(e) { }
+try { var00095.protocol = var00280; } catch(e) { }
+try { svgvar00008.setAttribute("text-anchor", "inherit"); } catch(e) { }
+try { var00024.setProperty("box-orient", "horizontal"); } catch(e) { }
+try { /* newvar{var00297:EventHandler} */ var var00297 = svgvar00011.ontoggle; } catch(e) { }
+try { if (!var00297) { var00297 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00297, 'EventHandler'); } } catch(e) { }
+try { var00114.onbeforeunload = var00243; } catch(e) { }
+try { var00077.setProperty("stroke-linecap", "round"); } catch(e) { }
+try { svgvar00004.setAttribute("k", "0"); } catch(e) { }
+try { var00090.setAttribute("standby", "" + String.fromCharCode(126, 106, 85, 123, 40, 123, 72, 84, 69, 42, 79, 62, 53, 33, 55, 72, 69, 117, 114, 98) + ""); } catch(e) { }
+try { /* newvar{var00298:EventHandler} */ var var00298 = var00266.onwebkitanimationstart; } catch(e) { }
+try { if (!var00298) { var00298 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00298, 'EventHandler'); } } catch(e) { }
+try { svgvar00012.setAttribute("unicode-bidi", "embed"); } catch(e) { }
+try { var00100.setProperty("-webkit-nbsp-mode", "space"); } catch(e) { }
+try { /* newvar{var00299:SVGAngle} */ var var00299 = var00223.animVal; } catch(e) { }
+try { if (!var00299) { var00299 = GetVariable(fuzzervars, 'SVGAngle'); } else { SetVariable(var00299, 'SVGAngle'); } } catch(e) { }
+try { var00299.newValueSpecifiedUnits(1,0.922521243088); } catch(e) { }
+try { /* newvar{var00300:DOMString} */ var var00300 = htmlvar00007.as; } catch(e) { }
+try { var00095.normalize(); } catch(e) { }
+try { /* newvar{var00301:boolean} */ var var00301 = htmlvar00005.capture; } catch(e) { }
+try { /* newvar{var00303:StyleMedia} */ var var00303 = window.styleMedia; } catch(e) { }
+try { if (!var00303) { var00303 = GetVariable(fuzzervars, 'StyleMedia'); } else { SetVariable(var00303, 'StyleMedia'); } } catch(e) { }
+try { /* newvar{var00302:boolean} */ var var00302 = var00303.matchMedium(); } catch(e) { }
+try { var00024.setProperty("-webkit-border-end", "-1px solid white"); } catch(e) { }
+try { /* newvar{var00304:DOMString} */ var var00304 = document.lastModified; } catch(e) { }
+try { /* newvar{var00305:EventHandler} */ var var00305 = var00021.onbeforepaste; } catch(e) { }
+try { if (!var00305) { var00305 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00305, 'EventHandler'); } } catch(e) { }
+try { var00279.setProperty("color-profile", "sRGB"); } catch(e) { }
+try { /* newvar{var00306:AnimationEffectReadOnly} */ var var00306 = var00049.effect; } catch(e) { }
+try { if (!var00306) { var00306 = GetVariable(fuzzervars, 'AnimationEffectReadOnly'); } else { SetVariable(var00306, 'AnimationEffectReadOnly'); } } catch(e) { }
+try { var00049.effect = var00306; } catch(e) { }
+try { htmlvar00005.accept = "image/*"; } catch(e) { }
+try { /* newvar{var00307:DOMString} */ var var00307 = var00093.getData(String.fromCodePoint(622981, 628268, 467841, 383092, 446724, 850593, 79132, 423255, 23215, 560360, 276112, 913424, 610021, 1103859, 658730, 126037, 72756, 567791, 850221, 105940)); } catch(e) { }
+try { var00100.setProperty("display", "table-column-group"); } catch(e) { }
+try { /* newvar{var00309:SVGFEBlendElement} */ var var00309 = document.createElementNS("http://www.w3.org/2000/svg", "feBlend"); } catch(e) { }
+try { if (!var00309) { var00309 = GetVariable(fuzzervars, 'SVGFEBlendElement'); } else { SetVariable(var00309, 'SVGFEBlendElement'); SetVariable(var00309, 'SVGFilterPrimitiveStandardAttributes'); SetVariable(var00309, 'SVGElement'); SetVariable(var00309, 'GlobalEventHandlers'); SetVariable(var00309, 'EventTarget'); SetVariable(var00309, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00308:SVGAnimatedString} */ var var00308 = var00309.in1; } catch(e) { }
+try { if (!var00308) { var00308 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00308, 'SVGAnimatedString'); } } catch(e) { }
+try { htmlvar00002.setAttribute("layout", "auto"); } catch(e) { }
+try { var00020.controls = true; } catch(e) { }
+try { htmlvar00016.target = "htmlvar00004"; } catch(e) { }
+try { /* newvar{var00310:DOMString} */ var var00310 = var00283.bgColor; } catch(e) { }
+try { /* newvar{var00311:HTMLCollection} */ var var00311 = var00283.scripts; } catch(e) { }
+try { if (!var00311) { var00311 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00311, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00315:CDATASection} */ var var00315 = document.createCDATASection("foo"); } catch(e) { }
+try { if (!var00315) { var00315 = GetVariable(fuzzervars, 'CDATASection'); } else { SetVariable(var00315, 'CDATASection'); SetVariable(var00315, 'Text'); SetVariable(var00315, 'CharacterData'); SetVariable(var00315, 'Element'); SetVariable(var00315, 'GlobalEventHandlers'); SetVariable(var00315, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00314:Text} */ var var00314 = var00315; } catch(e) { }
+try { if (!var00314) { var00314 = GetVariable(fuzzervars, 'Text'); } else { SetVariable(var00314, 'Text'); SetVariable(var00314, 'CharacterData'); SetVariable(var00314, 'Element'); SetVariable(var00314, 'GlobalEventHandlers'); SetVariable(var00314, 'EventTarget'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00313:Text} */ var var00313 = var00314.splitText(53); } catch(e) { }
+try { if (!var00313) { var00313 = GetVariable(fuzzervars, 'Text'); } else { SetVariable(var00313, 'Text'); SetVariable(var00313, 'CharacterData'); SetVariable(var00313, 'Element'); SetVariable(var00313, 'GlobalEventHandlers'); SetVariable(var00313, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00312:DOMString} */ var var00312 = var00313.wholeText; } catch(e) { }
+try { htmlvar00009.required = false; } catch(e) { }
+try { /* newvar{var00316:double} */ var var00316 = var00010.c; } catch(e) { }
+try { /* newvar{var00317:boolean} */ var var00317 = var00152.indeterminate; } catch(e) { }
+try { var00024.setProperty("border", "solid thin"); } catch(e) { }
+try { var00171.moveTo(-1,0); } catch(e) { }
+try { /* newvar{var00318:long} */ var var00318 = svgvar00006.clientLeft; } catch(e) { }
+try { window.status = "htmlvar00005"; } catch(e) { }
+try { htmlvar00015.scrollIntoView(false); } catch(e) { }
+try { var00226.setAttribute("span", "0"); } catch(e) { }
+try { var00045.modify(); } catch(e) { }
+try { var00193.deleteRule(String.fromCodePoint(660537, 391088, 541138, 188080, 398959, 461576, 670742, 395706, 331014, 327295, 974930, 779405, 656634, 623221, 155696, 151481, 774638, 209968, 601290, 598218)); } catch(e) { }
+try { /* newvar{var00319:USVString} */ var var00319 = var00102.search; } catch(e) { }
+try { if (!var00319) { var00319 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00319, 'USVString'); } } catch(e) { }
+try { /* newvar{var00320:EventHandler} */ var var00320 = var00289.onchecking; } catch(e) { }
+try { if (!var00320) { var00320 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00320, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00321:Document} */ var var00321 = var00226.contentDocument; } catch(e) { }
+try { if (!var00321) { var00321 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00321, 'Document'); SetVariable(var00321, 'GlobalEventHandlers'); SetVariable(var00321, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00322:DataTransfer} */ var var00322 = var00060.dataTransfer; } catch(e) { }
+try { if (!var00322) { var00322 = GetVariable(fuzzervars, 'DataTransfer'); } else { SetVariable(var00322, 'DataTransfer'); } } catch(e) { }
+try { var00225.onfullscreenchange = var00200; } catch(e) { }
+try { htmlvar00015.setAttribute("archive", "" + String.fromCharCode(88, 48, 32, 52, 96, 76, 124, 82, 80, 84, 97, 87, 84, 44, 32, 36, 91, 91, 124, 72) + ""); } catch(e) { }
+try { var00143.onupdateready = var00305; } catch(e) { }
+try { var00079.setAttribute("onmousedown", "eventhandler2()"); } catch(e) { }
+try { /* newvar{var00323:Element} */ var var00323 = var00026.commonAncestorContainer; } catch(e) { }
+try { if (!var00323) { var00323 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00323, 'Element'); SetVariable(var00323, 'GlobalEventHandlers'); SetVariable(var00323, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00324:boolean} */ var var00324 = var00266.find(String.fromCharCode(109, 37, 72, 38, 105, 82, 100, 73, 70, 91, 34, 80, 117, 60, 70, 52, 90, 53, 110, 83),true,false,true,false,false); } catch(e) { }
+try { /* newvar{var00325:DOMString} */ var var00325 = document.webkitVisibilityState; } catch(e) { }
+try { /* newvar{var00326:EventHandler} */ var var00326 = htmlvar00028.onscroll; } catch(e) { }
+try { if (!var00326) { var00326 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00326, 'EventHandler'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { var00024.setProperty("background-origin", "padding-box"); } catch(e) { }
+try { /* newvar{var00327:SVGAnimatedInteger} */ var var00327 = svgvar00011.orderX; } catch(e) { }
+try { if (!var00327) { var00327 = GetVariable(fuzzervars, 'SVGAnimatedInteger'); } else { SetVariable(var00327, 'SVGAnimatedInteger'); } } catch(e) { }
+try { document.all[57%document.all.length].appendChild(htmlvar00021); } catch(e) { }
+try { /* newvar{var00328:TextTrackCueList} */ var var00328 = var00254.activeCues; } catch(e) { }
+try { if (!var00328) { var00328 = GetVariable(fuzzervars, 'TextTrackCueList'); } else { SetVariable(var00328, 'TextTrackCueList'); } } catch(e) { }
+try { var00163.initKeyboardEvent(String.fromCharCode(41, 41, 96, 69, 114, 44, 89, 69, 108, 93, 116, 53, 45, 110, 32, 40, 75, 117, 75, 117),false,false,window); } catch(e) { }
+try { /* newvar{var00330:Window} */ var var00330 = window.frames; } catch(e) { }
+try { if (!var00330) { var00330 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00330, 'Window'); SetVariable(var00330, 'GlobalEventHandlers'); SetVariable(var00330, 'WindowBase64'); SetVariable(var00330, 'WindowEventHandlers'); SetVariable(var00330, 'WindowTimers'); SetVariable(var00330, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00329:Touch} */ var var00329 = var00225.createTouch(var00330,var00287,0,0.638715622455,0.527879195694,0.844408470463,0.657704590819,0.189846252136); } catch(e) { }
+try { if (!var00329) { var00329 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00329, 'Touch'); } } catch(e) { }
+try { var00083.f = 0.422677922779; } catch(e) { }
+try { var00176.scroll(); } catch(e) { }
+try { /* newvar{var00331:htmlstring} */ var var00331 = var00292.outerHTML; } catch(e) { }
+try { if (!var00331) { var00331 = GetVariable(fuzzervars, 'htmlstring'); } else { SetVariable(var00331, 'htmlstring'); } } catch(e) { }
+try { htmlvar00014.insertAdjacentHTML("beforeEnd",var00331); } catch(e) { }
+try { htmlvar00027.setAttribute("scope", "col"); } catch(e) { }
+try { /* newvar{var00332:XPathResult} */ var var00332 = var00004.evaluate("//figure",document); } catch(e) { }
+try { if (!var00332) { var00332 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00332, 'XPathResult'); } } catch(e) { }
+try { var00114.onerror = var00238; } catch(e) { }
+try { var00026.setStart(htmlvar00013,82); } catch(e) { }
+try { var00100.setProperty("text-decoration-style", "solid"); } catch(e) { }
+try { var00330.event = var00053; } catch(e) { }
+try { var00021.oncancel = var00217; } catch(e) { }
+try { var00077.setProperty("-webkit-box-lines", "multiple"); } catch(e) { }
+try { /* newvar{var00335:Attr} */ var var00335 = var00259.removeAttributeNode(var00259.attributes[14%var00259.attributes.length].name); } catch(e) { }
+try { if (!var00335) { var00335 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00335, 'Attr'); } } catch(e) { }
+try { /* newvar{var00334:Attr} */ var var00334 = svgvar00006.setAttributeNodeNS(var00335); } catch(e) { }
+try { if (!var00334) { var00334 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00334, 'Attr'); } } catch(e) { }
+try { /* newvar{var00333:Attr} */ var var00333 = var00161.setAttributeNode(var00334); } catch(e) { }
+try { if (!var00333) { var00333 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00333, 'Attr'); } } catch(e) { }
+try { var00039.setAttribute("fill-rule", "evenodd"); } catch(e) { }
+try { var00149.setAttribute("aria-posinset", "0"); } catch(e) { }
+try { /* newvar{var00336:Element} */ var var00336 = var00026.endContainer; } catch(e) { }
+try { if (!var00336) { var00336 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00336, 'Element'); SetVariable(var00336, 'GlobalEventHandlers'); SetVariable(var00336, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00337:NodeIterator} */ var var00337 = var00283.createNodeIterator(htmlvar00018,0,var00108); } catch(e) { }
+try { if (!var00337) { var00337 = GetVariable(fuzzervars, 'NodeIterator'); } else { SetVariable(var00337, 'NodeIterator'); } } catch(e) { }
+try { /* newvar{var00338:SVGMatrix} */ var var00338 = svgvar00007.getCTM(); } catch(e) { }
+try { if (!var00338) { var00338 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00338, 'SVGMatrix'); } } catch(e) { }
+try { var00095.href = var00080; } catch(e) { }
+try { var00277.ondblclick = var00305; } catch(e) { }
+try { var00292.setAttribute("id", "svgvar00001"); } catch(e) { }
+try { /* newvar{var00339:EventTarget} */ var var00339 = var00098.target; } catch(e) { }
+try { if (!var00339) { var00339 = GetVariable(fuzzervars, 'EventTarget'); } else { SetVariable(var00339, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00340:Document} */ var var00340 = htmlvar00009.ownerDocument; } catch(e) { }
+try { if (!var00340) { var00340 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00340, 'Document'); SetVariable(var00340, 'GlobalEventHandlers'); SetVariable(var00340, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { var00340.onselectstart = var00032; } catch(e) { }
+try { var00173.setAttribute("class", "class6"); } catch(e) { }
+try { var00167.setAttribute("onratechange", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00341:HTMLCollection} */ var var00341 = var00283.forms; } catch(e) { }
+try { if (!var00341) { var00341 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00341, 'HTMLCollection'); } } catch(e) { }
+try { htmlvar00007.charset = "Big5"; } catch(e) { }
+try { var00114.onbeforecopy = var00097; } catch(e) { }
+try { var00100.setProperty("-webkit-print-color-adjust", "economy"); } catch(e) { }
+try { htmlvar00003.reset(); } catch(e) { }
+try { /* newvar{var00342:DocumentReadyState} */ var var00342 = var00225.readyState; } catch(e) { }
+try { if (!var00342) { var00342 = GetVariable(fuzzervars, 'DocumentReadyState'); } else { SetVariable(var00342, 'DocumentReadyState'); } } catch(e) { }
+try { var00093.effectAllowed = "1"; } catch(e) { }
+try { htmlvar00013.bgColor = "black"; } catch(e) { }
+try { var00309.setAttribute("click", "none"); } catch(e) { }
+try { var00024.setProperty("offset-rotation", "7grad auto"); } catch(e) { }
+try { /* newvar{var00343:long} */ var var00343 = htmlvar00017.start; } catch(e) { }
+try { svgvar00009.setAttribute("preserveAlpha", "false"); } catch(e) { }
+try { /* newvar{var00344:Element} */ var var00344 = htmlvar00002; } catch(e) { }
+try { if (!var00344) { var00344 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00344, 'Element'); SetVariable(var00344, 'GlobalEventHandlers'); SetVariable(var00344, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00345:EventHandler} */ var var00345 = var00173.onscroll; } catch(e) { }
+try { if (!var00345) { var00345 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00345, 'EventHandler'); } } catch(e) { }
+try { htmlvar00005.accept = "video/*"; } catch(e) { }
+try { var00330.onorientationchange = var00087; } catch(e) { }
+try { /* newvar{var00346:long} */ var var00346 = var00123.length; } catch(e) { }
+try { var00077.setProperty("font-style", "oblique"); } catch(e) { }
+try { htmlvar00016.target = "htmlvar00009"; } catch(e) { }
+try { /* newvar{var00347:boolean} */ var var00347 = var00272.trueSpeed; } catch(e) { }
+try { var00292.ondragstart = var00249; } catch(e) { }
+try { var00100.setProperty("-webkit-border-top-right-radius", "0px"); } catch(e) { }
+try { var00208.replaceWith(var00069); } catch(e) { }
+try { var00171.name = "1"; } catch(e) { }
+try { htmlvar00022.setAttribute("indeterminate", "false"); } catch(e) { }
+try { var00218.setAttribute("clip-path", svg_url_clippath); } catch(e) { }
+try { /* newvar{var00349:AudioTrack} */ var var00349 = var00042[91%var00042.length]; } catch(e) { }
+try { if (!var00349) { var00349 = GetVariable(fuzzervars, 'AudioTrack'); } else { SetVariable(var00349, 'AudioTrack'); } } catch(e) { }
+try { /* newvar{var00348:DOMString} */ var var00348 = var00349.language; } catch(e) { }
+try { htmlvar00005.max = "0"; } catch(e) { }
+try { /* newvar{var00350:long} */ var var00350 = htmlvar00031.cols; } catch(e) { }
+try { /* newvar{var00351:DOMString} */ var var00351 = var00182.conditionText; } catch(e) { }
+try { var00279.setProperty("-webkit-flex-wrap", "wrap"); } catch(e) { }
+try { /* newvar{var00355:SVGAnimatedNumberList} */ var var00355 = svgvar00007.rotate; } catch(e) { }
+try { if (!var00355) { var00355 = GetVariable(fuzzervars, 'SVGAnimatedNumberList'); } else { SetVariable(var00355, 'SVGAnimatedNumberList'); } } catch(e) { }
+try { /* newvar{var00354:SVGNumberList} */ var var00354 = var00355.animVal; } catch(e) { }
+try { if (!var00354) { var00354 = GetVariable(fuzzervars, 'SVGNumberList'); } else { SetVariable(var00354, 'SVGNumberList'); } } catch(e) { }
+try { /* newvar{var00353:SVGNumber} */ var var00353 = var00354.removeItem(-1); } catch(e) { }
+try { if (!var00353) { var00353 = GetVariable(fuzzervars, 'SVGNumber'); } else { SetVariable(var00353, 'SVGNumber'); } } catch(e) { }
+try { /* newvar{var00352:float} */ var var00352 = var00353.value; } catch(e) { }
+try { /* newvar{var00356:long} */ var var00356 = htmlvar00013.scrollAmount; } catch(e) { }
+try { var00024.setProperty("grid-template-columns", "fit-content(0px)"); } catch(e) { }
+try { var00152.value = "" + String.fromCharCode(122, 75, 81, 69, 112, 53, 43, 121, 109, 96, 103, 100, 83, 122, 51, 52, 104, 40, 96, 61) + ""; } catch(e) { }
+try { /* newvar{var00357:boolean} */ var var00357 = var00218.hasAttribute("class"); } catch(e) { }
+try { /* newvar{var00358:SVGTransform} */ var var00358 = var00123.consolidate(); } catch(e) { }
+try { if (!var00358) { var00358 = GetVariable(fuzzervars, 'SVGTransform'); } else { SetVariable(var00358, 'SVGTransform'); } } catch(e) { }
+try { var00060.initMouseEvent("1",false,false,var00171,0,0); } catch(e) { }
+try { /* newvar{var00359:boolean} */ var var00359 = var00017.defaultPrevented; } catch(e) { }
+try { var00279.setProperty("background-repeat-y", "repeat"); } catch(e) { }
+try { var00226.csp = String.fromCodePoint(210922, 579708, 616967, 228118, 121139, 516292, 755262, 861751, 895077, 1034609, 265774, 512484, 886486, 895464, 1106300, 748846, 157362, 39573, 27409, 607112); } catch(e) { }
+try { /* newvar{var00360:boolean} */ var var00360 = var00171.find(String.fromCharCode(117, 89, 40, 37, 60, 68, 76, 94, 81, 86, 68, 39, 119, 126, 69, 108, 110, 58, 110, 44),true,true,false,false,true,true); } catch(e) { }
+try { var00077.setProperty("background-origin", "padding-box"); } catch(e) { }
+try { var00092.setAttribute("k3", "0"); } catch(e) { }
+try { htmlvar00005.setAttribute("classid", "" + String.fromCharCode(43, 108, 94, 112, 110, 37, 72, 95, 40, 111, 105, 54, 91, 73, 89, 101, 37, 47, 43, 80) + ""); } catch(e) { }
+try { var00077.setProperty("page-break-after", "always always"); } catch(e) { }
+try { /* newvar{var00361:boolean} */ var var00361 = var00225.execCommand("insertUnorderedList", false); } catch(e) { }
+try { svgvar00005.setAttribute("stdDeviation", "0.866311726887 0.366234187392"); } catch(e) { }
+try { var00340.xmlVersion = "htmlvar00008"; } catch(e) { }
+try { htmlvar00009.setSelectionRange(3,-1); } catch(e) { }
+try { var00283.onclose = var00298; } catch(e) { }
+try { /* newvar{var00363:Window} */ var var00363 = var00330.window; } catch(e) { }
+try { if (!var00363) { var00363 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00363, 'Window'); SetVariable(var00363, 'GlobalEventHandlers'); SetVariable(var00363, 'WindowBase64'); SetVariable(var00363, 'WindowEventHandlers'); SetVariable(var00363, 'WindowTimers'); SetVariable(var00363, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00362:Touch} */ var var00362 = var00225.createTouch(var00363,htmlvar00031,9); } catch(e) { }
+try { if (!var00362) { var00362 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00362, 'Touch'); } } catch(e) { }
+try { /* newvar{var00364:boolean} */ var var00364 = htmlvar00028.hasPointerCapture(1); } catch(e) { }
+try { /* newvar{var00365:Element} */ var var00365 = document.activeElement; } catch(e) { }
+try { if (!var00365) { var00365 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00365, 'Element'); SetVariable(var00365, 'GlobalEventHandlers'); SetVariable(var00365, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00366:DOMString} */ var var00366 = htmlvar00003.method; } catch(e) { }
+try { /* newvar{var00367:EventListenerOptions} */ var var00367 = {capture: true, passive: false}; } catch(e) { }
+try { if (!var00367) { var00367 = GetVariable(fuzzervars, 'EventListenerOptions'); } else { SetVariable(var00367, 'EventListenerOptions'); } } catch(e) { }
+try { htmlvar00030.removeEventListener("versionchange",var00029,var00367); } catch(e) { }
+try { var00129.src = "data:text/html,foo"; } catch(e) { }
+try { /* newvar{var00368:EventHandler} */ var var00368 = var00287.onpointercancel; } catch(e) { }
+try { if (!var00368) { var00368 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00368, 'EventHandler'); } } catch(e) { }
+try { var00093.setDragImage(htmlvar00004,7,1); } catch(e) { }
+try { var00172.setAttribute("onoffline", "eventhandler2()"); } catch(e) { }
+try { var00142.setAttribute("aria-labelledby", "htmlvar00002"); } catch(e) { }
+try { svgvar00007.setAttribute("stdDeviation", "0.444936809878 0.505919560574"); } catch(e) { }
+try { /* newvar{var00369:long} */ var var00369 = var00060.pageX; } catch(e) { }
+try { var00336.onprogress = var00032; } catch(e) { }
+try { var00314.setAttribute("scope", "rowgroup"); } catch(e) { }
+try { /* newvar{var00370:HTMLCollection} */ var var00370 = var00292.getElementsByTagName("i"); } catch(e) { }
+try { if (!var00370) { var00370 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00370, 'HTMLCollection'); } } catch(e) { }
+try { var00100.setProperty("grid-row-start", "span 1"); } catch(e) { }
+try { htmlvar00012.pathname = var00280; } catch(e) { }
+try { htmlvar00009.setAttribute("readonly", "readonly"); } catch(e) { }
+try { svgvar00007.setAttribute("orient", "auto-start-reverse"); } catch(e) { }
+try { /* newvar{var00371:StyleSheetList} */ var var00371 = var00046.styleSheets; } catch(e) { }
+try { if (!var00371) { var00371 = GetVariable(fuzzervars, 'StyleSheetList'); } else { SetVariable(var00371, 'StyleSheetList'); } } catch(e) { }
+try { /* newvar{var00372:SVGAnimatedLength} */ var var00372 = var00092.width; } catch(e) { }
+try { if (!var00372) { var00372 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00372, 'SVGAnimatedLength'); } } catch(e) { }
+try { var00365.insertAdjacentText("afterEnd","htmlvar00001"); } catch(e) { }
+try { /* newvar{var00373:boolean} */ var var00373 = var00152.formNoValidate; } catch(e) { }
+try { /* newvar{var00374:DOMString} */ var var00374 = var00167.name; } catch(e) { }
+//endjs
+var fuzzervars = {};
+freememory()
+
+
+}
+
+function eventhandler1() {
+
+runcount["eventhandler1"]++; if(runcount["eventhandler1"] > 2) { return; }
+
+var fuzzervars = {};
+
+SetVariable(fuzzervars, window, 'Window');
+SetVariable(fuzzervars, document, 'Document');
+SetVariable(fuzzervars, document.body.firstChild, 'Element');
+
+//beginjs
+/* newvar{htmlvar00001:HTMLVideoElement} */ var htmlvar00001 = document.getElementById("htmlvar00001"); //HTMLVideoElement
+/* newvar{htmlvar00002:HTMLTrackElement} */ var htmlvar00002 = document.getElementById("htmlvar00002"); //HTMLTrackElement
+/* newvar{htmlvar00003:HTMLFormElement} */ var htmlvar00003 = document.getElementById("htmlvar00003"); //HTMLFormElement
+/* newvar{htmlvar00004:HTMLLegendElement} */ var htmlvar00004 = document.getElementById("htmlvar00004"); //HTMLLegendElement
+/* newvar{htmlvar00005:HTMLInputElement} */ var htmlvar00005 = document.getElementById("htmlvar00005"); //HTMLInputElement
+/* newvar{htmlvar00006:HTMLVideoElement} */ var htmlvar00006 = document.getElementById("htmlvar00006"); //HTMLVideoElement
+/* newvar{htmlvar00007:HTMLLinkElement} */ var htmlvar00007 = document.getElementById("htmlvar00007"); //HTMLLinkElement
+/* newvar{htmlvar00008:HTMLMapElement} */ var htmlvar00008 = document.getElementById("htmlvar00008"); //HTMLMapElement
+/* newvar{htmlvar00009:HTMLTextAreaElement} */ var htmlvar00009 = document.getElementById("htmlvar00009"); //HTMLTextAreaElement
+/* newvar{svgvar00001:SVGSVGElement} */ var svgvar00001 = document.getElementById("svgvar00001"); //SVGSVGElement
+/* newvar{svgvar00002:SVGForeignObjectElement} */ var svgvar00002 = document.getElementById("svgvar00002"); //SVGForeignObjectElement
+/* newvar{htmlvar00010:HTMMLUnknownElement} */ var htmlvar00010 = document.getElementById("htmlvar00010"); //HTMMLUnknownElement
+/* newvar{htmlvar00011:HTMLDialogElement} */ var htmlvar00011 = document.getElementById("htmlvar00011"); //HTMLDialogElement
+/* newvar{svgvar00003:SVGGElement} */ var svgvar00003 = document.getElementById("svgvar00003"); //SVGGElement
+/* newvar{svgvar00004:SVGElement} */ var svgvar00004 = document.getElementById("svgvar00004"); //SVGElement
+/* newvar{htmlvar00012:HTMLAnchorElement} */ var htmlvar00012 = document.getElementById("htmlvar00012"); //HTMLAnchorElement
+/* newvar{svgvar00005:SVGFEMergeElement} */ var svgvar00005 = document.getElementById("svgvar00005"); //SVGFEMergeElement
+/* newvar{svgvar00006:SVGAnimateTransformElement} */ var svgvar00006 = document.getElementById("svgvar00006"); //SVGAnimateTransformElement
+/* newvar{svgvar00007:SVGTSpanElement} */ var svgvar00007 = document.getElementById("svgvar00007"); //SVGTSpanElement
+/* newvar{svgvar00008:SVGCircleElement} */ var svgvar00008 = document.getElementById("svgvar00008"); //SVGCircleElement
+/* newvar{svgvar00009:SVGAnimateElement} */ var svgvar00009 = document.getElementById("svgvar00009"); //SVGAnimateElement
+/* newvar{svgvar00010:SVGAnimateTransformElement} */ var svgvar00010 = document.getElementById("svgvar00010"); //SVGAnimateTransformElement
+/* newvar{svgvar00011:SVGFEConvolveMatrixElement} */ var svgvar00011 = document.getElementById("svgvar00011"); //SVGFEConvolveMatrixElement
+/* newvar{svgvar00012:SVGElement} */ var svgvar00012 = document.getElementById("svgvar00012"); //SVGElement
+/* newvar{htmlvar00013:HTMLMarqueeElement} */ var htmlvar00013 = document.getElementById("htmlvar00013"); //HTMLMarqueeElement
+/* newvar{htmlvar00014:HTMLUListElement} */ var htmlvar00014 = document.getElementById("htmlvar00014"); //HTMLUListElement
+/* newvar{htmlvar00015:HTMLLIElement} */ var htmlvar00015 = document.getElementById("htmlvar00015"); //HTMLLIElement
+/* newvar{htmlvar00016:HTMLBaseElement} */ var htmlvar00016 = document.getElementById("htmlvar00016"); //HTMLBaseElement
+/* newvar{htmlvar00017:HTMLOListElement} */ var htmlvar00017 = document.getElementById("htmlvar00017"); //HTMLOListElement
+/* newvar{htmlvar00018:HTMLLIElement} */ var htmlvar00018 = document.getElementById("htmlvar00018"); //HTMLLIElement
+/* newvar{htmlvar00019:HTMLDirectoryElement} */ var htmlvar00019 = document.getElementById("htmlvar00019"); //HTMLDirectoryElement
+/* newvar{htmlvar00020:HTMLShadowElement} */ var htmlvar00020 = document.getElementById("htmlvar00020"); //HTMLShadowElement
+/* newvar{htmlvar00021:HTMLUnknownElement} */ var htmlvar00021 = document.getElementById("htmlvar00021"); //HTMLUnknownElement
+/* newvar{htmlvar00022:HTMLLIElement} */ var htmlvar00022 = document.getElementById("htmlvar00022"); //HTMLLIElement
+/* newvar{htmlvar00023:HTMLDetailsElement} */ var htmlvar00023 = document.getElementById("htmlvar00023"); //HTMLDetailsElement
+/* newvar{htmlvar00024:HTMLVideoElement} */ var htmlvar00024 = document.getElementById("htmlvar00024"); //HTMLVideoElement
+/* newvar{htmlvar00025:HTMLTimeElement} */ var htmlvar00025 = document.getElementById("htmlvar00025"); //HTMLTimeElement
+/* newvar{htmlvar00026:HTMLUnknownElement} */ var htmlvar00026 = document.getElementById("htmlvar00026"); //HTMLUnknownElement
+/* newvar{htmlvar00027:HTMLTimeElement} */ var htmlvar00027 = document.getElementById("htmlvar00027"); //HTMLTimeElement
+/* newvar{htmlvar00028:HTMLUnknownElement} */ var htmlvar00028 = document.createElement("noembed"); //HTMLUnknownElement
+/* newvar{htmlvar00029:HTMLProgressElement} */ var htmlvar00029 = document.createElement("progress"); //HTMLProgressElement
+/* newvar{htmlvar00030:HTMLMapElement} */ var htmlvar00030 = document.createElement("map"); //HTMLMapElement
+/* newvar{htmlvar00031:HTMLTextAreaElement} */ var htmlvar00031 = document.createElement("textarea"); //HTMLTextAreaElement
+/* newvar{htmlvar00032:HTMLUnknownElement} */ var htmlvar00032 = document.createElement("html"); //HTMLUnknownElement
+try { htmlvar00005.setAttribute("leftmargin", "1"); } catch(e) { }
+try { htmlvar00006.loop = true; } catch(e) { }
+try { htmlvar00030.setAttribute("ontoggle", "eventhandler5()"); } catch(e) { }
+try { /* newvar{var00001:SVGAnimatedString} */ var var00001 = svgvar00005.result; } catch(e) { }
+try { if (!var00001) { var00001 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00001, 'SVGAnimatedString'); } } catch(e) { }
+try { htmlvar00016.setAttribute("ondragleave", "eventhandler4()"); } catch(e) { }
+try { /* newvar{var00002:VisualViewport} */ var var00002 = window.visualViewport; } catch(e) { }
+try { if (!var00002) { var00002 = GetVariable(fuzzervars, 'VisualViewport'); } else { SetVariable(var00002, 'VisualViewport'); SetVariable(var00002, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00003:DOMString} */ var var00003 = htmlvar00019.innerText; } catch(e) { }
+try { /* newvar{var00030:CSSRuleList} */ var var00030 = window.getMatchedCSSRules(); } catch(e) { }
+try { if (!var00030) { var00030 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00030, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00029:CSSRule} */ var var00029 = var00030.item(96%var00030.length); } catch(e) { }
+try { if (!var00029) { var00029 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00029, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00028:CSSViewportRule} */ var var00028 = var00029; } catch(e) { }
+try { if (!var00028) { var00028 = GetVariable(fuzzervars, 'CSSViewportRule'); } else { SetVariable(var00028, 'CSSViewportRule'); SetVariable(var00028, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00027:CSSRule} */ var var00027 = var00028; } catch(e) { }
+try { if (!var00027) { var00027 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00027, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00026:CSSMediaRule} */ var var00026 = var00027; } catch(e) { }
+try { if (!var00026) { var00026 = GetVariable(fuzzervars, 'CSSMediaRule'); } else { SetVariable(var00026, 'CSSMediaRule'); SetVariable(var00026, 'CSSGroupingRule'); SetVariable(var00026, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00025:CSSGroupingRule} */ var var00025 = var00026; } catch(e) { }
+try { if (!var00025) { var00025 = GetVariable(fuzzervars, 'CSSGroupingRule'); } else { SetVariable(var00025, 'CSSGroupingRule'); SetVariable(var00025, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00024:CSSRuleList} */ var var00024 = var00025.cssRules; } catch(e) { }
+try { if (!var00024) { var00024 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00024, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00023:CSSRule} */ var var00023 = var00024.item(16%var00024.length); } catch(e) { }
+try { if (!var00023) { var00023 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00023, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00022:CSSNamespaceRule} */ var var00022 = var00023; } catch(e) { }
+try { if (!var00022) { var00022 = GetVariable(fuzzervars, 'CSSNamespaceRule'); } else { SetVariable(var00022, 'CSSNamespaceRule'); SetVariable(var00022, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00021:CSSRule} */ var var00021 = var00022; } catch(e) { }
+try { if (!var00021) { var00021 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00021, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00020:CSSMediaRule} */ var var00020 = var00021; } catch(e) { }
+try { if (!var00020) { var00020 = GetVariable(fuzzervars, 'CSSMediaRule'); } else { SetVariable(var00020, 'CSSMediaRule'); SetVariable(var00020, 'CSSGroupingRule'); SetVariable(var00020, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00019:CSSGroupingRule} */ var var00019 = var00020; } catch(e) { }
+try { if (!var00019) { var00019 = GetVariable(fuzzervars, 'CSSGroupingRule'); } else { SetVariable(var00019, 'CSSGroupingRule'); SetVariable(var00019, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00018:CSSRule} */ var var00018 = var00019; } catch(e) { }
+try { if (!var00018) { var00018 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00018, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00017:CSSFontFaceRule} */ var var00017 = var00018; } catch(e) { }
+try { if (!var00017) { var00017 = GetVariable(fuzzervars, 'CSSFontFaceRule'); } else { SetVariable(var00017, 'CSSFontFaceRule'); SetVariable(var00017, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00016:CSSRule} */ var var00016 = var00017; } catch(e) { }
+try { if (!var00016) { var00016 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00016, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00015:CSSKeyframesRule} */ var var00015 = var00016; } catch(e) { }
+try { if (!var00015) { var00015 = GetVariable(fuzzervars, 'CSSKeyframesRule'); } else { SetVariable(var00015, 'CSSKeyframesRule'); SetVariable(var00015, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00014:CSSRule} */ var var00014 = var00015; } catch(e) { }
+try { if (!var00014) { var00014 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00014, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00013:CSSImportRule} */ var var00013 = var00014; } catch(e) { }
+try { if (!var00013) { var00013 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00013, 'CSSImportRule'); SetVariable(var00013, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00012:CSSStyleSheet} */ var var00012 = var00013.styleSheet; } catch(e) { }
+try { if (!var00012) { var00012 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00012, 'CSSStyleSheet'); SetVariable(var00012, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00011:CSSRuleList} */ var var00011 = var00012.cssRules; } catch(e) { }
+try { if (!var00011) { var00011 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00011, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00010:CSSRule} */ var var00010 = var00011.item(93%var00011.length); } catch(e) { }
+try { if (!var00010) { var00010 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00010, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00009:CSSFontFaceRule} */ var var00009 = var00010; } catch(e) { }
+try { if (!var00009) { var00009 = GetVariable(fuzzervars, 'CSSFontFaceRule'); } else { SetVariable(var00009, 'CSSFontFaceRule'); SetVariable(var00009, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00008:CSSRule} */ var var00008 = var00009; } catch(e) { }
+try { if (!var00008) { var00008 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00008, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00007:CSSImportRule} */ var var00007 = var00008; } catch(e) { }
+try { if (!var00007) { var00007 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00007, 'CSSImportRule'); SetVariable(var00007, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00006:CSSStyleSheet} */ var var00006 = var00007.styleSheet; } catch(e) { }
+try { if (!var00006) { var00006 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00006, 'CSSStyleSheet'); SetVariable(var00006, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00005:StyleSheet} */ var var00005 = var00006; } catch(e) { }
+try { if (!var00005) { var00005 = GetVariable(fuzzervars, 'StyleSheet'); } else { SetVariable(var00005, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00004:DOMString} */ var var00004 = var00005.title; } catch(e) { }
+try { /* newvar{var00031:EventHandler} */ var var00031 = document.oncut; } catch(e) { }
+try { if (!var00031) { var00031 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00031, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00032:Element} */ var var00032 = htmlvar00030; } catch(e) { }
+try { if (!var00032) { var00032 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00032, 'Element'); SetVariable(var00032, 'GlobalEventHandlers'); SetVariable(var00032, 'EventTarget'); } } catch(e) { }
+try { document.webkitExitFullscreen(); } catch(e) { }
+try { svgvar00012.setAttribute("in2", "blur"); } catch(e) { }
+try { document.all[63%document.all.length].appendChild(htmlvar00029); } catch(e) { }
+try { /* newvar{var00033:DOMString} */ var var00033 = document.preferredStylesheetSet; } catch(e) { }
+try { /* newvar{var00034:double} */ var var00034 = window.scrollY; } catch(e) { }
+try { htmlvar00029.ondurationchange = var00031; } catch(e) { }
+try { /* newvar{var00037:SVGFEFuncAElement} */ var var00037 = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA"); } catch(e) { }
+try { if (!var00037) { var00037 = GetVariable(fuzzervars, 'SVGFEFuncAElement'); } else { SetVariable(var00037, 'SVGFEFuncAElement'); SetVariable(var00037, 'SVGComponentTransferFunctionElement'); SetVariable(var00037, 'SVGElement'); SetVariable(var00037, 'GlobalEventHandlers'); SetVariable(var00037, 'EventTarget'); SetVariable(var00037, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00036:SVGComponentTransferFunctionElement} */ var var00036 = var00037; } catch(e) { }
+try { if (!var00036) { var00036 = GetVariable(fuzzervars, 'SVGComponentTransferFunctionElement'); } else { SetVariable(var00036, 'SVGComponentTransferFunctionElement'); SetVariable(var00036, 'SVGElement'); SetVariable(var00036, 'GlobalEventHandlers'); SetVariable(var00036, 'EventTarget'); SetVariable(var00036, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00035:SVGAnimatedNumber} */ var var00035 = var00036.intercept; } catch(e) { }
+try { if (!var00035) { var00035 = GetVariable(fuzzervars, 'SVGAnimatedNumber'); } else { SetVariable(var00035, 'SVGAnimatedNumber'); } } catch(e) { }
+try { /* newvar{var00038:CSSStyleDeclaration} */ var var00038 = htmlvar00001.style; } catch(e) { }
+try { if (!var00038) { var00038 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00038, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00040:WheelEvent} */ var var00040 = document.createEvent("WheelEvent"); } catch(e) { }
+try { if (!var00040) { var00040 = GetVariable(fuzzervars, 'WheelEvent'); } else { SetVariable(var00040, 'WheelEvent'); SetVariable(var00040, 'MouseEvent'); SetVariable(var00040, 'UIEvent'); SetVariable(var00040, 'Event'); } } catch(e) { }
+try { /* newvar{var00039:MouseEvent} */ var var00039 = var00040; } catch(e) { }
+try { if (!var00039) { var00039 = GetVariable(fuzzervars, 'MouseEvent'); } else { SetVariable(var00039, 'MouseEvent'); SetVariable(var00039, 'UIEvent'); SetVariable(var00039, 'Event'); } } catch(e) { }
+try { var00039.initMouseEvent(String.fromCharCode(35, 63, 110, 116, 96, 77, 98, 59, 87, 116, 114, 79, 94, 74, 104, 105, 85, 35, 77, 44),true,true,window,87,0,1,3,6,false,false); } catch(e) { }
+try { /* newvar{var00041:BarProp} */ var var00041 = window.statusbar; } catch(e) { }
+try { if (!var00041) { var00041 = GetVariable(fuzzervars, 'BarProp'); } else { SetVariable(var00041, 'BarProp'); } } catch(e) { }
+try { /* newvar{var00042:StyleSheet} */ var var00042 = var00006; } catch(e) { }
+try { if (!var00042) { var00042 = GetVariable(fuzzervars, 'StyleSheet'); } else { SetVariable(var00042, 'StyleSheet'); } } catch(e) { }
+try { htmlvar00005.autocomplete = "on"; } catch(e) { }
+try { svgvar00012.setAttribute("fill-rule", "evenodd"); } catch(e) { }
+try { document.all[63%document.all.length].appendChild(htmlvar00001); } catch(e) { }
+try { var00038.setProperty("weight", "*"); } catch(e) { }
+try { /* newvar{var00043:boolean} */ var var00043 = document.execCommand("removeFormat", false); } catch(e) { }
+try { /* newvar{var00044:SVGMatrix} */ var var00044 = svgvar00001.createSVGMatrix(); } catch(e) { }
+try { if (!var00044) { var00044 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00044, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00045:HTMLDataElement} */ var var00045 = document.createElement("data"); } catch(e) { }
+try { if (!var00045) { var00045 = GetVariable(fuzzervars, 'HTMLDataElement'); } else { SetVariable(var00045, 'HTMLDataElement'); SetVariable(var00045, 'Element'); SetVariable(var00045, 'GlobalEventHandlers'); SetVariable(var00045, 'EventTarget'); } } catch(e) { }
+try { var00045.value = "" + String.fromCharCode(83, 79, 115, 108, 118, 94, 109, 100, 56, 60, 120, 118, 110, 104, 108, 54, 111, 70, 60, 73) + ""; } catch(e) { }
+try { svgvar00006.setAttribute("ideographic", "0"); } catch(e) { }
+try { /* newvar{var00046:CSSRuleList} */ var var00046 = window.getMatchedCSSRules(); } catch(e) { }
+try { if (!var00046) { var00046 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00046, 'CSSRuleList'); } } catch(e) { }
+try { svgvar00010.setPointerCapture(0); } catch(e) { }
+try { /* newvar{var00047:boolean} */ var var00047 = svgvar00008.webkitMatchesSelector(String.fromCharCode(57, 33, 33, 73, 110, 62, 70, 102, 34, 115, 47, 96, 55, 79, 110, 95, 53, 101, 118, 64)); } catch(e) { }
+try { /* newvar{var00048:Element} */ var var00048 = document.webkitFullscreenElement; } catch(e) { }
+try { if (!var00048) { var00048 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00048, 'Element'); SetVariable(var00048, 'GlobalEventHandlers'); SetVariable(var00048, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00049:boolean} */ var var00049 = document.queryCommandIndeterm("1"); } catch(e) { }
+try { htmlvar00012.onloadeddata = var00031; } catch(e) { }
+try { svgvar00002.setAttribute("externalResourcesRequired", "true"); } catch(e) { }
+try { /* newvar{var00051:ApplicationCache} */ var var00051 = window.applicationCache; } catch(e) { }
+try { if (!var00051) { var00051 = GetVariable(fuzzervars, 'ApplicationCache'); } else { SetVariable(var00051, 'ApplicationCache'); SetVariable(var00051, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00050:EventHandler} */ var var00050 = var00051.onprogress; } catch(e) { }
+try { if (!var00050) { var00050 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00050, 'EventHandler'); } } catch(e) { }
+try { htmlvar00021.onscroll = var00050; } catch(e) { }
+try { var00051.onobsolete = var00050; } catch(e) { }
+try { svgvar00007.setAttribute("stroke", "none"); } catch(e) { }
+try { var00038.setProperty("stop-color", "rgb(193,174,8)"); } catch(e) { }
+try { var00038.setProperty("-webkit-box-ordinal-group", "1"); } catch(e) { }
+try { /* newvar{var00052:Element} */ var var00052 = htmlvar00014.getRootNode(); } catch(e) { }
+try { if (!var00052) { var00052 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00052, 'Element'); SetVariable(var00052, 'GlobalEventHandlers'); SetVariable(var00052, 'EventTarget'); } } catch(e) { }
+try { var00038.setProperty("outline-style", "ridge"); } catch(e) { }
+try { htmlvar00016.setAttribute("aria-activedescendant", "htmlvar00001"); } catch(e) { }
+try { /* newvar{var00053:EventHandler} */ var var00053 = window.onanimationstart; } catch(e) { }
+try { if (!var00053) { var00053 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00053, 'EventHandler'); } } catch(e) { }
+try { htmlvar00020.setAttribute("vlink", "red"); } catch(e) { }
+try { var00038.setProperty("animation-direction", "reverse"); } catch(e) { }
+try { var00038.setProperty("mask-source-type", "alpha"); } catch(e) { }
+try { /* newvar{var00054:long} */ var var00054 = htmlvar00013.scrollDelay; } catch(e) { }
+try { htmlvar00013.scrollDelay = 1; } catch(e) { }
+try { var00037.setAttribute("pointsAtY", "0"); } catch(e) { }
+try { var00038.setProperty("text-overflow", "ellipsis"); } catch(e) { }
+try { /* newvar{var00055:Event} */ var var00055 = document.createEvent(String.fromCodePoint(1100866, 41752, 354380, 805677, 1075254, 688597, 96247, 267802, 366413, 416391, 162894, 383189, 446513, 442706, 566252, 367112, 66004, 1062804, 287168, 380500)); } catch(e) { }
+try { if (!var00055) { var00055 = GetVariable(fuzzervars, 'Event'); } else { SetVariable(var00055, 'Event'); } } catch(e) { }
+try { /* newvar{var00056:Window} */ var var00056 = window.top; } catch(e) { }
+try { if (!var00056) { var00056 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00056, 'Window'); SetVariable(var00056, 'GlobalEventHandlers'); SetVariable(var00056, 'WindowBase64'); SetVariable(var00056, 'WindowEventHandlers'); SetVariable(var00056, 'WindowTimers'); SetVariable(var00056, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00057:boolean} */ var var00057 = document.execCommand("underline", false); } catch(e) { }
+try { /* newvar{var00058:eventhandler} */ var var00058 = eventhandler5; } catch(e) { }
+try { if (!var00058) { var00058 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00058, 'eventhandler'); } } catch(e) { }
+try { svgvar00004.setAttribute("onmouseover", "var00058"); } catch(e) { }
+try { var00038.setProperty("stroke-opacity", "-1"); } catch(e) { }
+try { var00038.setProperty("min-height", "-1px"); } catch(e) { }
+try { htmlvar00028.setAttribute("seamless", "seamless"); } catch(e) { }
+try { var00045.setAttribute("desc", "" + String.fromCharCode(34, 118, 81, 124, 118, 33, 79, 99, 113, 44, 39, 79, 105, 105, 36, 103, 97, 32, 76, 123) + ""); } catch(e) { }
+try { svgvar00012.setAttribute("offset", "85%"); } catch(e) { }
+try { var00038.setProperty("mso-font-kerning", "74pt"); } catch(e) { }
+try { /* newvar{var00060:CustomEvent} */ var var00060 = document.createEvent("CustomEvent"); } catch(e) { }
+try { if (!var00060) { var00060 = GetVariable(fuzzervars, 'CustomEvent'); } else { SetVariable(var00060, 'CustomEvent'); SetVariable(var00060, 'Event'); } } catch(e) { }
+try { /* newvar{var00059:any} */ var var00059 = var00060.detail; } catch(e) { }
+try { if (!var00059) { var00059 = GetVariable(fuzzervars, 'any'); } else { SetVariable(var00059, 'any'); } } catch(e) { }
+try { /* newvar{var00061:sequence_Transferable_} */ var var00061 = undefined } catch(e) { }
+try { if (!var00061) { var00061 = GetVariable(fuzzervars, 'sequence_Transferable_'); } else { SetVariable(var00061, 'sequence_Transferable_'); } } catch(e) { }
+try { var00056.postMessage(var00059,String.fromCodePoint(646039, 470521, 21180, 1054642, 839514, 162012, 959916, 1027102, 995783, 129450, 873435, 139177, 432181, 706545, 731305, 671992, 616369, 1057290, 342377, 716479),var00061); } catch(e) { }
+try { var00038.setProperty("perspective-origin", "99% 63%"); } catch(e) { }
+try { /* newvar{var00062:HTMLObjectElement} */ var var00062 = document.createElement("object"); } catch(e) { }
+try { if (!var00062) { var00062 = GetVariable(fuzzervars, 'HTMLObjectElement'); } else { SetVariable(var00062, 'HTMLObjectElement'); SetVariable(var00062, 'Element'); SetVariable(var00062, 'GlobalEventHandlers'); SetVariable(var00062, 'EventTarget'); } } catch(e) { }
+try { var00062.useMap = "#htmlvar00009"; } catch(e) { }
+try { /* newvar{var00063:EventHandler} */ var var00063 = htmlvar00022.onpaste; } catch(e) { }
+try { if (!var00063) { var00063 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00063, 'EventHandler'); } } catch(e) { }
+try { var00038.setProperty("user-zoom", "zoom"); } catch(e) { }
+try { var00038.setProperty("-webkit-perspective-origin", "59% 80%"); } catch(e) { }
+try { /* newvar{var00067:XPathEvaluator} */ var var00067 = new XPathEvaluator(); } catch(e) { }
+try { if (!var00067) { var00067 = GetVariable(fuzzervars, 'XPathEvaluator'); } else { SetVariable(var00067, 'XPathEvaluator'); } } catch(e) { }
+try { /* newvar{var00066:XPathNSResolver} */ var var00066 = var00067.createNSResolver(var00045); } catch(e) { }
+try { if (!var00066) { var00066 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00066, 'XPathNSResolver'); } } catch(e) { }
+try { /* newvar{var00065:XPathExpression} */ var var00065 = document.createExpression("//command",var00066); } catch(e) { }
+try { if (!var00065) { var00065 = GetVariable(fuzzervars, 'XPathExpression'); } else { SetVariable(var00065, 'XPathExpression'); } } catch(e) { }
+try { /* newvar{var00064:XPathResult} */ var var00064 = var00065.evaluate(htmlvar00017,7); } catch(e) { }
+try { if (!var00064) { var00064 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00064, 'XPathResult'); } } catch(e) { }
+try { var00038.setProperty("opacity", "0.65750094532"); } catch(e) { }
+try { /* newvar{var00070:sequence_Dictionary_} */ var var00070 = { "gradientUnits": [0, 0] }; } catch(e) { }
+try { if (!var00070) { var00070 = GetVariable(fuzzervars, 'sequence_Dictionary_'); } else { SetVariable(var00070, 'sequence_Dictionary_'); } } catch(e) { }
+try { /* newvar{var00071:KeyframeEffectOptions} */ var var00071 = { delay: -1, direction: "htmlvar00004", duration: 1, easing: "1", endDelay: 1, fill: String.fromCharCode(101, 85, 117, 57, 80, 92, 120, 95, 85, 114, 53, 39, 33, 45, 60, 79, 39, 55, 110, 94), iterationStart: 0.242479538205, iterations: Infinity }; } catch(e) { }
+try { if (!var00071) { var00071 = GetVariable(fuzzervars, 'KeyframeEffectOptions'); } else { SetVariable(var00071, 'KeyframeEffectOptions'); } } catch(e) { }
+try { /* newvar{var00069:Animation} */ var var00069 = htmlvar00015.animate(var00070,var00071); } catch(e) { }
+try { if (!var00069) { var00069 = GetVariable(fuzzervars, 'Animation'); } else { SetVariable(var00069, 'Animation'); SetVariable(var00069, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00068:Promise_Animation_} */ var var00068 = var00069.finished; } catch(e) { }
+try { if (!var00068) { var00068 = GetVariable(fuzzervars, 'Promise_Animation_'); } else { SetVariable(var00068, 'Promise_Animation_'); } } catch(e) { }
+try { /* newvar{var00072:EventHandler} */ var var00072 = var00062.onlostpointercapture; } catch(e) { }
+try { if (!var00072) { var00072 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00072, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00073:boolean} */ var var00073 = svgvar00001.animationsPaused(); } catch(e) { }
+try { /* newvar{var00074:Touch} */ var var00074 = document.createTouch(var00056,htmlvar00007); } catch(e) { }
+try { if (!var00074) { var00074 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00074, 'Touch'); } } catch(e) { }
+try { /* newvar{var00075:DOMString} */ var var00075 = svgvar00011.id; } catch(e) { }
+try { var00038.setProperty("-webkit-align-items", "center"); } catch(e) { }
+try { /* newvar{var00076:DOMString} */ var var00076 = window.atob(String.fromCharCode(88, 100, 34, 57, 97, 60, 52, 44, 74, 111, 64, 41, 85, 35, 51, 81, 109, 97, 46, 93)); } catch(e) { }
+try { svgvar00003.setAttribute("rx", "-1"); } catch(e) { }
+try { /* newvar{var00077:MouseEvent} */ var var00077 = var00040; } catch(e) { }
+try { if (!var00077) { var00077 = GetVariable(fuzzervars, 'MouseEvent'); } else { SetVariable(var00077, 'MouseEvent'); SetVariable(var00077, 'UIEvent'); SetVariable(var00077, 'Event'); } } catch(e) { }
+try { /* newvar{var00078:DOMString} */ var var00078 = htmlvar00012.rev; } catch(e) { }
+try { /* newvar{var00079:DOMString} */ var var00079 = var00038.getPropertyPriority("-webkit-hyphenate-character"); } catch(e) { }
+try { svgvar00003.onkeydown = var00063; } catch(e) { }
+try { htmlvar00031.setSelectionRange(-1,95,"htmlvar00003"); } catch(e) { }
+try { /* newvar{var00080:DOMString} */ var var00080 = var00048.localName; } catch(e) { }
+try { htmlvar00013.setAttribute("char", "" + String.fromCharCode(79) + ""); } catch(e) { }
+try { htmlvar00005.src = "data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAA5NtZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0OCByMjY0MyA1YzY1NzA0IC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTEgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAvWWIhAAh/9PWYQ7q+jvvWOfBgvpv0eIYkqWiQW6SsLQx8ByoouBLEC9HBQTAXOJh/wFnteOP+NH5Er2DeHrP4kxvjj4nXKG9Zm/FycSAdlzoMDOFc4CmXmCL51Dj+zekurxKazOLwXVd7f/rOQpa9+iPXYTZsRw+WFFNokI8saLT7Mt03UvGxwdAYkwe7UmwPZacue5goP6rQhBgGMjgK21nSHZWUcz5Y6Ec/wdCPp0Sxx/h6UsSneF9hINuvwAAAAhBmiJsQx92QAAAAAgBnkF5DH/EgQAAAzRtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAZAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACXnRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAZAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAIAAAACAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAGQAAAQAAAEAAAAAAdZtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAADwAAAAGAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAGBbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABQXN0YmwAAACVc3RzZAAAAAAAAAABAAAAhWF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAIAAgAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAvYXZjQwFkAAr/4QAWZ2QACqzZSWhAAAADAEAAAA8DxIllgAEABmjr48siwAAAABhzdHRzAAAAAAAAAAEAAAADAAACAAAAABRzdHNzAAAAAAAAAAEAAAABAAAAKGN0dHMAAAAAAAAAAwAAAAEAAAQAAAAAAQAABgAAAAABAAACAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAwAAAAEAAAAgc3RzegAAAAAAAAAAAAAAAwAAA3MAAAAMAAAADAAAABRzdGNvAAAAAAAAAAEAAAAwAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="; } catch(e) { }
+try { /* newvar{var00081:MutationObserver} */ var var00081 = new MutationObserver(var00058); } catch(e) { }
+try { if (!var00081) { var00081 = GetVariable(fuzzervars, 'MutationObserver'); } else { SetVariable(var00081, 'MutationObserver'); } } catch(e) { }
+try { /* newvar{var00082:MutationObserverInit} */ var var00082 = {childList: true, attributes: false, characterData: false, subtree: false, attributeOldValue: true, characterDataOldValue: true}; } catch(e) { }
+try { if (!var00082) { var00082 = GetVariable(fuzzervars, 'MutationObserverInit'); } else { SetVariable(var00082, 'MutationObserverInit'); } } catch(e) { }
+try { var00081.observe(htmlvar00004,var00082); } catch(e) { }
+try { var00048.setAttribute("quality", "high"); } catch(e) { }
+try { svgvar00001.scrollLeft = 0.842342631971; } catch(e) { }
+try { window.onstorage = var00050; } catch(e) { }
+try { /* newvar{var00083:long} */ var var00083 = htmlvar00024.videoWidth; } catch(e) { }
+try { svgvar00005.setAttribute("xChannelSelector", "R"); } catch(e) { }
+try { var00038.setProperty("-webkit-padding-end", "1px"); } catch(e) { }
+try { var00038.setProperty("mso-border-alt", "solid red .5pt"); } catch(e) { }
+try { /* newvar{var00084:Element} */ var var00084 = htmlvar00013.previousSibling; } catch(e) { }
+try { if (!var00084) { var00084 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00084, 'Element'); SetVariable(var00084, 'GlobalEventHandlers'); SetVariable(var00084, 'EventTarget'); } } catch(e) { }
+try { var00038.setProperty("color-profile", "sRGB"); } catch(e) { }
+try { var00045.setAttribute("usemap", "#htmlvar00004"); } catch(e) { }
+try { /* newvar{var00086:Window} */ var var00086 = var00056.parent; } catch(e) { }
+try { if (!var00086) { var00086 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00086, 'Window'); SetVariable(var00086, 'GlobalEventHandlers'); SetVariable(var00086, 'WindowBase64'); SetVariable(var00086, 'WindowEventHandlers'); SetVariable(var00086, 'WindowTimers'); SetVariable(var00086, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00085:MutationObserverConstructor} */ var var00085 = var00086.WebKitMutationObserver; } catch(e) { }
+try { if (!var00085) { var00085 = GetVariable(fuzzervars, 'MutationObserverConstructor'); } else { SetVariable(var00085, 'MutationObserverConstructor'); } } catch(e) { }
+try { var00056.WebKitMutationObserver = var00085; } catch(e) { }
+try { var00038.setProperty("align-self", "center"); } catch(e) { }
+try { /* newvar{var00088:ErrorEvent} */ var var00088 = document.createEvent("ErrorEvent"); } catch(e) { }
+try { if (!var00088) { var00088 = GetVariable(fuzzervars, 'ErrorEvent'); } else { SetVariable(var00088, 'ErrorEvent'); SetVariable(var00088, 'Event'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00087:long} */ var var00087 = var00088.colno; } catch(e) { }
+try { svgvar00001.onchange = var00031; } catch(e) { }
+try { /* newvar{var00089:double} */ var var00089 = htmlvar00007.scrollTop; } catch(e) { }
+try { svgvar00001.setAttribute("writing-mode", "tb"); } catch(e) { }
+try { var00038.setProperty("backface-visibility", "visible"); } catch(e) { }
+try { var00038.setProperty("-webkit-transition", "height -1s"); } catch(e) { }
+try { htmlvar00023.setAttribute("default", "" + String.fromCharCode(86, 61, 63, 121, 66, 78, 123, 33, 67, 54, 69, 61, 106, 63, 52, 35, 83, 102, 78, 81) + ""); } catch(e) { }
+try { /* newvar{var00091:HTMLFrameElement} */ var var00091 = document.createElement("frame"); } catch(e) { }
+try { if (!var00091) { var00091 = GetVariable(fuzzervars, 'HTMLFrameElement'); } else { SetVariable(var00091, 'HTMLFrameElement'); SetVariable(var00091, 'Element'); SetVariable(var00091, 'GlobalEventHandlers'); SetVariable(var00091, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00090:Element} */ var var00090 = var00091; } catch(e) { }
+try { if (!var00090) { var00090 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00090, 'Element'); SetVariable(var00090, 'GlobalEventHandlers'); SetVariable(var00090, 'EventTarget'); } } catch(e) { }
+try { htmlvar00027.setAttribute("aria-controls", "htmlvar00006"); } catch(e) { }
+try { /* newvar{var00092:HTMLCollection} */ var var00092 = htmlvar00030.areas; } catch(e) { }
+try { if (!var00092) { var00092 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00092, 'HTMLCollection'); } } catch(e) { }
+try { htmlvar00028.setAttribute("aria-controls", "htmlvar00002"); } catch(e) { }
+try { var00037.onprogress = var00031; } catch(e) { }
+try { var00038.setProperty("list-style-position", "outside"); } catch(e) { }
+try { var00038.setProperty("-webkit-appearance", "slider-vertical"); } catch(e) { }
+try { var00038.setProperty("mso-style-id", "-1"); } catch(e) { }
+try { var00038.setProperty("-webkit-marquee-speed", "1"); } catch(e) { }
+try { /* newvar{var00093:boolean} */ var var00093 = document.execCommand("subscript", false); } catch(e) { }
+try { document.all[60%document.all.length].appendChild(htmlvar00007); } catch(e) { }
+try { var00038.setProperty("-webkit-margin-collapse", "collapse separate"); } catch(e) { }
+try { /* newvar{var00094:boolean} */ var var00094 = document.webkitFullscreenEnabled; } catch(e) { }
+try { var00086.status = String.fromCharCode(63, 68, 36, 74, 106, 38, 87, 56, 116, 107, 37, 47, 109, 45, 101, 41, 40, 93, 34, 84); } catch(e) { }
+try { svgvar00012.setAttribute("targetY", "0"); } catch(e) { }
+try { /* newvar{var00095:USVString} */ var var00095 = htmlvar00012.host; } catch(e) { }
+try { if (!var00095) { var00095 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00095, 'USVString'); } } catch(e) { }
+try { /* newvar{var00096:DOMString} */ var var00096 = htmlvar00005.name; } catch(e) { }
+try { var00036.setAttribute("order", "0"); } catch(e) { }
+try { htmlvar00019.setAttribute("for", "htmlvar00004"); } catch(e) { }
+try { htmlvar00015.setAttribute("onwebkitfullscreenchange", "eventhandler5()"); } catch(e) { }
+try { htmlvar00007.type = "submit"; } catch(e) { }
+try { /* newvar{var00097:EventTarget} */ var var00097 = var00060.target; } catch(e) { }
+try { if (!var00097) { var00097 = GetVariable(fuzzervars, 'EventTarget'); } else { SetVariable(var00097, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00098:boolean} */ var var00098 = document.execCommand("justifyCenter", false); } catch(e) { }
+try { /* newvar{var00099:CSSRule} */ var var00099 = var00012.ownerRule; } catch(e) { }
+try { if (!var00099) { var00099 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00099, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00100:USVString} */ var var00100 = htmlvar00012.hostname; } catch(e) { }
+try { if (!var00100) { var00100 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00100, 'USVString'); } } catch(e) { }
+try { document.all[99%document.all.length].appendChild(htmlvar00022); } catch(e) { }
+try { /* newvar{var00102:HTMLTableCellElement} */ var var00102 = document.createElement("th"); } catch(e) { }
+try { if (!var00102) { var00102 = GetVariable(fuzzervars, 'HTMLTableCellElement'); } else { SetVariable(var00102, 'HTMLTableCellElement'); SetVariable(var00102, 'Element'); SetVariable(var00102, 'GlobalEventHandlers'); SetVariable(var00102, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00101:DOMString} */ var var00101 = var00102.height; } catch(e) { }
+try { /* newvar{var00103:SVGAnimatedLength} */ var var00103 = svgvar00002.height; } catch(e) { }
+try { if (!var00103) { var00103 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00103, 'SVGAnimatedLength'); } } catch(e) { }
+try { htmlvar00012.setAttribute("data", "" + String.fromCharCode(86, 112, 88, 38, 53, 95, 84, 47, 116, 69, 55, 109, 126, 55, 36, 94, 50, 113, 84, 59) + ""); } catch(e) { }
+try { /* newvar{var00104:boolean} */ var var00104 = document.webkitIsFullScreen; } catch(e) { }
+try { /* newvar{var00105:boolean} */ var var00105 = htmlvar00005.webkitdirectory; } catch(e) { }
+try { /* newvar{var00106:Window} */ var var00106 = var00091.contentWindow; } catch(e) { }
+try { if (!var00106) { var00106 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00106, 'Window'); SetVariable(var00106, 'GlobalEventHandlers'); SetVariable(var00106, 'WindowBase64'); SetVariable(var00106, 'WindowEventHandlers'); SetVariable(var00106, 'WindowTimers'); SetVariable(var00106, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00107:long} */ var var00107 = htmlvar00024.height; } catch(e) { }
+try { /* newvar{var00108:SVGAnimatedString} */ var var00108 = svgvar00001.className; } catch(e) { }
+try { if (!var00108) { var00108 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00108, 'SVGAnimatedString'); } } catch(e) { }
+try { /* newvar{var00109:boolean} */ var var00109 = htmlvar00023.open; } catch(e) { }
+try { /* newvar{var00112:Window} */ var var00112 = var00106.self; } catch(e) { }
+try { if (!var00112) { var00112 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00112, 'Window'); SetVariable(var00112, 'GlobalEventHandlers'); SetVariable(var00112, 'WindowBase64'); SetVariable(var00112, 'WindowEventHandlers'); SetVariable(var00112, 'WindowTimers'); SetVariable(var00112, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00111:Window} */ var var00111 = var00112.self; } catch(e) { }
+try { if (!var00111) { var00111 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00111, 'Window'); SetVariable(var00111, 'GlobalEventHandlers'); SetVariable(var00111, 'WindowBase64'); SetVariable(var00111, 'WindowEventHandlers'); SetVariable(var00111, 'WindowTimers'); SetVariable(var00111, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00110:Touch} */ var var00110 = document.createTouch(var00111,htmlvar00008,0,0.447059936845,0.948049407786); } catch(e) { }
+try { if (!var00110) { var00110 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00110, 'Touch'); } } catch(e) { }
+try { var00038.setProperty("transition-properties", "transform"); } catch(e) { }
+try { /* newvar{var00113:DOMString} */ var var00113 = var00062.code; } catch(e) { }
+try { /* newvar{var00114:EventHandler} */ var var00114 = var00069.onfinish; } catch(e) { }
+try { if (!var00114) { var00114 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00114, 'EventHandler'); } } catch(e) { }
+try { var00086.opener = var00111; } catch(e) { }
+try { svgvar00010.setAttribute("restart", "whenNotActive"); } catch(e) { }
+try { /* newvar{var00115:boolean} */ var var00115 = svgvar00011.isEqualNode(svgvar00006); } catch(e) { }
+try { /* newvar{var00116:MutationObserverConstructor} */ var var00116 = var00086.WebKitMutationObserver; } catch(e) { }
+try { if (!var00116) { var00116 = GetVariable(fuzzervars, 'MutationObserverConstructor'); } else { SetVariable(var00116, 'MutationObserverConstructor'); } } catch(e) { }
+try { /* newvar{var00117:CSSStyleDeclaration} */ var var00117 = htmlvar00018.style; } catch(e) { }
+try { if (!var00117) { var00117 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00117, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00117.setProperty("-webkit-animation", "anim 9s linear"); } catch(e) { }
+try { htmlvar00005.autocomplete = "off"; } catch(e) { }
+try { /* newvar{var00118:DOMString} */ var var00118 = var00007.href; } catch(e) { }
+try { var00038.setProperty("-webkit-mask-image", "none"); } catch(e) { }
+try { /* newvar{var00119:long} */ var var00119 = htmlvar00005.selectionStart; } catch(e) { }
+try { svgvar00005.setAttribute("y", "0in"); } catch(e) { }
+try { htmlvar00025.replaceWith(htmlvar00020); } catch(e) { }
+try { svgvar00010.setAttribute("stdDeviation", "3 1"); } catch(e) { }
+try { /* newvar{var00121:XSLTProcessor} */ var var00121 = new XSLTProcessor(); } catch(e) { }
+try { if (!var00121) { var00121 = GetVariable(fuzzervars, 'XSLTProcessor'); } else { SetVariable(var00121, 'XSLTProcessor'); } } catch(e) { }
+try { /* newvar{var00120:DocumentFragment} */ var var00120 = var00121.transformToFragment(htmlvar00012,document); } catch(e) { }
+try { if (!var00120) { var00120 = GetVariable(fuzzervars, 'DocumentFragment'); } else { SetVariable(var00120, 'DocumentFragment'); SetVariable(var00120, 'Element'); SetVariable(var00120, 'GlobalEventHandlers'); SetVariable(var00120, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00122:DOMString} */ var var00122 = var00062.code; } catch(e) { }
+try { /* newvar{var00123:EventHandler} */ var var00123 = htmlvar00008.onmousemove; } catch(e) { }
+try { if (!var00123) { var00123 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00123, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00124:SVGPoint} */ var var00124 = svgvar00007.getEndPositionOfChar(-1); } catch(e) { }
+try { if (!var00124) { var00124 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00124, 'SVGPoint'); } } catch(e) { }
+try { htmlvar00032.onmouseenter = var00123; } catch(e) { }
+try { /* newvar{var00125:CSSStyleDeclaration} */ var var00125 = htmlvar00032.style; } catch(e) { }
+try { if (!var00125) { var00125 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00125, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00127:PointerEvent} */ var var00127 = document.createEvent("PointerEvent"); } catch(e) { }
+try { if (!var00127) { var00127 = GetVariable(fuzzervars, 'PointerEvent'); } else { SetVariable(var00127, 'PointerEvent'); SetVariable(var00127, 'MouseEvent'); SetVariable(var00127, 'UIEvent'); SetVariable(var00127, 'Event'); } } catch(e) { }
+try { /* newvar{var00126:double} */ var var00126 = var00127.height; } catch(e) { }
+try { var00052.append(htmlvar00005); } catch(e) { }
+try { /* newvar{var00130:StaticRange} */ var var00130 = sequence_StaticRange[0]; } catch(e) { }
+try { if (!var00130) { var00130 = GetVariable(fuzzervars, 'StaticRange'); } else { SetVariable(var00130, 'StaticRange'); } } catch(e) { }
+try { /* newvar{var00129:Range} */ var var00129 = var00130.toRange(); } catch(e) { }
+try { if (!var00129) { var00129 = GetVariable(fuzzervars, 'Range'); } else { SetVariable(var00129, 'Range'); } } catch(e) { }
+try { /* newvar{var00128:short} */ var var00128 = var00129.comparePoint(htmlvar00017,5); } catch(e) { }
+try { var00032.setAttribute("aria-readonly", "true"); } catch(e) { }
+try { /* newvar{var00131:Element} */ var var00131 = htmlvar00027.previousSibling; } catch(e) { }
+try { if (!var00131) { var00131 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00131, 'Element'); SetVariable(var00131, 'GlobalEventHandlers'); SetVariable(var00131, 'EventTarget'); } } catch(e) { }
+try { htmlvar00008.webkitRequestFullscreen(); } catch(e) { }
+try { /* newvar{var00132:EventHandler} */ var var00132 = svgvar00004.onemptied; } catch(e) { }
+try { if (!var00132) { var00132 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00132, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00133:GlobalEventHandlers} */ var var00133 = htmlvar00011; } catch(e) { }
+try { if (!var00133) { var00133 = GetVariable(fuzzervars, 'GlobalEventHandlers'); } else { SetVariable(var00133, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00134:long} */ var var00134 = svgvar00001.clientWidth; } catch(e) { }
+try { /* newvar{var00135:long} */ var var00135 = htmlvar00009.rows; } catch(e) { }
+try { var00125.setProperty("padding-left", "7vh"); } catch(e) { }
+try { htmlvar00007.rev = "section"; } catch(e) { }
+try { htmlvar00005.stepUp(); } catch(e) { }
+try { /* newvar{var00136:XPathResult} */ var var00136 = var00067.evaluate("//samp",htmlvar00032); } catch(e) { }
+try { if (!var00136) { var00136 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00136, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00137:NodeList} */ var var00137 = htmlvar00020.getDistributedNodes(); } catch(e) { }
+try { if (!var00137) { var00137 = GetVariable(fuzzervars, 'NodeList'); } else { SetVariable(var00137, 'NodeList'); } } catch(e) { }
+try { var00125.setProperty("visibility", "inherit"); } catch(e) { }
+try { svgvar00005.setAttribute("keySplines", "46"); } catch(e) { }
+try { /* newvar{var00138:long} */ var var00138 = htmlvar00024.webkitDroppedFrameCount; } catch(e) { }
+try { var00038.setProperty("quotes", "'a' 'v' 'b' 'u'"); } catch(e) { }
+try { /* newvar{var00139:boolean} */ var var00139 = window.find(String.fromCodePoint(160002, 656069, 876329, 995871, 359420, 329072, 972128, 872280, 344907, 884187, 266381, 191752, 566843, 866577, 413854, 1021197, 31478, 832645, 75458, 1006944),true,true,true,false,false,true); } catch(e) { }
+try { htmlvar00005.incremental = true; } catch(e) { }
+try { var00117.setProperty("-webkit-perspective-origin", "1px 0px"); } catch(e) { }
+try { var00037.scroll(0.977614411819,0.534504911401); } catch(e) { }
+try { /* newvar{var00140:long} */ var var00140 = htmlvar00013.offsetLeft; } catch(e) { }
+try { htmlvar00022.onkeydown = var00063; } catch(e) { }
+try { /* newvar{var00141:htmlstring} */ var var00141 = svgvar00006.innerHTML; } catch(e) { }
+try { if (!var00141) { var00141 = GetVariable(fuzzervars, 'htmlstring'); } else { SetVariable(var00141, 'htmlstring'); } } catch(e) { }
+try { var00038.setProperty("min-height", "0em"); } catch(e) { }
+try { /* newvar{var00142:Element} */ var var00142 = document.createElement("html"); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00142) { var00142 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00142, 'Element'); SetVariable(var00142, 'GlobalEventHandlers'); SetVariable(var00142, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00143:boolean} */ var var00143 = var00077.shiftKey; } catch(e) { }
+try { /* newvar{var00145:SecurityPolicyViolationEvent} */ var var00145 = document.createEvent("SecurityPolicyViolationEvent"); } catch(e) { }
+try { if (!var00145) { var00145 = GetVariable(fuzzervars, 'SecurityPolicyViolationEvent'); } else { SetVariable(var00145, 'SecurityPolicyViolationEvent'); SetVariable(var00145, 'Event'); } } catch(e) { }
+try { /* newvar{var00144:DOMString} */ var var00144 = var00145.violatedDirective; } catch(e) { }
+try { htmlvar00027.tabIndex = 0; } catch(e) { }
+try { /* newvar{var00146:Document} */ var var00146 = var00062.contentDocument; } catch(e) { }
+try { if (!var00146) { var00146 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00146, 'Document'); SetVariable(var00146, 'GlobalEventHandlers'); SetVariable(var00146, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { var00102.replaceWith(String.fromCodePoint(1069506, 807484, 36238, 948338, 635212, 265774, 1037742, 753554, 825843, 1065018, 278720, 990327, 1052386, 663499, 314088, 640370, 941701, 378901, 1058859, 976091)); } catch(e) { }
+try { /* newvar{var00147:Element} */ var var00147 = htmlvar00005.list; } catch(e) { }
+try { if (!var00147) { var00147 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00147, 'Element'); SetVariable(var00147, 'GlobalEventHandlers'); SetVariable(var00147, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00148:EventHandler} */ var var00148 = svgvar00009.oncontextmenu; } catch(e) { }
+try { if (!var00148) { var00148 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00148, 'EventHandler'); } } catch(e) { }
+try { var00125.cssFloat = "right auto"; } catch(e) { }
+try { svgvar00011.setAttribute("orient", "2turn"); } catch(e) { }
+try { /* newvar{var00149:ScrollStateCallback} */ var var00149 = var00058; } catch(e) { }
+try { if (!var00149) { var00149 = GetVariable(fuzzervars, 'ScrollStateCallback'); } else { SetVariable(var00149, 'ScrollStateCallback'); } } catch(e) { }
+try { /* newvar{var00150:NativeScrollBehavior} */ var var00150 = "disable-native-scroll"; } catch(e) { }
+try { if (!var00150) { var00150 = GetVariable(fuzzervars, 'NativeScrollBehavior'); } else { SetVariable(var00150, 'NativeScrollBehavior'); } } catch(e) { }
+try { var00037.setApplyScroll(var00149,var00150); } catch(e) { }
+try { /* newvar{var00151:short} */ var var00151 = svgvar00008.nodeType; } catch(e) { }
+try { var00069.startTime = 0.51867165456; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00152:DOMString} */ var var00152 = htmlvar00012.hreflang; } catch(e) { }
+try { /* newvar{var00153:CanPlayTypeResult} */ var var00153 = htmlvar00001.canPlayType(String.fromCharCode(41, 110, 86, 114, 41, 85, 110, 115, 35, 111, 97, 112, 65, 57, 54, 37, 123, 94, 72, 45)); } catch(e) { }
+try { if (!var00153) { var00153 = GetVariable(fuzzervars, 'CanPlayTypeResult'); } else { SetVariable(var00153, 'CanPlayTypeResult'); } } catch(e) { }
+try { /* newvar{var00154:ScrollStateCallback} */ var var00154 = var00058; } catch(e) { }
+try { if (!var00154) { var00154 = GetVariable(fuzzervars, 'ScrollStateCallback'); } else { SetVariable(var00154, 'ScrollStateCallback'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00155:NativeScrollBehavior} */ var var00155 = "disable-native-scroll"; } catch(e) { }
+try { if (!var00155) { var00155 = GetVariable(fuzzervars, 'NativeScrollBehavior'); } else { SetVariable(var00155, 'NativeScrollBehavior'); } } catch(e) { }
+try { htmlvar00004.setApplyScroll(var00154,var00155); } catch(e) { }
+try { var00038.setProperty("src", "local(0)"); } catch(e) { }
+try { var00117.setProperty("lighting-color", "green"); } catch(e) { }
+try { /* newvar{var00156:HTMLCollection} */ var var00156 = var00036.getElementsByClassName("class4"); } catch(e) { }
+try { if (!var00156) { var00156 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00156, 'HTMLCollection'); } } catch(e) { }
+try { htmlvar00005.dirName = "" + String.fromCharCode(110, 52, 53, 83, 102, 106, 119, 48, 80, 104, 61, 111, 94, 33, 83, 52, 117, 84, 74, 125) + ""; } catch(e) { }
+try { /* newvar{var00165:TrackEvent} */ var var00165 = document.createEvent("TrackEvent"); } catch(e) { }
+try { if (!var00165) { var00165 = GetVariable(fuzzervars, 'TrackEvent'); } else { SetVariable(var00165, 'TrackEvent'); SetVariable(var00165, 'Event'); } } catch(e) { }
+try { /* newvar{var00164:TextTrack} */ var var00164 = var00165.track; } catch(e) { }
+try { if (!var00164) { var00164 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00164, 'TextTrack'); SetVariable(var00164, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00163:VTTRegionList} */ var var00163 = var00164.regions; } catch(e) { }
+try { if (!var00163) { var00163 = GetVariable(fuzzervars, 'VTTRegionList'); } else { SetVariable(var00163, 'VTTRegionList'); } } catch(e) { }
+try { /* newvar{var00162:VTTRegion} */ var var00162 = var00163.item(2%var00163.length); } catch(e) { }
+try { if (!var00162) { var00162 = GetVariable(fuzzervars, 'VTTRegion'); } else { SetVariable(var00162, 'VTTRegion'); } } catch(e) { }
+try { /* newvar{var00161:TextTrack} */ var var00161 = var00162.track; } catch(e) { }
+try { if (!var00161) { var00161 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00161, 'TextTrack'); SetVariable(var00161, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00160:TextTrackKind} */ var var00160 = var00161.kind; } catch(e) { }
+try { if (!var00160) { var00160 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00160, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00159:TextTrack} */ var var00159 = htmlvar00024.addTextTrack(var00160,String.fromCharCode(99, 86, 104, 118, 117, 65, 70, 41, 55, 97, 92, 76, 41, 49, 88, 114, 119, 99, 83, 62)); } catch(e) { }
+try { if (!var00159) { var00159 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00159, 'TextTrack'); SetVariable(var00159, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00158:TextTrackCueList} */ var var00158 = var00159.cues; } catch(e) { }
+try { if (!var00158) { var00158 = GetVariable(fuzzervars, 'TextTrackCueList'); } else { SetVariable(var00158, 'TextTrackCueList'); } } catch(e) { }
+try { /* newvar{var00157:TextTrackCue} */ var var00157 = var00158[66%var00158.length]; } catch(e) { }
+try { if (!var00157) { var00157 = GetVariable(fuzzervars, 'TextTrackCue'); } else { SetVariable(var00157, 'TextTrackCue'); SetVariable(var00157, 'EventTarget'); } } catch(e) { }
+try { var00157.onexit = var00031; } catch(e) { }
+try { var00039.initUIEvent(); } catch(e) { }
+try { svgvar00010.setAttribute("divisor", "1"); } catch(e) { }
+try { /* newvar{var00166:DOMString} */ var var00166 = var00146.alinkColor; } catch(e) { }
+try { svgvar00003.setPointerCapture(0); } catch(e) { }
+try { var00090.id = String.fromCharCode(65, 46, 112, 114, 90, 48, 42, 79, 60, 92, 89, 103, 40, 69, 120, 63, 121, 77, 32, 45); } catch(e) { }
+try { htmlvar00030.setAttribute("declare", "declare"); } catch(e) { }
+try { var00125.setProperty("float", "left"); } catch(e) { }
+try { htmlvar00025.onratechange = var00063; } catch(e) { }
+try { document.all[94%document.all.length].appendChild(htmlvar00001); } catch(e) { }
+try { var00117.setProperty("quotes", "'-' '-'"); } catch(e) { }
+try { /* newvar{var00167:long} */ var var00167 = var00127.tiltY; } catch(e) { }
+try { /* newvar{var00168:GlobalEventHandlers} */ var var00168 = htmlvar00029; } catch(e) { }
+try { if (!var00168) { var00168 = GetVariable(fuzzervars, 'GlobalEventHandlers'); } else { SetVariable(var00168, 'GlobalEventHandlers'); } } catch(e) { }
+try { var00038.setProperty("-webkit-margin-end", "1px"); } catch(e) { }
+try { /* newvar{var00169:SVGGraphicsElement} */ var var00169 = svgvar00002; } catch(e) { }
+try { if (!var00169) { var00169 = GetVariable(fuzzervars, 'SVGGraphicsElement'); } else { SetVariable(var00169, 'SVGGraphicsElement'); SetVariable(var00169, 'SVGElement'); SetVariable(var00169, 'GlobalEventHandlers'); SetVariable(var00169, 'EventTarget'); SetVariable(var00169, 'GlobalEventHandlers'); } } catch(e) { }
+try { htmlvar00003.oninvalid = var00050; } catch(e) { }
+try { /* newvar{var00170:GlobalEventHandlers} */ var var00170 = var00086; } catch(e) { }
+try { if (!var00170) { var00170 = GetVariable(fuzzervars, 'GlobalEventHandlers'); } else { SetVariable(var00170, 'GlobalEventHandlers'); } } catch(e) { }
+try { var00036.setAttribute("display", "table-caption"); } catch(e) { }
+try { /* newvar{var00171:double} */ var var00171 = var00040.deltaY; } catch(e) { }
+try { /* newvar{var00172:DOMString} */ var var00172 = htmlvar00012.charset; } catch(e) { }
+try { /* newvar{var00173:boolean} */ var var00173 = document.execCommand("fontName", false, "fantasy"); } catch(e) { }
+try { /* newvar{var00174:NodeList} */ var var00174 = htmlvar00017.childNodes; } catch(e) { }
+try { if (!var00174) { var00174 = GetVariable(fuzzervars, 'NodeList'); } else { SetVariable(var00174, 'NodeList'); } } catch(e) { }
+try { var00038.setProperty("border-left-width", "-1"); } catch(e) { }
+try { /* newvar{var00175:HTMLCollection} */ var var00175 = document.getElementsByClassName("class8"); } catch(e) { }
+try { if (!var00175) { var00175 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00175, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00176:DOMString} */ var var00176 = htmlvar00012.ping; } catch(e) { }
+try { /* newvar{var00177:EventTarget} */ var var00177 = var00002; } catch(e) { }
+try { if (!var00177) { var00177 = GetVariable(fuzzervars, 'EventTarget'); } else { SetVariable(var00177, 'EventTarget'); } } catch(e) { }
+try { var00170.ondrag = var00148; } catch(e) { }
+try { var00062.setAttribute("azimuth", "0"); } catch(e) { }
+try { /* newvar{var00178:XPathResult} */ var var00178 = var00067.evaluate("//source",document); } catch(e) { }
+try { if (!var00178) { var00178 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00178, 'XPathResult'); } } catch(e) { }
+try { var00117.setProperty("mso-outline-level", "0"); } catch(e) { }
+try { /* newvar{var00179:Navigator} */ var var00179 = var00106.clientInformation; } catch(e) { }
+try { if (!var00179) { var00179 = GetVariable(fuzzervars, 'Navigator'); } else { SetVariable(var00179, 'Navigator'); SetVariable(var00179, 'NavigatorCPU'); SetVariable(var00179, 'NavigatorID'); SetVariable(var00179, 'NavigatorLanguage'); SetVariable(var00179, 'NavigatorOnLine'); SetVariable(var00179, 'NavigatorStorageUtils'); } } catch(e) { }
+try { htmlvar00011.close(String.fromCodePoint(414522, 842544, 849760, 511910, 589600, 632483, 138205, 1098956, 453978, 240482, 657463, 867189, 862008, 492040, 82729, 587376, 301529, 201590, 127190, 810753)); } catch(e) { }
+try { var00062[String.fromCodePoint(487850, 1058627, 222410, 687614, 1082065, 632359, 270630, 163424, 1107042, 9533, 538149, 448542, 688467, 347279, 490755, 168529, 706281, 245963, 398530, 559215)] = var00090; } catch(e) { }
+try { svgvar00006.setAttribute("onmouseover", "var00058"); } catch(e) { }
+try { /* newvar{var00180:TextTrack} */ var var00180 = htmlvar00006.addTextTrack(var00160); } catch(e) { }
+try { if (!var00180) { var00180 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00180, 'TextTrack'); SetVariable(var00180, 'EventTarget'); } } catch(e) { }
+try { var00038.setProperty("-webkit-line-clamp", "0"); } catch(e) { }
+try { svgvar00012.prepend(svgvar00011); } catch(e) { }
+try { /* newvar{var00181:DOMString} */ var var00181 = var00117.cssFloat; } catch(e) { }
+try { /* newvar{var00182:EventHandler} */ var var00182 = svgvar00005.ondragstart; } catch(e) { }
+try { if (!var00182) { var00182 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00182, 'EventHandler'); } } catch(e) { }
+try { var00117.setProperty("-webkit-transform", "translatez(1)"); } catch(e) { }
+try { /* newvar{var00183:DOMString} */ var var00183 = var00179.productSub; } catch(e) { }
+try { var00117.setProperty("-webkit-transition-timing-function", "inherit"); } catch(e) { }
+try { var00106.onorientationchange = var00182; } catch(e) { }
+try { var00077.initMouseEvent("htmlvar00003",false,false,var00111,7,0); } catch(e) { }
+try { /* newvar{var00184:DOMString} */ var var00184 = var00146.charset; } catch(e) { }
+try { svgvar00011.setAttribute("font-size", "inherit"); } catch(e) { }
+try { htmlvar00027.onbeforecopy = var00114; } catch(e) { }
+try { /* newvar{var00185:HTMLMeterElement} */ var var00185 = document.createElement("meter"); } catch(e) { }
+try { if (!var00185) { var00185 = GetVariable(fuzzervars, 'HTMLMeterElement'); } else { SetVariable(var00185, 'HTMLMeterElement'); SetVariable(var00185, 'Element'); SetVariable(var00185, 'GlobalEventHandlers'); SetVariable(var00185, 'EventTarget'); } } catch(e) { }
+try { var00185.value = 0.375488427849; } catch(e) { }
+try { /* newvar{var00186:Touch} */ var var00186 = document.createTouch(var00106,htmlvar00003,1,0.0519696971099,0.843963727014,0.468285856779,0.669980246038,0.139357285727,0.262224331229,0.878068711435); } catch(e) { }
+try { if (!var00186) { var00186 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00186, 'Touch'); } } catch(e) { }
+try { /* newvar{var00187:AnimationPlayState} */ var var00187 = var00069.playState; } catch(e) { }
+try { if (!var00187) { var00187 = GetVariable(fuzzervars, 'AnimationPlayState'); } else { SetVariable(var00187, 'AnimationPlayState'); } } catch(e) { }
+try { var00032.setAttribute("contenteditable", "false"); } catch(e) { }
+try { var00125.setProperty("-webkit-user-select", "ignore"); } catch(e) { }
+try { htmlvar00031.maxLength = 4; } catch(e) { }
+try { /* newvar{var00188:EventHandler} */ var var00188 = var00086.onlanguagechange; } catch(e) { }
+try { if (!var00188) { var00188 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00188, 'EventHandler'); } } catch(e) { }
+try { var00111.focus(); } catch(e) { }
+try { var00125.setProperty("animation-timing-function", "step-end"); } catch(e) { }
+try { /* newvar{var00189:boolean} */ var var00189 = var00129.intersectsNode(htmlvar00016); } catch(e) { }
+try { /* newvar{var00190:EventHandler} */ var var00190 = window.onstorage; } catch(e) { }
+try { if (!var00190) { var00190 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00190, 'EventHandler'); } } catch(e) { }
+try { htmlvar00001.setAttribute("onabort", "eventhandler4()"); } catch(e) { }
+try { /* newvar{var00191:Window} */ var var00191 = var00112[14%var00112.length]; } catch(e) { }
+try { if (!var00191) { var00191 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00191, 'Window'); SetVariable(var00191, 'GlobalEventHandlers'); SetVariable(var00191, 'WindowBase64'); SetVariable(var00191, 'WindowEventHandlers'); SetVariable(var00191, 'WindowTimers'); SetVariable(var00191, 'EventTarget'); } } catch(e) { }
+try { var00038.setProperty("-webkit-user-drag", "none"); } catch(e) { }
+try { window.WebKitMutationObserver = var00116; } catch(e) { }
+try { /* newvar{var00192:DOMString} */ var var00192 = htmlvar00004.lang; } catch(e) { }
+try { var00102.scope = "row"; } catch(e) { }
+try { /* newvar{var00193:Attr} */ var var00193 = var00146.createAttributeNS("http://www.w3.org/1999/xhtml","onmouseenter"); } catch(e) { }
+try { if (!var00193) { var00193 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00193, 'Attr'); } } catch(e) { }
+try { var00191.onorientationchange = var00053; } catch(e) { }
+try { /* newvar{var00194:DOMString} */ var var00194 = var00091.name; } catch(e) { }
+try { /* newvar{var00195:long} */ var var00195 = var00006.addRule(String.fromCharCode(41, 122, 111, 37, 71, 57, 104, 37, 120, 62, 71, 117, 80, 42, 83, 44, 70, 114, 85, 93),"foo",-1); } catch(e) { }
+try { /* newvar{var00196:DOMString} */ var var00196 = var00038["-webkit-shape-outside"]; } catch(e) { }
+try { var00036.setAttribute("scale", "0.957044605847"); } catch(e) { }
+try { /* newvar{var00197:boolean} */ var var00197 = htmlvar00006.hidden; } catch(e) { }
+try { htmlvar00029.setAttribute("onmessage", "eventhandler5()"); } catch(e) { }
+try { /* newvar{var00198:boolean} */ var var00198 = var00146.execCommand("selectAll", false); } catch(e) { }
+try { /* newvar{var00199:long} */ var var00199 = svgvar00004.tabIndex; } catch(e) { }
+try { /* newvar{var00201:HTMLSelectElement} */ var var00201 = document.createElement("select"); } catch(e) { }
+try { if (!var00201) { var00201 = GetVariable(fuzzervars, 'HTMLSelectElement'); } else { SetVariable(var00201, 'HTMLSelectElement'); SetVariable(var00201, 'Element'); SetVariable(var00201, 'GlobalEventHandlers'); SetVariable(var00201, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00200:HTMLOptionsCollection} */ var var00200 = var00201.options; } catch(e) { }
+try { if (!var00200) { var00200 = GetVariable(fuzzervars, 'HTMLOptionsCollection'); } else { SetVariable(var00200, 'HTMLOptionsCollection'); SetVariable(var00200, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00202:HTMLOptionElement} */ var var00202 = document.createElement("option"); } catch(e) { }
+try { if (!var00202) { var00202 = GetVariable(fuzzervars, 'HTMLOptionElement'); } else { SetVariable(var00202, 'HTMLOptionElement'); SetVariable(var00202, 'Element'); SetVariable(var00202, 'GlobalEventHandlers'); SetVariable(var00202, 'EventTarget'); } } catch(e) { }
+try { var00200.add(var00202,htmlvar00027); } catch(e) { }
+//endjs
+var fuzzervars = {};
+freememory()
+
+
+}
+
+function eventhandler2() {
+
+runcount["eventhandler2"]++; if(runcount["eventhandler2"] > 2) { return; }
+
+var fuzzervars = {};
+
+SetVariable(fuzzervars, window, 'Window');
+SetVariable(fuzzervars, document, 'Document');
+SetVariable(fuzzervars, document.body.firstChild, 'Element');
+
+//beginjs
+/* newvar{htmlvar00001:HTMLVideoElement} */ var htmlvar00001 = document.getElementById("htmlvar00001"); //HTMLVideoElement
+/* newvar{htmlvar00002:HTMLTrackElement} */ var htmlvar00002 = document.getElementById("htmlvar00002"); //HTMLTrackElement
+/* newvar{htmlvar00003:HTMLFormElement} */ var htmlvar00003 = document.getElementById("htmlvar00003"); //HTMLFormElement
+/* newvar{htmlvar00004:HTMLLegendElement} */ var htmlvar00004 = document.getElementById("htmlvar00004"); //HTMLLegendElement
+/* newvar{htmlvar00005:HTMLInputElement} */ var htmlvar00005 = document.getElementById("htmlvar00005"); //HTMLInputElement
+/* newvar{htmlvar00006:HTMLVideoElement} */ var htmlvar00006 = document.getElementById("htmlvar00006"); //HTMLVideoElement
+/* newvar{htmlvar00007:HTMLLinkElement} */ var htmlvar00007 = document.getElementById("htmlvar00007"); //HTMLLinkElement
+/* newvar{htmlvar00008:HTMLMapElement} */ var htmlvar00008 = document.getElementById("htmlvar00008"); //HTMLMapElement
+/* newvar{htmlvar00009:HTMLTextAreaElement} */ var htmlvar00009 = document.getElementById("htmlvar00009"); //HTMLTextAreaElement
+/* newvar{svgvar00001:SVGSVGElement} */ var svgvar00001 = document.getElementById("svgvar00001"); //SVGSVGElement
+/* newvar{svgvar00002:SVGForeignObjectElement} */ var svgvar00002 = document.getElementById("svgvar00002"); //SVGForeignObjectElement
+/* newvar{htmlvar00010:HTMMLUnknownElement} */ var htmlvar00010 = document.getElementById("htmlvar00010"); //HTMMLUnknownElement
+/* newvar{htmlvar00011:HTMLDialogElement} */ var htmlvar00011 = document.getElementById("htmlvar00011"); //HTMLDialogElement
+/* newvar{svgvar00003:SVGGElement} */ var svgvar00003 = document.getElementById("svgvar00003"); //SVGGElement
+/* newvar{svgvar00004:SVGElement} */ var svgvar00004 = document.getElementById("svgvar00004"); //SVGElement
+/* newvar{htmlvar00012:HTMLAnchorElement} */ var htmlvar00012 = document.getElementById("htmlvar00012"); //HTMLAnchorElement
+/* newvar{svgvar00005:SVGFEMergeElement} */ var svgvar00005 = document.getElementById("svgvar00005"); //SVGFEMergeElement
+/* newvar{svgvar00006:SVGAnimateTransformElement} */ var svgvar00006 = document.getElementById("svgvar00006"); //SVGAnimateTransformElement
+/* newvar{svgvar00007:SVGTSpanElement} */ var svgvar00007 = document.getElementById("svgvar00007"); //SVGTSpanElement
+/* newvar{svgvar00008:SVGCircleElement} */ var svgvar00008 = document.getElementById("svgvar00008"); //SVGCircleElement
+/* newvar{svgvar00009:SVGAnimateElement} */ var svgvar00009 = document.getElementById("svgvar00009"); //SVGAnimateElement
+/* newvar{svgvar00010:SVGAnimateTransformElement} */ var svgvar00010 = document.getElementById("svgvar00010"); //SVGAnimateTransformElement
+/* newvar{svgvar00011:SVGFEConvolveMatrixElement} */ var svgvar00011 = document.getElementById("svgvar00011"); //SVGFEConvolveMatrixElement
+/* newvar{svgvar00012:SVGElement} */ var svgvar00012 = document.getElementById("svgvar00012"); //SVGElement
+/* newvar{htmlvar00013:HTMLMarqueeElement} */ var htmlvar00013 = document.getElementById("htmlvar00013"); //HTMLMarqueeElement
+/* newvar{htmlvar00014:HTMLUListElement} */ var htmlvar00014 = document.getElementById("htmlvar00014"); //HTMLUListElement
+/* newvar{htmlvar00015:HTMLLIElement} */ var htmlvar00015 = document.getElementById("htmlvar00015"); //HTMLLIElement
+/* newvar{htmlvar00016:HTMLBaseElement} */ var htmlvar00016 = document.getElementById("htmlvar00016"); //HTMLBaseElement
+/* newvar{htmlvar00017:HTMLOListElement} */ var htmlvar00017 = document.getElementById("htmlvar00017"); //HTMLOListElement
+/* newvar{htmlvar00018:HTMLLIElement} */ var htmlvar00018 = document.getElementById("htmlvar00018"); //HTMLLIElement
+/* newvar{htmlvar00019:HTMLDirectoryElement} */ var htmlvar00019 = document.getElementById("htmlvar00019"); //HTMLDirectoryElement
+/* newvar{htmlvar00020:HTMLShadowElement} */ var htmlvar00020 = document.getElementById("htmlvar00020"); //HTMLShadowElement
+/* newvar{htmlvar00021:HTMLUnknownElement} */ var htmlvar00021 = document.getElementById("htmlvar00021"); //HTMLUnknownElement
+/* newvar{htmlvar00022:HTMLLIElement} */ var htmlvar00022 = document.getElementById("htmlvar00022"); //HTMLLIElement
+/* newvar{htmlvar00023:HTMLDetailsElement} */ var htmlvar00023 = document.getElementById("htmlvar00023"); //HTMLDetailsElement
+/* newvar{htmlvar00024:HTMLVideoElement} */ var htmlvar00024 = document.getElementById("htmlvar00024"); //HTMLVideoElement
+/* newvar{htmlvar00025:HTMLTimeElement} */ var htmlvar00025 = document.getElementById("htmlvar00025"); //HTMLTimeElement
+/* newvar{htmlvar00026:HTMLUnknownElement} */ var htmlvar00026 = document.getElementById("htmlvar00026"); //HTMLUnknownElement
+/* newvar{htmlvar00027:HTMLTimeElement} */ var htmlvar00027 = document.getElementById("htmlvar00027"); //HTMLTimeElement
+/* newvar{htmlvar00028:HTMLUnknownElement} */ var htmlvar00028 = document.createElement("noembed"); //HTMLUnknownElement
+/* newvar{htmlvar00029:HTMLProgressElement} */ var htmlvar00029 = document.createElement("progress"); //HTMLProgressElement
+/* newvar{htmlvar00030:HTMLMapElement} */ var htmlvar00030 = document.createElement("map"); //HTMLMapElement
+/* newvar{htmlvar00031:HTMLTextAreaElement} */ var htmlvar00031 = document.createElement("textarea"); //HTMLTextAreaElement
+/* newvar{htmlvar00032:HTMLUnknownElement} */ var htmlvar00032 = document.createElement("html"); //HTMLUnknownElement
+try { htmlvar00028.setAttribute("ontransitionend", "eventhandler5()"); } catch(e) { }
+try { htmlvar00007.setAttribute("row", "26"); } catch(e) { }
+try { /* newvar{var00001:SVGAnimationElement} */ var var00001 = svgvar00010; } catch(e) { }
+try { if (!var00001) { var00001 = GetVariable(fuzzervars, 'SVGAnimationElement'); } else { SetVariable(var00001, 'SVGAnimationElement'); SetVariable(var00001, 'SVGTests'); SetVariable(var00001, 'SVGElement'); SetVariable(var00001, 'GlobalEventHandlers'); SetVariable(var00001, 'EventTarget'); SetVariable(var00001, 'GlobalEventHandlers'); } } catch(e) { }
+try { htmlvar00017.setAttribute("ondblclick", "eventhandler4()"); } catch(e) { }
+try { htmlvar00020.setAttribute("sizes", "1vw"); } catch(e) { }
+try { /* newvar{var00002:boolean} */ var var00002 = document.xmlStandalone; } catch(e) { }
+try { /* newvar{var00004:TouchEvent} */ var var00004 = document.createEvent("TouchEvent"); } catch(e) { }
+try { if (!var00004) { var00004 = GetVariable(fuzzervars, 'TouchEvent'); } else { SetVariable(var00004, 'TouchEvent'); SetVariable(var00004, 'UIEvent'); SetVariable(var00004, 'Event'); } } catch(e) { }
+try { /* newvar{var00003:boolean} */ var var00003 = var00004.shiftKey; } catch(e) { }
+try { htmlvar00005.src = "data:audio/mp3;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAADAAAGhgBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr///////////////////////////////////////////8AAAA5TEFNRTMuOTlyAc0AAAAAAAAAABSAJAKjQgAAgAAABoaLLYLcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//uQxAAAVHoO86Ch/wKrQh+UIz/YShKDZqEIAAE3kQFg+NSyUDm5f/yB+D/GP8hjmzG6Jy7lvFu8Iif7i7vApIeVfN/DkGIKGInCaJxNu9wifzeiTfJlaJX/Np//9wKClWWDcG4vBiIYwcB4NHigohguDcBcIxSiAaB4JAgT6jf2YDkQi5/mmabkya6nTRBy5uRyKB48TiFogeguDih66JwykEQBKzjbzTdl3FjUCgfnYZFWM01W3xx4g/qtMn//v/////9+j9oeZe+G35O3ZKZ9f+8N1LCTyD5/hhewsfDj0TDUzpMMkhzaPS6TS172Po89nnJ1mln9/pod31/j4jYgPWx7Aq5MUFns3tUmlSzP2fSvZYbOVT9OP3yLJ4kTEQacS6PSzeXtGQ2It0A5GhIiGn0WMgS8ajcLgZ5bBbhuIFSj0FuHwJQsY9yIPgmZ0C5kpLKpyAaBMiOBSC9Lmcypf2WJKVNItoAE2UDUo2XGvl3+5Sn5///efkKpqSl6nNZq7mRvk4LTEpFJ8EAuIIcxAhRdGejHgAcDIOpMMVju//uSxB6AVKYRAYCN/sKXwiAoFL/gDcjA/qGXMzOkX/l6QcZi6hvb6Y4WczOL93AnkfJl7CVqfnbUQ0Ho3KpwmVbcT59DQkvrEhSnUC6Vj6U8DvLevkCV5hs+WMupZKsylEjyvcT0cEcY7S2P0YSlVGAubM6oKYf5cj6jZk1KwsxdIeZzRc/S4vzv5eR9ur/9Leh0fZPPeV5uvbrzTv1SuTy5NxTyW3CF0vrF1tLFsuFa7336yxlTi7cnKcof3kvPKu5/1fyqy/lVf2b1DpDDpE7RIhSOJDZQicyQqsmKYEpKJ2M6IbchCvO84TjUCHIWP411MmlAd6cVrAhDUf5xJU/mJkJihqdI4dY9D5RrxBi+sQeEacRPSTBouAj48i+Lh04Z/8v/mf/f////+8V7RiRllObiOvpaJWu06xcyGP0pkpaptJDnnhj0eWiixyiewi5rebgxesayRHMuP+27WN/HfdbJvEP4fQXk7++VdHVMZm+0Oe2aU4o1xHQ5iSKepDeM60sIchLEqmFqep1TE9OEwxKtsdOtj1EFMyJsxcoWMv/7ksQ/gFTqEPwAmf7CYEId8BM/4JpLqWw6TTWAcxNS6msRk0RbhJT6D+FfP4lBBVSsgOJvhmkkOEjSBhUgSJQIpiTyc1V/nL+i/8UK//upf/4Sf9vjfy8+nynnTUTkjVVv7VZGEnfN9PLHSckai1d/TotT5X/9PLV2rznavW+ZYltU8yxyRqTkUTkjcaTlgpiU0XVgsUcmATAkqN8xYUZh3lOsCilexWJqjvXq8hR+qluTrIW5pOUyTCLESFHH6dLVGP5Li2qxlP1UD1JclJkro0lDNtVMQU1FMy45OS41VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU="; } catch(e) { }
+try { /* newvar{var00005:Element} */ var var00005 = htmlvar00025; } catch(e) { }
+try { if (!var00005) { var00005 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00005, 'Element'); SetVariable(var00005, 'GlobalEventHandlers'); SetVariable(var00005, 'EventTarget'); } } catch(e) { }
+try { htmlvar00020.setAttribute("dirname", "" + String.fromCharCode(106, 39, 119, 67, 84, 94, 110, 59, 41, 41, 79, 86, 122, 114, 68, 91, 77, 63, 86, 103) + ""); } catch(e) { }
+try { /* newvar{var00007:HTMLButtonElement} */ var var00007 = document.createElement("button"); } catch(e) { }
+try { if (!var00007) { var00007 = GetVariable(fuzzervars, 'HTMLButtonElement'); } else { SetVariable(var00007, 'HTMLButtonElement'); SetVariable(var00007, 'Element'); SetVariable(var00007, 'GlobalEventHandlers'); SetVariable(var00007, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00006:ValidityState} */ var var00006 = var00007.validity; } catch(e) { }
+try { if (!var00006) { var00006 = GetVariable(fuzzervars, 'ValidityState'); } else { SetVariable(var00006, 'ValidityState'); } } catch(e) { }
+try { /* newvar{var00008:EventHandler} */ var var00008 = svgvar00005.onbeforecut; } catch(e) { }
+try { if (!var00008) { var00008 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00008, 'EventHandler'); } } catch(e) { }
+try { htmlvar00022.value = 1; } catch(e) { }
+try { /* newvar{var00009:SVGElement} */ var var00009 = svgvar00003.nearestViewportElement; } catch(e) { }
+try { if (!var00009) { var00009 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00009, 'SVGElement'); SetVariable(var00009, 'GlobalEventHandlers'); SetVariable(var00009, 'EventTarget'); SetVariable(var00009, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00010:Touch} */ var var00010 = document.createTouch(window,htmlvar00025,-1,0.396517145887,0.135522347195,0.651616191836,0.843538003236,0.388552689112); } catch(e) { }
+try { if (!var00010) { var00010 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00010, 'Touch'); } } catch(e) { }
+try { htmlvar00025.setAttribute("onplaying", "eventhandler4()"); } catch(e) { }
+try { htmlvar00012.ping = "" + String.fromCharCode(69, 98, 64, 105, 37, 73, 83, 68, 99, 109, 103, 57, 54, 123, 82, 125, 126, 35, 41, 79) + ""; } catch(e) { }
+try { htmlvar00013.attachedCallback(); } catch(e) { }
+try { svgvar00004.prepend(svgvar00007); } catch(e) { }
+try { /* newvar{var00011:EventHandler} */ var var00011 = window.onwebkitanimationend; } catch(e) { }
+try { if (!var00011) { var00011 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00011, 'EventHandler'); } } catch(e) { }
+try { var00009.setAttribute("stroke-opacity", "1"); } catch(e) { }
+try { /* newvar{var00012:EventHandler} */ var var00012 = svgvar00002.onbeforecut; } catch(e) { }
+try { if (!var00012) { var00012 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00012, 'EventHandler'); } } catch(e) { }
+try { htmlvar00027.onwheel = var00012; } catch(e) { }
+try { document.all[80%document.all.length].appendChild(var00005); } catch(e) { }
+try { /* newvar{var00013:SVGElement} */ var var00013 = svgvar00001; } catch(e) { }
+try { if (!var00013) { var00013 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00013, 'SVGElement'); SetVariable(var00013, 'GlobalEventHandlers'); SetVariable(var00013, 'EventTarget'); SetVariable(var00013, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00014:Element} */ var var00014 = htmlvar00025.offsetParent; } catch(e) { }
+try { if (!var00014) { var00014 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00014, 'Element'); SetVariable(var00014, 'GlobalEventHandlers'); SetVariable(var00014, 'EventTarget'); } } catch(e) { }
+try { htmlvar00028.setAttribute("noshade", "noshade"); } catch(e) { }
+try { /* newvar{var00015:StaticRange} */ var var00015 = sequence_StaticRange[0]; } catch(e) { }
+try { if (!var00015) { var00015 = GetVariable(fuzzervars, 'StaticRange'); } else { SetVariable(var00015, 'StaticRange'); } } catch(e) { }
+try { var00015.startContainer = htmlvar00024; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00016:ApplicationCache} */ var var00016 = window.applicationCache; } catch(e) { }
+try { if (!var00016) { var00016 = GetVariable(fuzzervars, 'ApplicationCache'); } else { SetVariable(var00016, 'ApplicationCache'); SetVariable(var00016, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00017:EventHandler} */ var var00017 = htmlvar00013.onsuspend; } catch(e) { }
+try { if (!var00017) { var00017 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00017, 'EventHandler'); } } catch(e) { }
+try { var00016.onnoupdate = var00017; } catch(e) { }
+try { /* newvar{var00018:double} */ var var00018 = var00010.screenX; } catch(e) { }
+try { htmlvar00017.onblur = var00012; } catch(e) { }
+try { svgvar00009.onrepeat = var00012; } catch(e) { }
+try { /* newvar{var00020:FocusEvent} */ var var00020 = document.createEvent("FocusEvent"); } catch(e) { }
+try { if (!var00020) { var00020 = GetVariable(fuzzervars, 'FocusEvent'); } else { SetVariable(var00020, 'FocusEvent'); SetVariable(var00020, 'UIEvent'); SetVariable(var00020, 'Event'); } } catch(e) { }
+try { /* newvar{var00019:UIEvent} */ var var00019 = var00020; } catch(e) { }
+try { if (!var00019) { var00019 = GetVariable(fuzzervars, 'UIEvent'); } else { SetVariable(var00019, 'UIEvent'); SetVariable(var00019, 'Event'); } } catch(e) { }
+try { htmlvar00005.setAttribute("can-process-drag", "false"); } catch(e) { }
+try { /* newvar{var00021:long} */ var var00021 = htmlvar00006.width; } catch(e) { }
+try { /* newvar{var00022:eventhandler} */ var var00022 = eventhandler1; } catch(e) { }
+try { if (!var00022) { var00022 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00022, 'eventhandler'); } } catch(e) { }
+try { svgvar00003.setAttribute("onerror", "var00022"); } catch(e) { }
+try { var00005.setAttribute("ondragleave", "eventhandler5()"); } catch(e) { }
+try { /* newvar{var00023:long} */ var var00023 = window.length; } catch(e) { }
+try { htmlvar00005.minLength = 1; } catch(e) { }
+try { htmlvar00022.setAttribute("onmouseup", "eventhandler4()"); } catch(e) { }
+try { /* newvar{var00024:EventHandler} */ var var00024 = svgvar00008.onselectstart; } catch(e) { }
+try { if (!var00024) { var00024 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00024, 'EventHandler'); } } catch(e) { }
+try { var00001.onmouseenter = var00024; } catch(e) { }
+try { /* newvar{var00028:SVGMPathElement} */ var var00028 = document.createElementNS("http://www.w3.org/2000/svg", "mpath"); } catch(e) { }
+try { if (!var00028) { var00028 = GetVariable(fuzzervars, 'SVGMPathElement'); } else { SetVariable(var00028, 'SVGMPathElement'); SetVariable(var00028, 'SVGURIReference'); SetVariable(var00028, 'SVGElement'); SetVariable(var00028, 'GlobalEventHandlers'); SetVariable(var00028, 'EventTarget'); SetVariable(var00028, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00027:SVGURIReference} */ var var00027 = var00028; } catch(e) { }
+try { if (!var00027) { var00027 = GetVariable(fuzzervars, 'SVGURIReference'); } else { SetVariable(var00027, 'SVGURIReference'); } } catch(e) { }
+try { /* newvar{var00026:SVGAnimatedString} */ var var00026 = var00027.href; } catch(e) { }
+try { if (!var00026) { var00026 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00026, 'SVGAnimatedString'); } } catch(e) { }
+try { /* newvar{var00025:DOMString} */ var var00025 = var00026.baseVal; } catch(e) { }
+try { /* newvar{var00029:HTMLCollection} */ var var00029 = document.anchors; } catch(e) { }
+try { if (!var00029) { var00029 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00029, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00030:EventHandler} */ var var00030 = window.onrejectionhandled; } catch(e) { }
+try { if (!var00030) { var00030 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00030, 'EventHandler'); } } catch(e) { }
+try { svgvar00005.onpointerenter = var00030; } catch(e) { }
+try { /* newvar{var00031:boolean} */ var var00031 = htmlvar00005.autofocus; } catch(e) { }
+try { /* newvar{var00032:float} */ var var00032 = svgvar00007.getComputedTextLength(); } catch(e) { }
+try { /* newvar{var00033:EventHandler} */ var var00033 = svgvar00001.onbeforepaste; } catch(e) { }
+try { if (!var00033) { var00033 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00033, 'EventHandler'); } } catch(e) { }
+try { svgvar00012.setAttribute("color-interpolation", "linearRGB"); } catch(e) { }
+try { htmlvar00024.playbackRate = 0.652795782185; } catch(e) { }
+try { var00016.onchecking = var00011; } catch(e) { }
+try { htmlvar00005.setAttribute("language", "javascript"); } catch(e) { }
+try { /* newvar{var00034:SVGAnimatedLength} */ var var00034 = svgvar00008.r; } catch(e) { }
+try { if (!var00034) { var00034 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00034, 'SVGAnimatedLength'); } } catch(e) { }
+try { htmlvar00015.setAttribute("data", "" + String.fromCharCode(35, 53, 68, 94, 60, 123, 38, 102, 98, 73, 89, 107, 110, 65, 38, 121, 89, 40, 49, 91) + ""); } catch(e) { }
+try { htmlvar00003.acceptCharset = String.fromCharCode(79, 105, 41, 34, 46, 74, 66, 46, 105, 40, 39, 125, 61, 125, 111, 32, 85, 98, 120, 108); } catch(e) { }
+try { /* newvar{var00035:Comment} */ var var00035 = document.createComment("htmlvar00001"); } catch(e) { }
+try { if (!var00035) { var00035 = GetVariable(fuzzervars, 'Comment'); } else { SetVariable(var00035, 'Comment'); SetVariable(var00035, 'CharacterData'); SetVariable(var00035, 'Element'); SetVariable(var00035, 'GlobalEventHandlers'); SetVariable(var00035, 'EventTarget'); } } catch(e) { }
+try { svgvar00011.focus(); } catch(e) { }
+try { htmlvar00031.setAttribute("ondrop", "eventhandler5()"); } catch(e) { }
+try { var00013.onratechange = var00012; } catch(e) { }
+try { /* newvar{var00036:long} */ var var00036 = htmlvar00001.webkitDroppedFrameCount; } catch(e) { }
+try { htmlvar00023.setAttribute("shape", "poly"); } catch(e) { }
+try { /* newvar{var00037:Comment} */ var var00037 = document.createComment("htmlvar00009"); } catch(e) { }
+try { if (!var00037) { var00037 = GetVariable(fuzzervars, 'Comment'); } else { SetVariable(var00037, 'Comment'); SetVariable(var00037, 'CharacterData'); SetVariable(var00037, 'Element'); SetVariable(var00037, 'GlobalEventHandlers'); SetVariable(var00037, 'EventTarget'); } } catch(e) { }
+try { var00001.scrollIntoViewIfNeeded(); } catch(e) { }
+try { /* newvar{var00038:boolean} */ var var00038 = document.execCommand("selectAll", false); } catch(e) { }
+try { /* newvar{var00039:ClientRectList} */ var var00039 = var00001.getClientRects(); } catch(e) { }
+try { if (!var00039) { var00039 = GetVariable(fuzzervars, 'ClientRectList'); } else { SetVariable(var00039, 'ClientRectList'); } } catch(e) { }
+try { /* newvar{var00040:boolean} */ var var00040 = var00006.stepMismatch; } catch(e) { }
+try { /* newvar{var00041:boolean} */ var var00041 = document.queryCommandEnabled(String.fromCharCode(98, 41, 97, 49, 49, 62, 119, 97, 48, 119, 59, 46, 123, 48, 119, 70, 118, 53, 43, 97)); } catch(e) { }
+try { var00014.setAttribute("cellspacing", "1"); } catch(e) { }
+try { /* newvar{var00042:SVGAnimatedLength} */ var var00042 = svgvar00002.height; } catch(e) { }
+try { if (!var00042) { var00042 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00042, 'SVGAnimatedLength'); } } catch(e) { }
+try { /* newvar{var00045:XPathEvaluator} */ var var00045 = new XPathEvaluator(); } catch(e) { }
+try { if (!var00045) { var00045 = GetVariable(fuzzervars, 'XPathEvaluator'); } else { SetVariable(var00045, 'XPathEvaluator'); } } catch(e) { }
+try { /* newvar{var00044:XPathExpression} */ var var00044 = var00045.createExpression("//canvas"); } catch(e) { }
+try { if (!var00044) { var00044 = GetVariable(fuzzervars, 'XPathExpression'); } else { SetVariable(var00044, 'XPathExpression'); } } catch(e) { }
+try { /* newvar{var00050:HTMLObjectElement} */ var var00050 = document.createElement("object"); } catch(e) { }
+try { if (!var00050) { var00050 = GetVariable(fuzzervars, 'HTMLObjectElement'); } else { SetVariable(var00050, 'HTMLObjectElement'); SetVariable(var00050, 'Element'); SetVariable(var00050, 'GlobalEventHandlers'); SetVariable(var00050, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00049:Document} */ var var00049 = var00050.contentDocument; } catch(e) { }
+try { if (!var00049) { var00049 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00049, 'Document'); SetVariable(var00049, 'GlobalEventHandlers'); SetVariable(var00049, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00048:DOMImplementation} */ var var00048 = var00049.implementation; } catch(e) { }
+try { if (!var00048) { var00048 = GetVariable(fuzzervars, 'DOMImplementation'); } else { SetVariable(var00048, 'DOMImplementation'); } } catch(e) { }
+try { /* newvar{var00047:Document} */ var var00047 = var00048.createHTMLDocument("foo"); } catch(e) { }
+try { if (!var00047) { var00047 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00047, 'Document'); SetVariable(var00047, 'GlobalEventHandlers'); SetVariable(var00047, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00051:XPathNSResolver} */ var var00051 = var00045.createNSResolver(htmlvar00028); } catch(e) { }
+try { if (!var00051) { var00051 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00051, 'XPathNSResolver'); } } catch(e) { }
+try { /* newvar{var00046:XPathResult} */ var var00046 = var00047.evaluate("//h6",htmlvar00011,var00051); } catch(e) { }
+try { if (!var00046) { var00046 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00046, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00043:XPathResult} */ var var00043 = var00044.evaluate(htmlvar00015,XPathResult.ANY_TYPE,var00046); } catch(e) { }
+try { if (!var00043) { var00043 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00043, 'XPathResult'); } } catch(e) { }
+try { htmlvar00009.setAttribute("scoped", "scoped"); } catch(e) { }
+try { document.all[25%document.all.length].appendChild(htmlvar00007); } catch(e) { }
+try { svgvar00006.append(svgvar00004); } catch(e) { }
+try { /* newvar{var00052:DOMString} */ var var00052 = var00007.type; } catch(e) { }
+try { /* newvar{var00053:USVString} */ var var00053 = htmlvar00012.origin; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00053) { var00053 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00053, 'USVString'); } } catch(e) { }
+try { htmlvar00012.search = var00053; } catch(e) { }
+try { /* newvar{var00057:SVGTextPositioningElement} */ var var00057 = svgvar00007; } catch(e) { }
+try { if (!var00057) { var00057 = GetVariable(fuzzervars, 'SVGTextPositioningElement'); } else { SetVariable(var00057, 'SVGTextPositioningElement'); SetVariable(var00057, 'SVGTextContentElement'); SetVariable(var00057, 'SVGGraphicsElement'); SetVariable(var00057, 'SVGElement'); SetVariable(var00057, 'GlobalEventHandlers'); SetVariable(var00057, 'EventTarget'); SetVariable(var00057, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00056:SVGAnimatedLengthList} */ var var00056 = var00057.x; } catch(e) { }
+try { if (!var00056) { var00056 = GetVariable(fuzzervars, 'SVGAnimatedLengthList'); } else { SetVariable(var00056, 'SVGAnimatedLengthList'); } } catch(e) { }
+try { /* newvar{var00055:SVGLengthList} */ var var00055 = var00056.baseVal; } catch(e) { }
+try { if (!var00055) { var00055 = GetVariable(fuzzervars, 'SVGLengthList'); } else { SetVariable(var00055, 'SVGLengthList'); } } catch(e) { }
+try { /* newvar{var00054:SVGLength} */ var var00054 = var00055.getItem(0); } catch(e) { }
+try { if (!var00054) { var00054 = GetVariable(fuzzervars, 'SVGLength'); } else { SetVariable(var00054, 'SVGLength'); } } catch(e) { }
+try { var00054.valueInSpecifiedUnits = 1; } catch(e) { }
+try { /* newvar{var00058:long} */ var var00058 = svgvar00006.clientLeft; } catch(e) { }
+try { window.resizeBy(1,0); } catch(e) { }
+try { /* newvar{var00061:Document} */ var var00061 = htmlvar00030.ownerDocument; } catch(e) { }
+try { if (!var00061) { var00061 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00061, 'Document'); SetVariable(var00061, 'GlobalEventHandlers'); SetVariable(var00061, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00060:Attr} */ var var00060 = var00061.createAttribute("loopstart"); } catch(e) { }
+try { if (!var00060) { var00060 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00060, 'Attr'); } } catch(e) { }
+try { /* newvar{var00059:Attr} */ var var00059 = var00028.setAttributeNodeNS(var00060); } catch(e) { }
+try { if (!var00059) { var00059 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00059, 'Attr'); } } catch(e) { }
+try { /* newvar{var00075:CSSRuleList} */ var var00075 = window.getMatchedCSSRules(); } catch(e) { }
+try { if (!var00075) { var00075 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00075, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00074:CSSRule} */ var var00074 = var00075.item(46%var00075.length); } catch(e) { }
+try { if (!var00074) { var00074 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00074, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00073:CSSImportRule} */ var var00073 = var00074; } catch(e) { }
+try { if (!var00073) { var00073 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00073, 'CSSImportRule'); SetVariable(var00073, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00072:CSSRule} */ var var00072 = var00073; } catch(e) { }
+try { if (!var00072) { var00072 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00072, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00071:CSSViewportRule} */ var var00071 = var00072; } catch(e) { }
+try { if (!var00071) { var00071 = GetVariable(fuzzervars, 'CSSViewportRule'); } else { SetVariable(var00071, 'CSSViewportRule'); SetVariable(var00071, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00070:CSSRule} */ var var00070 = var00071; } catch(e) { }
+try { if (!var00070) { var00070 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00070, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00069:CSSImportRule} */ var var00069 = var00070; } catch(e) { }
+try { if (!var00069) { var00069 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00069, 'CSSImportRule'); SetVariable(var00069, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00068:CSSStyleSheet} */ var var00068 = var00069.styleSheet; } catch(e) { }
+try { if (!var00068) { var00068 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00068, 'CSSStyleSheet'); SetVariable(var00068, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00067:CSSRule} */ var var00067 = var00068.ownerRule; } catch(e) { }
+try { if (!var00067) { var00067 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00067, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00066:CSSStyleRule} */ var var00066 = var00067; } catch(e) { }
+try { if (!var00066) { var00066 = GetVariable(fuzzervars, 'CSSStyleRule'); } else { SetVariable(var00066, 'CSSStyleRule'); SetVariable(var00066, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00065:CSSRule} */ var var00065 = var00066; } catch(e) { }
+try { if (!var00065) { var00065 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00065, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00064:CSSKeyframesRule} */ var var00064 = var00065; } catch(e) { }
+try { if (!var00064) { var00064 = GetVariable(fuzzervars, 'CSSKeyframesRule'); } else { SetVariable(var00064, 'CSSKeyframesRule'); SetVariable(var00064, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00063:CSSKeyframeRule} */ var var00063 = var00064.findRule(String.fromCodePoint(692168, 913385, 270237, 280702, 894061, 591592, 281801, 6551, 906405, 126157, 282941, 1065794, 921902, 935714, 676377, 500588, 162070, 471288, 281137, 816929)); } catch(e) { }
+try { if (!var00063) { var00063 = GetVariable(fuzzervars, 'CSSKeyframeRule'); } else { SetVariable(var00063, 'CSSKeyframeRule'); SetVariable(var00063, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00062:CSSStyleDeclaration} */ var var00062 = var00063.style; } catch(e) { }
+try { if (!var00062) { var00062 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00062, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00062.setProperty("dominant-baseline", "no-change"); } catch(e) { }
+try { htmlvar00013.attachedCallback(); } catch(e) { }
+try { /* newvar{var00076:boolean} */ var var00076 = var00020.cancelBubble; } catch(e) { }
+try { /* newvar{var00077:EventHandler} */ var var00077 = htmlvar00007.oncancel; } catch(e) { }
+try { if (!var00077) { var00077 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00077, 'EventHandler'); } } catch(e) { }
+try { var00007.autofocus = false; } catch(e) { }
+try { /* newvar{var00078:Element} */ var var00078 = var00046.iterateNext(); } catch(e) { }
+try { if (!var00078) { var00078 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00078, 'Element'); SetVariable(var00078, 'GlobalEventHandlers'); SetVariable(var00078, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00079:DOMString} */ var var00079 = var00007.name; } catch(e) { }
+try { svgvar00012.setAttribute("gradientTransform", "translate(0 -1) scale(0.490402629687)"); } catch(e) { }
+try { /* newvar{var00080:HTMLBodyElement} */ var var00080 = document.createElement("body"); } catch(e) { }
+try { if (!var00080) { var00080 = GetVariable(fuzzervars, 'HTMLBodyElement'); } else { SetVariable(var00080, 'HTMLBodyElement'); SetVariable(var00080, 'WindowEventHandlers'); SetVariable(var00080, 'Element'); SetVariable(var00080, 'GlobalEventHandlers'); SetVariable(var00080, 'EventTarget'); } } catch(e) { }
+try { var00080.onblur = var00012; } catch(e) { }
+try { htmlvar00014.setAttribute("onabort", "eventhandler4()"); } catch(e) { }
+try { var00062.setProperty("-webkit-text-stroke", "0px white"); } catch(e) { }
+try { /* newvar{var00081:boolean} */ var var00081 = htmlvar00005.readOnly; } catch(e) { }
+try { var00015.endContainer = htmlvar00006; } catch(e) { }
+try { /* newvar{var00082:DOMString} */ var var00082 = var00050.width; } catch(e) { }
+try { /* newvar{var00083:float} */ var var00083 = var00010.radiusY; } catch(e) { }
+try { /* newvar{var00084:Window} */ var var00084 = window.frames; } catch(e) { }
+try { if (!var00084) { var00084 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00084, 'Window'); SetVariable(var00084, 'GlobalEventHandlers'); SetVariable(var00084, 'WindowBase64'); SetVariable(var00084, 'WindowEventHandlers'); SetVariable(var00084, 'WindowTimers'); SetVariable(var00084, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00085:Document} */ var var00085 = var00050.getSVGDocument(); } catch(e) { }
+try { if (!var00085) { var00085 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00085, 'Document'); SetVariable(var00085, 'GlobalEventHandlers'); SetVariable(var00085, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00086:long} */ var var00086 = htmlvar00001.height; } catch(e) { }
+try { /* newvar{var00087:boolean} */ var var00087 = var00006.stepMismatch; } catch(e) { }
+try { svgvar00002.setAttribute("targetX", "0"); } catch(e) { }
+try { var00062.setProperty("text-rendering", "optimizelegibility"); } catch(e) { }
+try { var00062.setProperty("font-variant", "all"); } catch(e) { }
+try { /* newvar{var00089:HTMLTrackElement} */ var var00089 = document.createElement("track"); } catch(e) { }
+try { if (!var00089) { var00089 = GetVariable(fuzzervars, 'HTMLTrackElement'); } else { SetVariable(var00089, 'HTMLTrackElement'); SetVariable(var00089, 'Element'); SetVariable(var00089, 'GlobalEventHandlers'); SetVariable(var00089, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00088:TextTrack} */ var var00088 = var00089.track; } catch(e) { }
+try { if (!var00088) { var00088 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00088, 'TextTrack'); SetVariable(var00088, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00091:TextTrackCueList} */ var var00091 = var00088.activeCues; } catch(e) { }
+try { if (!var00091) { var00091 = GetVariable(fuzzervars, 'TextTrackCueList'); } else { SetVariable(var00091, 'TextTrackCueList'); } } catch(e) { }
+try { /* newvar{var00090:TextTrackCue} */ var var00090 = var00091[74%var00091.length]; } catch(e) { }
+try { if (!var00090) { var00090 = GetVariable(fuzzervars, 'TextTrackCue'); } else { SetVariable(var00090, 'TextTrackCue'); SetVariable(var00090, 'EventTarget'); } } catch(e) { }
+try { var00088.addCue(var00090); } catch(e) { }
+try { /* newvar{var00093:MediaQueryListEvent} */ var var00093 = document.createEvent("MediaQueryListEvent"); } catch(e) { }
+try { if (!var00093) { var00093 = GetVariable(fuzzervars, 'MediaQueryListEvent'); } else { SetVariable(var00093, 'MediaQueryListEvent'); SetVariable(var00093, 'Event'); } } catch(e) { }
+try { /* newvar{var00092:boolean} */ var var00092 = var00093.matches; } catch(e) { }
+try { /* newvar{var00094:Navigator} */ var var00094 = var00084.navigator; } catch(e) { }
+try { if (!var00094) { var00094 = GetVariable(fuzzervars, 'Navigator'); } else { SetVariable(var00094, 'Navigator'); SetVariable(var00094, 'NavigatorCPU'); SetVariable(var00094, 'NavigatorID'); SetVariable(var00094, 'NavigatorLanguage'); SetVariable(var00094, 'NavigatorOnLine'); SetVariable(var00094, 'NavigatorStorageUtils'); } } catch(e) { }
+try { svgvar00006.setAttribute("systemLanguage", "ca"); } catch(e) { }
+try { htmlvar00011.close(); } catch(e) { }
+try { /* newvar{var00095:Attr} */ var var00095 = htmlvar00025.removeAttributeNode(htmlvar00025.attributes[42%htmlvar00025.attributes.length].name); } catch(e) { }
+try { if (!var00095) { var00095 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00095, 'Attr'); } } catch(e) { }
+try { htmlvar00005.useMap = "#htmlvar00005"; } catch(e) { }
+try { /* newvar{var00096:boolean} */ var var00096 = htmlvar00007.disabled; } catch(e) { }
+try { /* newvar{var00097:long} */ var var00097 = var00084.screenLeft; } catch(e) { }
+try { /* newvar{var00098:TextTrackCueList} */ var var00098 = var00088.activeCues; } catch(e) { }
+try { if (!var00098) { var00098 = GetVariable(fuzzervars, 'TextTrackCueList'); } else { SetVariable(var00098, 'TextTrackCueList'); } } catch(e) { }
+try { /* newvar{var00100:eventhandler} */ var var00100 = eventhandler3; } catch(e) { }
+try { if (!var00100) { var00100 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00100, 'eventhandler'); } } catch(e) { }
+try { /* newvar{var00099:EventListener} */ var var00099 = var00100; } catch(e) { }
+try { if (!var00099) { var00099 = GetVariable(fuzzervars, 'EventListener'); } else { SetVariable(var00099, 'EventListener'); } } catch(e) { }
+try { htmlvar00006.addEventListener("DOMNodeRemovedFromDocument", var00099); } catch(e) { }
+try { svgvar00009.setAttribute("stroke-dashoffset", "inherit"); } catch(e) { }
+try { /* newvar{var00101:FileList} */ var var00101 = htmlvar00005.files; } catch(e) { }
+try { if (!var00101) { var00101 = GetVariable(fuzzervars, 'FileList'); } else { SetVariable(var00101, 'FileList'); } } catch(e) { }
+try { /* newvar{var00102:float} */ var var00102 = svgvar00006.getSimpleDuration(); } catch(e) { }
+try { /* newvar{var00103:long} */ var var00103 = var00068.addRule("htmlvar00008","htmlvar00007"); } catch(e) { }
+try { /* newvar{var00104:boolean} */ var var00104 = var00006.tooLong; } catch(e) { }
+try { document.all[35%document.all.length].appendChild(htmlvar00016); } catch(e) { }
+try { /* newvar{var00105:boolean} */ var var00105 = htmlvar00001.autoplay; } catch(e) { }
+try { var00062.setProperty("baseline-shift", "0"); } catch(e) { }
+try { var00005.setAttribute("srcdoc", "" + String.fromCharCode(107, 72, 123, 77, 113, 88, 99, 90, 107, 56, 77, 126, 55, 87, 107, 52, 34, 96, 102, 58) + ""); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { svgvar00004.setAttribute("transform", "scale(81,0) scale(1,100,-1) translate(0,1)"); } catch(e) { }
+try { /* newvar{var00106:SVGURIReference} */ var var00106 = var00028; } catch(e) { }
+try { if (!var00106) { var00106 = GetVariable(fuzzervars, 'SVGURIReference'); } else { SetVariable(var00106, 'SVGURIReference'); } } catch(e) { }
+try { htmlvar00001.pause(); } catch(e) { }
+try { /* newvar{var00107:CSSStyleSheet} */ var var00107 = var00069.styleSheet; } catch(e) { }
+try { if (!var00107) { var00107 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00107, 'CSSStyleSheet'); SetVariable(var00107, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00108:Element} */ var var00108 = var00015.endContainer; } catch(e) { }
+try { if (!var00108) { var00108 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00108, 'Element'); SetVariable(var00108, 'GlobalEventHandlers'); SetVariable(var00108, 'EventTarget'); } } catch(e) { }
+try { window.onunhandledrejection = var00017; } catch(e) { }
+try { /* newvar{var00109:long} */ var var00109 = htmlvar00031.cols; } catch(e) { }
+try { var00001.ontimeupdate = var00033; } catch(e) { }
+try { /* newvar{var00111:HTMLScriptElement} */ var var00111 = document.createElement("script"); } catch(e) { }
+try { if (!var00111) { var00111 = GetVariable(fuzzervars, 'HTMLScriptElement'); } else { SetVariable(var00111, 'HTMLScriptElement'); SetVariable(var00111, 'Element'); SetVariable(var00111, 'GlobalEventHandlers'); SetVariable(var00111, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00110:DOMString} */ var var00110 = var00111.charset; } catch(e) { }
+try { var00080.setAttribute("lang", "en-au"); } catch(e) { }
+try { var00062.setProperty("direction", "ltr"); } catch(e) { }
+try { var00062.setProperty("background-repeat", "auto repeat-x"); } catch(e) { }
+try { document.cookie = "1"; } catch(e) { }
+try { /* newvar{var00112:URL} */ var var00112 = new URL("http://foo/bar"); } catch(e) { }
+try { if (!var00112) { var00112 = GetVariable(fuzzervars, 'URL'); } else { SetVariable(var00112, 'URL'); } } catch(e) { }
+try { var00112.hash = var00053; } catch(e) { }
+try { /* newvar{var00113:EventHandler} */ var var00113 = svgvar00003.ondurationchange; } catch(e) { }
+try { if (!var00113) { var00113 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00113, 'EventHandler'); } } catch(e) { }
+try { htmlvar00006.setAttribute("aria-multiselectable", "true"); } catch(e) { }
+try { svgvar00010.onwheel = var00113; } catch(e) { }
+try { document.all[83%document.all.length].appendChild(var00078); } catch(e) { }
+try { /* newvar{var00114:DOMString} */ var var00114 = svgvar00012.className; } catch(e) { }
+try { var00062.setProperty("rotate", "initial"); } catch(e) { }
+try { /* newvar{var00115:boolean} */ var var00115 = var00068.disabled; } catch(e) { }
+try { /* newvar{var00116:XPathNSResolver} */ var var00116 = var00045.createNSResolver(htmlvar00019); } catch(e) { }
+try { if (!var00116) { var00116 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00116, 'XPathNSResolver'); } } catch(e) { }
+try { htmlvar00005.setAttribute("vlink", ""); } catch(e) { }
+try { var00062.setProperty("margin-left", "0vw"); } catch(e) { }
+try { /* newvar{var00117:EventHandler} */ var var00117 = htmlvar00012.onpointermove; } catch(e) { }
+try { if (!var00117) { var00117 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00117, 'EventHandler'); } } catch(e) { }
+try { var00084.focus(); } catch(e) { }
+try { htmlvar00027.setAttribute("onplaying", "eventhandler4()"); } catch(e) { }
+try { /* newvar{var00118:USVString} */ var var00118 = htmlvar00012.port; } catch(e) { }
+try { if (!var00118) { var00118 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00118, 'USVString'); } } catch(e) { }
+try { htmlvar00012.search = var00118; } catch(e) { }
+try { /* newvar{var00119:DOMString} */ var var00119 = var00014.getAttributeNS("http://www.w3.org/1999/xhtml","alink"); } catch(e) { }
+try { var00062.setProperty("list-style-position", "inside"); } catch(e) { }
+try { var00078.onplay = var00017; } catch(e) { }
+try { /* newvar{var00120:boolean} */ var var00120 = var00093.composed; } catch(e) { }
+try { /* newvar{var00122:sequence_Dictionary_} */ var var00122 = { "accesskey": [1, 1] }; } catch(e) { }
+try { if (!var00122) { var00122 = GetVariable(fuzzervars, 'sequence_Dictionary_'); } else { SetVariable(var00122, 'sequence_Dictionary_'); } } catch(e) { }
+try { /* newvar{var00121:Animation} */ var var00121 = var00035.animate(var00122,0.571871087328); } catch(e) { }
+try { if (!var00121) { var00121 = GetVariable(fuzzervars, 'Animation'); } else { SetVariable(var00121, 'Animation'); SetVariable(var00121, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00123:short} */ var var00123 = var00089.readyState; } catch(e) { }
+try { var00062.setProperty("font-size", "75vmax"); } catch(e) { }
+try { htmlvar00005.alt = "" + String.fromCharCode(107, 66, 120, 41, 37, 68, 118, 125, 43, 66, 104, 125, 99, 44, 68, 119, 49, 32, 106, 114) + ""; } catch(e) { }
+try { /* newvar{var00124:CSSRule} */ var var00124 = var00068.ownerRule; } catch(e) { }
+try { if (!var00124) { var00124 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00124, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00125:double} */ var var00125 = var00010.pageX; } catch(e) { }
+try { svgvar00006.scrollIntoView(true); } catch(e) { }
+try { /* newvar{var00126:Element} */ var var00126 = htmlvar00003[26%htmlvar00003.length]; } catch(e) { }
+try { if (!var00126) { var00126 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00126, 'Element'); SetVariable(var00126, 'GlobalEventHandlers'); SetVariable(var00126, 'EventTarget'); } } catch(e) { }
+try { htmlvar00005.formNoValidate = true; } catch(e) { }
+try { htmlvar00008.setAttribute("lowsrc", "" + String.fromCharCode(36, 43, 38, 68, 86, 107, 85, 117, 49, 49, 60, 86, 71, 42, 64, 92, 86, 77, 113, 60) + ""); } catch(e) { }
+try { /* newvar{var00127:CSSStyleDeclaration} */ var var00127 = svgvar00009.style; } catch(e) { }
+try { if (!var00127) { var00127 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00127, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00127.setProperty("weight", "*"); } catch(e) { }
+try { /* newvar{var00128:XMLDocument} */ var var00128 = var00048.createDocument("1",String.fromCodePoint(321257, 548597, 996044, 690718, 626553, 718512, 616584, 278, 49604, 25179, 320609, 818475, 481503, 582187, 65840, 623373, 760303, 332874, 500809, 464079)); } catch(e) { }
+try { if (!var00128) { var00128 = GetVariable(fuzzervars, 'XMLDocument'); } else { SetVariable(var00128, 'XMLDocument'); SetVariable(var00128, 'Document'); SetVariable(var00128, 'GlobalEventHandlers'); SetVariable(var00128, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { var00062.setProperty("caption-side", "top"); } catch(e) { }
+try { var00062.setProperty("border-right-width", "medium"); } catch(e) { }
+try { /* newvar{var00129:ScrollToOptions} */ var var00129 = {left: 13, top: 0}; } catch(e) { }
+try { if (!var00129) { var00129 = GetVariable(fuzzervars, 'ScrollToOptions'); } else { SetVariable(var00129, 'ScrollToOptions'); } } catch(e) { }
+try { var00084.scrollBy(var00129); } catch(e) { }
+try { htmlvar00011.close(String.fromCharCode(59, 86, 50, 36, 46, 108, 52, 77, 71, 64, 58, 123, 110, 80, 115, 69, 80, 76, 51, 61)); } catch(e) { }
+try { /* newvar{var00130:SVGElement} */ var var00130 = var00001.nextElementSibling; } catch(e) { }
+try { if (!var00130) { var00130 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00130, 'SVGElement'); SetVariable(var00130, 'GlobalEventHandlers'); SetVariable(var00130, 'EventTarget'); SetVariable(var00130, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00131:boolean} */ var var00131 = htmlvar00008.hasAttributeNS("http://www.w3.org/1999/xhtml","role"); } catch(e) { }
+try { var00037.setAttribute("scheme", "NIST"); } catch(e) { }
+try { htmlvar00002.setAttribute("onprogress", "eventhandler2()"); } catch(e) { }
+try { /* newvar{var00132:Document} */ var var00132 = htmlvar00007.import; } catch(e) { }
+try { if (!var00132) { var00132 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00132, 'Document'); SetVariable(var00132, 'GlobalEventHandlers'); SetVariable(var00132, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { var00078.setAttribute("onwebkitkeyadded", "eventhandler4()"); } catch(e) { }
+try { var00050.codeBase = "" + String.fromCharCode(102, 125, 117, 61, 53, 61, 66, 45, 32, 122, 69, 100, 97, 102, 92, 126, 126, 92, 122, 61) + ""; } catch(e) { }
+try { /* newvar{var00133:EventHandler} */ var var00133 = htmlvar00002.onpaste; } catch(e) { }
+try { if (!var00133) { var00133 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00133, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00134:DOMString} */ var var00134 = var00080.background; } catch(e) { }
+try { /* newvar{var00135:ScrollStateCallback} */ var var00135 = var00022; } catch(e) { }
+try { if (!var00135) { var00135 = GetVariable(fuzzervars, 'ScrollStateCallback'); } else { SetVariable(var00135, 'ScrollStateCallback'); } } catch(e) { }
+try { /* newvar{var00136:NativeScrollBehavior} */ var var00136 = "disable-native-scroll"; } catch(e) { }
+try { if (!var00136) { var00136 = GetVariable(fuzzervars, 'NativeScrollBehavior'); } else { SetVariable(var00136, 'NativeScrollBehavior'); } } catch(e) { }
+try { htmlvar00024.setApplyScroll(var00135,var00136); } catch(e) { }
+try { var00093.initEvent(String.fromCharCode(57, 45, 33, 122, 101, 100, 98, 42, 38, 82, 125, 120, 107, 95, 35, 97, 71, 121, 35, 122),false,true); } catch(e) { }
+try { /* newvar{var00137:Touch} */ var var00137 = document.createTouch(window); } catch(e) { }
+try { if (!var00137) { var00137 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00137, 'Touch'); } } catch(e) { }
+try { /* newvar{var00138:DOMString} */ var var00138 = htmlvar00012.rel; } catch(e) { }
+try { htmlvar00006.setAttribute("placeholder", "" + String.fromCharCode(32, 36, 46, 42, 56, 48, 115, 110, 75, 119, 85, 112, 92, 123, 46, 64, 43, 106, 101, 34) + ""); } catch(e) { }
+try { /* newvar{var00139:ShadowRoot} */ var var00139 = htmlvar00025.shadowRoot; } catch(e) { }
+try { if (!var00139) { var00139 = GetVariable(fuzzervars, 'ShadowRoot'); } else { SetVariable(var00139, 'ShadowRoot'); SetVariable(var00139, 'DocumentOrShadowRoot'); SetVariable(var00139, 'DocumentFragment'); SetVariable(var00139, 'Element'); SetVariable(var00139, 'GlobalEventHandlers'); SetVariable(var00139, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00140:DOMString} */ var var00140 = var00047.alinkColor; } catch(e) { }
+try { var00057.setAttribute("path", "M0,0c-1,98 83,-1 0,-1"); } catch(e) { }
+try { /* newvar{var00141:HTMLHeadElement} */ var var00141 = document.head; } catch(e) { }
+try { if (!var00141) { var00141 = GetVariable(fuzzervars, 'HTMLHeadElement'); } else { SetVariable(var00141, 'HTMLHeadElement'); SetVariable(var00141, 'Element'); SetVariable(var00141, 'GlobalEventHandlers'); SetVariable(var00141, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00142:Promise_Animation_} */ var var00142 = var00121.ready; } catch(e) { }
+try { if (!var00142) { var00142 = GetVariable(fuzzervars, 'Promise_Animation_'); } else { SetVariable(var00142, 'Promise_Animation_'); } } catch(e) { }
+try { svgvar00007.setAttribute("name", "Courier"); } catch(e) { }
+try { var00009.blur(); } catch(e) { }
+try { var00062.setProperty("background-position-x", "47%"); } catch(e) { }
+try { var00111.webkitRequestFullscreen(); } catch(e) { }
+try { /* newvar{var00143:USVString} */ var var00143 = var00112.href; } catch(e) { }
+try { if (!var00143) { var00143 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00143, 'USVString'); } } catch(e) { }
+try { var00062.setProperty("mso-data-placement", "same-cell"); } catch(e) { }
+try { /* newvar{var00145:URLSearchParams} */ var var00145 = var00112.searchParams; } catch(e) { }
+try { if (!var00145) { var00145 = GetVariable(fuzzervars, 'URLSearchParams'); } else { SetVariable(var00145, 'URLSearchParams'); } } catch(e) { }
+try { /* newvar{var00144:USVString} */ var var00144 = var00145.get(var00053); } catch(e) { }
+try { if (!var00144) { var00144 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00144, 'USVString'); } } catch(e) { }
+try { var00062.setProperty("grid-auto-flow", "stack"); } catch(e) { }
+try { htmlvar00021.setAttribute("onselectstart", "eventhandler4()"); } catch(e) { }
+try { var00016.onobsolete = var00008; } catch(e) { }
+try { htmlvar00012.protocol = var00118; } catch(e) { }
+try { /* newvar{var00146:boolean} */ var var00146 = htmlvar00024.matches("1"); } catch(e) { }
+try { /* newvar{var00147:boolean} */ var var00147 = htmlvar00005.isSameNode(var00141); } catch(e) { }
+try { var00127.setProperty("-webkit-mask-composite", "copy"); } catch(e) { }
+try { /* newvar{var00148:EventHandler} */ var var00148 = svgvar00011.onloadstart; } catch(e) { }
+try { if (!var00148) { var00148 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00148, 'EventHandler'); } } catch(e) { }
+try { var00062.setProperty("contain", "size"); } catch(e) { }
+try { /* newvar{var00149:EventHandler} */ var var00149 = svgvar00005.onreset; } catch(e) { }
+try { if (!var00149) { var00149 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00149, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00151:RelatedEvent} */ var var00151 = document.createEvent("RelatedEvent"); } catch(e) { }
+try { if (!var00151) { var00151 = GetVariable(fuzzervars, 'RelatedEvent'); } else { SetVariable(var00151, 'RelatedEvent'); SetVariable(var00151, 'Event'); } } catch(e) { }
+try { /* newvar{var00150:Event} */ var var00150 = var00151; } catch(e) { }
+try { if (!var00150) { var00150 = GetVariable(fuzzervars, 'Event'); } else { SetVariable(var00150, 'Event'); } } catch(e) { }
+try { /* newvar{var00152:Event} */ var var00152 = var00019; } catch(e) { }
+try { if (!var00152) { var00152 = GetVariable(fuzzervars, 'Event'); } else { SetVariable(var00152, 'Event'); } } catch(e) { }
+try { var00014.setAttribute("valign", "baseline"); } catch(e) { }
+try { var00062.setProperty("mso-padding-alt", "-1in 1pt 72in -1pt"); } catch(e) { }
+try { var00078.setAttribute("language", "vbscript"); } catch(e) { }
+try { /* newvar{var00153:long} */ var var00153 = htmlvar00024.webkitVideoDecodedByteCount; } catch(e) { }
+try { /* newvar{var00154:EventHandler} */ var var00154 = var00132.oncopy; } catch(e) { }
+try { if (!var00154) { var00154 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00154, 'EventHandler'); } } catch(e) { }
+try { var00005.setAttribute("aria-describedby", "htmlvar00002"); } catch(e) { }
+try { htmlvar00011.setAttribute("defer", "defer"); } catch(e) { }
+try { /* newvar{var00155:ClientRect} */ var var00155 = svgvar00008.getBoundingClientRect(); } catch(e) { }
+try { if (!var00155) { var00155 = GetVariable(fuzzervars, 'ClientRect'); } else { SetVariable(var00155, 'ClientRect'); } } catch(e) { }
+try { htmlvar00013.stop(); } catch(e) { }
+try { var00126.setAttribute("aria-valuemin", "1"); } catch(e) { }
+try { /* newvar{var00156:EventHandler} */ var var00156 = var00084.onanimationend; } catch(e) { }
+try { if (!var00156) { var00156 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00156, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00158:GetRootNodeOptions} */ var var00158 = {composed: true}; } catch(e) { }
+try { if (!var00158) { var00158 = GetVariable(fuzzervars, 'GetRootNodeOptions'); } else { SetVariable(var00158, 'GetRootNodeOptions'); } } catch(e) { }
+try { /* newvar{var00157:SVGElement} */ var var00157 = svgvar00008.getRootNode(var00158); } catch(e) { }
+try { if (!var00157) { var00157 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00157, 'SVGElement'); SetVariable(var00157, 'GlobalEventHandlers'); SetVariable(var00157, 'EventTarget'); SetVariable(var00157, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00159:EventTarget} */ var var00159 = var00016; } catch(e) { }
+try { if (!var00159) { var00159 = GetVariable(fuzzervars, 'EventTarget'); } else { SetVariable(var00159, 'EventTarget'); } } catch(e) { }
+try { svgvar00007.setAttribute("calcMode", "discrete"); } catch(e) { }
+try { /* newvar{var00160:Selection} */ var var00160 = var00139.getSelection(); } catch(e) { }
+try { if (!var00160) { var00160 = GetVariable(fuzzervars, 'Selection'); } else { SetVariable(var00160, 'Selection'); } } catch(e) { }
+try { var00160.extend(htmlvar00020); } catch(e) { }
+try { var00093.initEvent("1",false); } catch(e) { }
+try { var00061.webkitExitFullscreen(); } catch(e) { }
+try { var00127.setProperty("-webkit-border-end", "solid"); } catch(e) { }
+try { /* newvar{var00161:DOMString} */ var var00161 = htmlvar00024.crossOrigin; } catch(e) { }
+try { /* newvar{var00162:Element} */ var var00162 = document.activeElement; } catch(e) { }
+try { if (!var00162) { var00162 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00162, 'Element'); SetVariable(var00162, 'GlobalEventHandlers'); SetVariable(var00162, 'EventTarget'); } } catch(e) { }
+try { var00127.setProperty("offset", "path('M 0 -1 h 1 v 1') 2px 0rad"); } catch(e) { }
+try { /* newvar{var00163:DOMString} */ var var00163 = var00047.linkColor; } catch(e) { }
+try { /* newvar{var00164:WindowEventHandlers} */ var var00164 = var00084; } catch(e) { }
+try { if (!var00164) { var00164 = GetVariable(fuzzervars, 'WindowEventHandlers'); } else { SetVariable(var00164, 'WindowEventHandlers'); } } catch(e) { }
+try { var00028.setAttribute("pointer-events", "fill"); } catch(e) { }
+try { /* newvar{var00165:DOMString} */ var var00165 = htmlvar00007.media; } catch(e) { }
+try { /* newvar{var00166:NodeList} */ var var00166 = htmlvar00029.labels; } catch(e) { }
+try { if (!var00166) { var00166 = GetVariable(fuzzervars, 'NodeList'); } else { SetVariable(var00166, 'NodeList'); } } catch(e) { }
+try { var00127.setProperty("user-zoom", "zoom"); } catch(e) { }
+try { htmlvar00013.behavior = "slide"; } catch(e) { }
+try { /* newvar{var00167:DOMTokenList} */ var var00167 = htmlvar00007.sizes; } catch(e) { }
+try { if (!var00167) { var00167 = GetVariable(fuzzervars, 'DOMTokenList'); } else { SetVariable(var00167, 'DOMTokenList'); } } catch(e) { }
+try { /* newvar{var00168:EventHandler} */ var var00168 = var00061.onsecuritypolicyviolation; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00168) { var00168 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00168, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00169:MediaList} */ var var00169 = var00107.media; } catch(e) { }
+try { if (!var00169) { var00169 = GetVariable(fuzzervars, 'MediaList'); } else { SetVariable(var00169, 'MediaList'); } } catch(e) { }
+try { /* newvar{var00170:boolean} */ var var00170 = htmlvar00005.multiple; } catch(e) { }
+try { /* newvar{var00171:XPathNSResolver} */ var var00171 = var00085.createNSResolver(htmlvar00017); } catch(e) { }
+try { if (!var00171) { var00171 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00171, 'XPathNSResolver'); } } catch(e) { }
+try { var00127.setProperty("columns", "0px"); } catch(e) { }
+try { /* newvar{var00172:CSSRuleList} */ var var00172 = var00068.rules; } catch(e) { }
+try { if (!var00172) { var00172 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00172, 'CSSRuleList'); } } catch(e) { }
+try { var00062.setProperty("-webkit-padding-end", "1px"); } catch(e) { }
+try { var00009.setAttribute("maskContentUnits", "objectBoundingBox"); } catch(e) { }
+try { /* newvar{var00173:DOMString} */ var var00173 = var00059.value; } catch(e) { }
+try { svgvar00001.setAttribute("min", "-1s"); } catch(e) { }
+try { /* newvar{var00174:EventHandler} */ var var00174 = var00084.ontransitionend; } catch(e) { }
+try { if (!var00174) { var00174 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00174, 'EventHandler'); } } catch(e) { }
+try { var00111.removeAttribute(var00111.attributes[35%var00111.attributes.length].name); } catch(e) { }
+try { /* newvar{var00175:boolean} */ var var00175 = svgvar00004.isEqualNode(svgvar00005); } catch(e) { }
+try { var00084.resizeBy(1,0); } catch(e) { }
+try { /* newvar{var00177:HTMLDListElement} */ var var00177 = document.createElement("dl"); } catch(e) { }
+try { if (!var00177) { var00177 = GetVariable(fuzzervars, 'HTMLDListElement'); } else { SetVariable(var00177, 'HTMLDListElement'); SetVariable(var00177, 'Element'); SetVariable(var00177, 'GlobalEventHandlers'); SetVariable(var00177, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00176:Element} */ var var00176 = var00177; } catch(e) { }
+try { if (!var00176) { var00176 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00176, 'Element'); SetVariable(var00176, 'GlobalEventHandlers'); SetVariable(var00176, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00178:boolean} */ var var00178 = var00061.execCommand("selectAll", false); } catch(e) { }
+try { htmlvar00025.setAttribute("challenge", "" + String.fromCharCode(44, 98, 125, 50, 32, 114, 53, 71, 73, 106, 58, 100, 119, 125, 115, 95, 67, 84, 50, 85) + ""); } catch(e) { }
+try { var00127.setProperty("-webkit-transition-duration", "1s"); } catch(e) { }
+try { svgvar00006.setAttribute("kernelUnitLength", "0 9"); } catch(e) { }
+try { var00035.setAttribute("rel", "license"); } catch(e) { }
+try { htmlvar00005.setAttribute("sizes", "-1px"); } catch(e) { }
+try { var00009.onbeforepaste = var00017; } catch(e) { }
+try { /* newvar{var00180:SVGMaskElement} */ var var00180 = document.createElementNS("http://www.w3.org/2000/svg", "mask"); } catch(e) { }
+try { if (!var00180) { var00180 = GetVariable(fuzzervars, 'SVGMaskElement'); } else { SetVariable(var00180, 'SVGMaskElement'); SetVariable(var00180, 'SVGTests'); SetVariable(var00180, 'SVGElement'); SetVariable(var00180, 'GlobalEventHandlers'); SetVariable(var00180, 'EventTarget'); SetVariable(var00180, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00179:svg_url_mask} */ var var00179 = "url(#" + var00180.id + ")"; } catch(e) { }
+try { if (!var00179) { var00179 = GetVariable(fuzzervars, 'svg_url_mask'); } else { SetVariable(var00179, 'svg_url_mask'); } } catch(e) { }
+try { svgvar00010.setAttribute("mask", var00179); } catch(e) { }
+try { var00062.setProperty("word-space", "nowrap"); } catch(e) { }
+try { /* newvar{var00181:double} */ var var00181 = var00137.screenY; } catch(e) { }
+try { /* newvar{var00182:EventHandler} */ var var00182 = htmlvar00009.oncancel; } catch(e) { }
+try { if (!var00182) { var00182 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00182, 'EventHandler'); } } catch(e) { }
+try { var00013.ondragenter = var00008; } catch(e) { }
+try { /* newvar{var00183:EventHandler} */ var var00183 = var00016.onchecking; } catch(e) { }
+try { if (!var00183) { var00183 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00183, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00184:SVGLength} */ var var00184 = var00055.initialize(var00054); } catch(e) { }
+try { if (!var00184) { var00184 = GetVariable(fuzzervars, 'SVGLength'); } else { SetVariable(var00184, 'SVGLength'); } } catch(e) { }
+try { /* newvar{var00185:CDATASection} */ var var00185 = var00085.createCDATASection("1"); } catch(e) { }
+try { if (!var00185) { var00185 = GetVariable(fuzzervars, 'CDATASection'); } else { SetVariable(var00185, 'CDATASection'); SetVariable(var00185, 'Text'); SetVariable(var00185, 'CharacterData'); SetVariable(var00185, 'Element'); SetVariable(var00185, 'GlobalEventHandlers'); SetVariable(var00185, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00186:DOMString} */ var var00186 = htmlvar00009.inputMode; } catch(e) { }
+try { /* newvar{var00187:EventHandler} */ var var00187 = var00080.onblur; } catch(e) { }
+try { if (!var00187) { var00187 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00187, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00188:boolean} */ var var00188 = var00004.ctrlKey; } catch(e) { }
+try { /* newvar{var00189:EventHandler} */ var var00189 = var00084.onwebkitanimationstart; } catch(e) { }
+try { if (!var00189) { var00189 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00189, 'EventHandler'); } } catch(e) { }
+try { htmlvar00024.setAttribute("onscroll", "eventhandler4()"); } catch(e) { }
+try { /* newvar{var00190:long} */ var var00190 = var00107.insertRule(String.fromCharCode(83, 80, 69, 33, 62, 100, 115, 113, 66, 42, 38, 90, 47, 110, 123, 73, 116, 102, 85, 76)); } catch(e) { }
+try { var00035.setAttribute("profile", "" + String.fromCharCode(79, 125, 76, 89, 108, 36, 56, 92, 118, 75, 62, 98, 125, 71, 46, 121, 103, 52, 34, 109) + ""); } catch(e) { }
+try { var00127.setProperty("max-height", "min-content"); } catch(e) { }
+try { /* newvar{var00191:CSSStyleDeclaration} */ var var00191 = svgvar00009.style; } catch(e) { }
+try { if (!var00191) { var00191 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00191, 'CSSStyleDeclaration'); } } catch(e) { }
+try { htmlvar00013.direction = "rtl"; } catch(e) { }
+try { var00035.replaceData(7,1,"htmlvar00008"); } catch(e) { }
+try { svgvar00008.onscroll = var00117; } catch(e) { }
+try { var00060.textContent = "false"; } catch(e) { }
+try { window.ontransitionend = var00024; } catch(e) { }
+try { var00078.setAttribute("case", "second"); } catch(e) { }
+try { var00127.setProperty("float", "-webkit-positioned"); } catch(e) { }
+try { htmlvar00004.setAttribute("onautocompleteerror", "eventhandler3()"); } catch(e) { }
+try { svgvar00012.setAttribute("direction", "rtl"); } catch(e) { }
+try { var00013.scrollLeft = 0.909759935717; } catch(e) { }
+try { htmlvar00011.setAttribute("onbeforescriptexecute", "eventhandler2()"); } catch(e) { }
+try { var00127.setProperty("counter-increment", "c"); } catch(e) { }
+try { var00084.onwebkitanimationiteration = var00133; } catch(e) { }
+try { var00057.setAttribute("hanging", "1"); } catch(e) { }
+try { document.all[85%document.all.length].appendChild(htmlvar00002); } catch(e) { }
+try { /* newvar{var00192:Element} */ var var00192 = var00049.createElement("noscript","1"); } catch(e) { }
+try { if (!var00192) { var00192 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00192, 'Element'); SetVariable(var00192, 'GlobalEventHandlers'); SetVariable(var00192, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00193:boolean} */ var var00193 = window.find("htmlvar00007",false,true); } catch(e) { }
+try { htmlvar00025.onseeked = var00189; } catch(e) { }
+try { svgvar00002.setAttribute("y1", "0.927697860167"); } catch(e) { }
+try { /* newvar{var00194:Element} */ var var00194 = htmlvar00012; } catch(e) { }
+try { if (!var00194) { var00194 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00194, 'Element'); SetVariable(var00194, 'GlobalEventHandlers'); SetVariable(var00194, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00198:SVGPolygonElement} */ var var00198 = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); } catch(e) { }
+try { if (!var00198) { var00198 = GetVariable(fuzzervars, 'SVGPolygonElement'); } else { SetVariable(var00198, 'SVGPolygonElement'); SetVariable(var00198, 'SVGGeometryElement'); SetVariable(var00198, 'SVGGraphicsElement'); SetVariable(var00198, 'SVGElement'); SetVariable(var00198, 'GlobalEventHandlers'); SetVariable(var00198, 'EventTarget'); SetVariable(var00198, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00197:SVGPointList} */ var var00197 = var00198.points; } catch(e) { }
+try { if (!var00197) { var00197 = GetVariable(fuzzervars, 'SVGPointList'); } else { SetVariable(var00197, 'SVGPointList'); } } catch(e) { }
+try { /* newvar{var00204:SVGPoint} */ var var00204 = svgvar00007.getEndPositionOfChar(1); } catch(e) { }
+try { if (!var00204) { var00204 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00204, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00203:SVGPoint} */ var var00203 = var00197.insertItemBefore(var00204,0); } catch(e) { }
+try { if (!var00203) { var00203 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00203, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00202:SVGPoint} */ var var00202 = var00197.appendItem(var00203); } catch(e) { }
+try { if (!var00202) { var00202 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00202, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00201:SVGPoint} */ var var00201 = var00197.replaceItem(var00202,55); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00201) { var00201 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00201, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00207:SVGMatrix} */ var var00207 = svgvar00001.createSVGMatrix(); } catch(e) { }
+try { if (!var00207) { var00207 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00207, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00206:SVGMatrix} */ var var00206 = var00207.skewY(0.733277113774); } catch(e) { }
+try { if (!var00206) { var00206 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00206, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00205:SVGMatrix} */ var var00205 = var00206.flipY(); } catch(e) { }
+try { if (!var00205) { var00205 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00205, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00200:SVGPoint} */ var var00200 = var00201.matrixTransform(var00205); } catch(e) { }
+try { if (!var00200) { var00200 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00200, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00199:SVGPoint} */ var var00199 = var00197.appendItem(var00200); } catch(e) { }
+try { if (!var00199) { var00199 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00199, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00196:SVGPoint} */ var var00196 = var00197.insertItemBefore(var00199,-1); } catch(e) { }
+try { if (!var00196) { var00196 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00196, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00195:boolean} */ var var00195 = svgvar00008.isPointInFill(var00196); } catch(e) { }
+//endjs
+var fuzzervars = {};
+freememory()
+
+
+}
+
+function eventhandler3() {
+
+runcount["eventhandler3"]++; if(runcount["eventhandler3"] > 2) { return; }
+
+var fuzzervars = {};
+
+SetVariable(fuzzervars, window, 'Window');
+SetVariable(fuzzervars, document, 'Document');
+SetVariable(fuzzervars, document.body.firstChild, 'Element');
+
+//beginjs
+/* newvar{htmlvar00001:HTMLVideoElement} */ var htmlvar00001 = document.getElementById("htmlvar00001"); //HTMLVideoElement
+/* newvar{htmlvar00002:HTMLTrackElement} */ var htmlvar00002 = document.getElementById("htmlvar00002"); //HTMLTrackElement
+/* newvar{htmlvar00003:HTMLFormElement} */ var htmlvar00003 = document.getElementById("htmlvar00003"); //HTMLFormElement
+/* newvar{htmlvar00004:HTMLLegendElement} */ var htmlvar00004 = document.getElementById("htmlvar00004"); //HTMLLegendElement
+/* newvar{htmlvar00005:HTMLInputElement} */ var htmlvar00005 = document.getElementById("htmlvar00005"); //HTMLInputElement
+/* newvar{htmlvar00006:HTMLVideoElement} */ var htmlvar00006 = document.getElementById("htmlvar00006"); //HTMLVideoElement
+/* newvar{htmlvar00007:HTMLLinkElement} */ var htmlvar00007 = document.getElementById("htmlvar00007"); //HTMLLinkElement
+/* newvar{htmlvar00008:HTMLMapElement} */ var htmlvar00008 = document.getElementById("htmlvar00008"); //HTMLMapElement
+/* newvar{htmlvar00009:HTMLTextAreaElement} */ var htmlvar00009 = document.getElementById("htmlvar00009"); //HTMLTextAreaElement
+/* newvar{svgvar00001:SVGSVGElement} */ var svgvar00001 = document.getElementById("svgvar00001"); //SVGSVGElement
+/* newvar{svgvar00002:SVGForeignObjectElement} */ var svgvar00002 = document.getElementById("svgvar00002"); //SVGForeignObjectElement
+/* newvar{htmlvar00010:HTMMLUnknownElement} */ var htmlvar00010 = document.getElementById("htmlvar00010"); //HTMMLUnknownElement
+/* newvar{htmlvar00011:HTMLDialogElement} */ var htmlvar00011 = document.getElementById("htmlvar00011"); //HTMLDialogElement
+/* newvar{svgvar00003:SVGGElement} */ var svgvar00003 = document.getElementById("svgvar00003"); //SVGGElement
+/* newvar{svgvar00004:SVGElement} */ var svgvar00004 = document.getElementById("svgvar00004"); //SVGElement
+/* newvar{htmlvar00012:HTMLAnchorElement} */ var htmlvar00012 = document.getElementById("htmlvar00012"); //HTMLAnchorElement
+/* newvar{svgvar00005:SVGFEMergeElement} */ var svgvar00005 = document.getElementById("svgvar00005"); //SVGFEMergeElement
+/* newvar{svgvar00006:SVGAnimateTransformElement} */ var svgvar00006 = document.getElementById("svgvar00006"); //SVGAnimateTransformElement
+/* newvar{svgvar00007:SVGTSpanElement} */ var svgvar00007 = document.getElementById("svgvar00007"); //SVGTSpanElement
+/* newvar{svgvar00008:SVGCircleElement} */ var svgvar00008 = document.getElementById("svgvar00008"); //SVGCircleElement
+/* newvar{svgvar00009:SVGAnimateElement} */ var svgvar00009 = document.getElementById("svgvar00009"); //SVGAnimateElement
+/* newvar{svgvar00010:SVGAnimateTransformElement} */ var svgvar00010 = document.getElementById("svgvar00010"); //SVGAnimateTransformElement
+/* newvar{svgvar00011:SVGFEConvolveMatrixElement} */ var svgvar00011 = document.getElementById("svgvar00011"); //SVGFEConvolveMatrixElement
+/* newvar{svgvar00012:SVGElement} */ var svgvar00012 = document.getElementById("svgvar00012"); //SVGElement
+/* newvar{htmlvar00013:HTMLMarqueeElement} */ var htmlvar00013 = document.getElementById("htmlvar00013"); //HTMLMarqueeElement
+/* newvar{htmlvar00014:HTMLUListElement} */ var htmlvar00014 = document.getElementById("htmlvar00014"); //HTMLUListElement
+/* newvar{htmlvar00015:HTMLLIElement} */ var htmlvar00015 = document.getElementById("htmlvar00015"); //HTMLLIElement
+/* newvar{htmlvar00016:HTMLBaseElement} */ var htmlvar00016 = document.getElementById("htmlvar00016"); //HTMLBaseElement
+/* newvar{htmlvar00017:HTMLOListElement} */ var htmlvar00017 = document.getElementById("htmlvar00017"); //HTMLOListElement
+/* newvar{htmlvar00018:HTMLLIElement} */ var htmlvar00018 = document.getElementById("htmlvar00018"); //HTMLLIElement
+/* newvar{htmlvar00019:HTMLDirectoryElement} */ var htmlvar00019 = document.getElementById("htmlvar00019"); //HTMLDirectoryElement
+/* newvar{htmlvar00020:HTMLShadowElement} */ var htmlvar00020 = document.getElementById("htmlvar00020"); //HTMLShadowElement
+/* newvar{htmlvar00021:HTMLUnknownElement} */ var htmlvar00021 = document.getElementById("htmlvar00021"); //HTMLUnknownElement
+/* newvar{htmlvar00022:HTMLLIElement} */ var htmlvar00022 = document.getElementById("htmlvar00022"); //HTMLLIElement
+/* newvar{htmlvar00023:HTMLDetailsElement} */ var htmlvar00023 = document.getElementById("htmlvar00023"); //HTMLDetailsElement
+/* newvar{htmlvar00024:HTMLVideoElement} */ var htmlvar00024 = document.getElementById("htmlvar00024"); //HTMLVideoElement
+/* newvar{htmlvar00025:HTMLTimeElement} */ var htmlvar00025 = document.getElementById("htmlvar00025"); //HTMLTimeElement
+/* newvar{htmlvar00026:HTMLUnknownElement} */ var htmlvar00026 = document.getElementById("htmlvar00026"); //HTMLUnknownElement
+/* newvar{htmlvar00027:HTMLTimeElement} */ var htmlvar00027 = document.getElementById("htmlvar00027"); //HTMLTimeElement
+/* newvar{htmlvar00028:HTMLUnknownElement} */ var htmlvar00028 = document.createElement("noembed"); //HTMLUnknownElement
+/* newvar{htmlvar00029:HTMLProgressElement} */ var htmlvar00029 = document.createElement("progress"); //HTMLProgressElement
+/* newvar{htmlvar00030:HTMLMapElement} */ var htmlvar00030 = document.createElement("map"); //HTMLMapElement
+/* newvar{htmlvar00031:HTMLTextAreaElement} */ var htmlvar00031 = document.createElement("textarea"); //HTMLTextAreaElement
+/* newvar{htmlvar00032:HTMLUnknownElement} */ var htmlvar00032 = document.createElement("html"); //HTMLUnknownElement
+try { /* newvar{var00003:HTMLKeygenElement} */ var var00003 = document.createElement("keygen"); } catch(e) { }
+try { if (!var00003) { var00003 = GetVariable(fuzzervars, 'HTMLKeygenElement'); } else { SetVariable(var00003, 'HTMLKeygenElement'); SetVariable(var00003, 'Element'); SetVariable(var00003, 'GlobalEventHandlers'); SetVariable(var00003, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00002:ValidityState} */ var var00002 = var00003.validity; } catch(e) { }
+try { if (!var00002) { var00002 = GetVariable(fuzzervars, 'ValidityState'); } else { SetVariable(var00002, 'ValidityState'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00001:boolean} */ var var00001 = var00002.badInput; } catch(e) { }
+try { /* newvar{var00004:Touch} */ var var00004 = document.createTouch(window,window,1,0.655032238225,0.639921455751); } catch(e) { }
+try { if (!var00004) { var00004 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00004, 'Touch'); } } catch(e) { }
+try { /* newvar{var00005:boolean} */ var var00005 = var00003.autofocus; } catch(e) { }
+try { /* newvar{var00006:ScrollToOptions} */ var var00006 = {left: 8, top: 0}; } catch(e) { }
+try { if (!var00006) { var00006 = GetVariable(fuzzervars, 'ScrollToOptions'); } else { SetVariable(var00006, 'ScrollToOptions'); } } catch(e) { }
+try { window.scroll(var00006); } catch(e) { }
+try { htmlvar00004.setAttribute("loopstart", "0"); } catch(e) { }
+try { /* newvar{var00007:DOMString} */ var var00007 = htmlvar00018.type; } catch(e) { }
+try { htmlvar00020.setAttribute("onstorage", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00008:NodeIterator} */ var var00008 = document.createNodeIterator(htmlvar00029); } catch(e) { }
+try { if (!var00008) { var00008 = GetVariable(fuzzervars, 'NodeIterator'); } else { SetVariable(var00008, 'NodeIterator'); } } catch(e) { }
+try { svgvar00007.setAttribute("kernelMatrix", "-1 57 1 -1 0 1 9 29 -1"); } catch(e) { }
+try { /* newvar{var00009:CSSStyleDeclaration} */ var var00009 = htmlvar00013.style; } catch(e) { }
+try { if (!var00009) { var00009 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00009, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00009.setProperty("-webkit-border-before", "4px solid"); } catch(e) { }
+try { /* newvar{var00010:DOMString} */ var var00010 = htmlvar00005.inputMode; } catch(e) { }
+try { htmlvar00029.setAttribute("onmouseleave", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00011:DOMString} */ var var00011 = htmlvar00012.download; } catch(e) { }
+try { var00009.setProperty("mso-pagination", "widow-orphan"); } catch(e) { }
+try { /* newvar{var00012:Element} */ var var00012 = document.importNode(htmlvar00008); } catch(e) { }
+try { if (!var00012) { var00012 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00012, 'Element'); SetVariable(var00012, 'GlobalEventHandlers'); SetVariable(var00012, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00013:Attr} */ var var00013 = document.createAttributeNS("http://www.w3.org/1999/xhtml","framespacing"); } catch(e) { }
+try { if (!var00013) { var00013 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00013, 'Attr'); } } catch(e) { }
+try { /* newvar{var00014:Text} */ var var00014 = document.createTextNode(String.fromCodePoint(782712, 1030839, 249191, 788383, 459434, 132606, 789043, 199443, 238375, 770765, 376879, 394555, 1046427, 211903, 127921, 496491, 196003, 699576, 688994, 53607)); } catch(e) { }
+try { if (!var00014) { var00014 = GetVariable(fuzzervars, 'Text'); } else { SetVariable(var00014, 'Text'); SetVariable(var00014, 'CharacterData'); SetVariable(var00014, 'Element'); SetVariable(var00014, 'GlobalEventHandlers'); SetVariable(var00014, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00015:EventHandler} */ var var00015 = svgvar00012.oncopy; } catch(e) { }
+try { if (!var00015) { var00015 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00015, 'EventHandler'); } } catch(e) { }
+try { document.onfullscreenchange = var00015; } catch(e) { }
+try { /* newvar{var00016:Element} */ var var00016 = document.webkitFullscreenElement; } catch(e) { }
+try { if (!var00016) { var00016 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00016, 'Element'); SetVariable(var00016, 'GlobalEventHandlers'); SetVariable(var00016, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00017:EventHandler} */ var var00017 = window.onsearch; } catch(e) { }
+try { if (!var00017) { var00017 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00017, 'EventHandler'); } } catch(e) { }
+try { svgvar00010.setAttribute("viewBox", "3 1 0 1"); } catch(e) { }
+try { var00009.setProperty("widows", "15"); } catch(e) { }
+try { htmlvar00005.size = 0; } catch(e) { }
+try { var00009.setProperty("-webkit-transform", "translate(8px, 69px) scale(10)"); } catch(e) { }
+try { htmlvar00001.load(); } catch(e) { }
+try { var00009.setProperty("list-style-position", "inside"); } catch(e) { }
+try { /* newvar{var00019:VTTCue} */ var var00019 = new VTTCue(0.505921455919, 0.82964360557, "1"); } catch(e) { }
+try { if (!var00019) { var00019 = GetVariable(fuzzervars, 'VTTCue'); } else { SetVariable(var00019, 'VTTCue'); SetVariable(var00019, 'TextTrackCue'); SetVariable(var00019, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00018:TextTrackCue} */ var var00018 = var00019; } catch(e) { }
+try { if (!var00018) { var00018 = GetVariable(fuzzervars, 'TextTrackCue'); } else { SetVariable(var00018, 'TextTrackCue'); SetVariable(var00018, 'EventTarget'); } } catch(e) { }
+try { var00018.onenter = var00017; } catch(e) { }
+try { htmlvar00013.loop = 0; } catch(e) { }
+try { var00009.setProperty("-webkit-mask-box-image-outset", "0px"); } catch(e) { }
+try { var00009.setProperty("color", "rgb(30,204,116)"); } catch(e) { }
+try { /* newvar{var00020:VideoTrackList} */ var var00020 = htmlvar00001.videoTracks; } catch(e) { }
+try { if (!var00020) { var00020 = GetVariable(fuzzervars, 'VideoTrackList'); } else { SetVariable(var00020, 'VideoTrackList'); SetVariable(var00020, 'EventTarget'); } } catch(e) { }
+try { var00020.onchange = var00017; } catch(e) { }
+try { htmlvar00001.setAttribute("onbeforepaste", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00021:short} */ var var00021 = htmlvar00002.readyState; } catch(e) { }
+try { svgvar00007.setAttribute("mask-type", "alpha"); } catch(e) { }
+try { htmlvar00003.encoding = "TEXT/HTML"; } catch(e) { }
+try { /* newvar{var00022:KeyboardEvent} */ var var00022 = document.createEvent("KeyboardEvents"); } catch(e) { }
+try { if (!var00022) { var00022 = GetVariable(fuzzervars, 'KeyboardEvent'); } else { SetVariable(var00022, 'KeyboardEvent'); SetVariable(var00022, 'UIEvent'); SetVariable(var00022, 'Event'); } } catch(e) { }
+try { var00022.initKeyboardEvent("htmlvar00005",true,true,window,"htmlvar00003",57,false); } catch(e) { }
+try { /* newvar{var00023:DOMString} */ var var00023 = var00009.cssFloat; } catch(e) { }
+try { /* newvar{var00024:HTMLFrameSetElement} */ var var00024 = document.createElement("frameset"); } catch(e) { }
+try { if (!var00024) { var00024 = GetVariable(fuzzervars, 'HTMLFrameSetElement'); } else { SetVariable(var00024, 'HTMLFrameSetElement'); SetVariable(var00024, 'WindowEventHandlers'); SetVariable(var00024, 'Element'); SetVariable(var00024, 'GlobalEventHandlers'); SetVariable(var00024, 'EventTarget'); } } catch(e) { }
+try { var00024.onblur = var00017; } catch(e) { }
+try { /* newvar{var00025:USVString} */ var var00025 = htmlvar00012.host; } catch(e) { }
+try { if (!var00025) { var00025 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00025, 'USVString'); } } catch(e) { }
+try { htmlvar00029.setAttribute("onautocompleteerror", "eventhandler4()"); } catch(e) { }
+try { /* newvar{var00026:Element} */ var var00026 = htmlvar00029; } catch(e) { }
+try { if (!var00026) { var00026 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00026, 'Element'); SetVariable(var00026, 'GlobalEventHandlers'); SetVariable(var00026, 'EventTarget'); } } catch(e) { }
+try { var00009.setProperty("-webkit-locale", "'zh_CN'"); } catch(e) { }
+try { /* newvar{var00027:HTMLObjectElement} */ var var00027 = document.createElement("object"); } catch(e) { }
+try { if (!var00027) { var00027 = GetVariable(fuzzervars, 'HTMLObjectElement'); } else { SetVariable(var00027, 'HTMLObjectElement'); SetVariable(var00027, 'Element'); SetVariable(var00027, 'GlobalEventHandlers'); SetVariable(var00027, 'EventTarget'); } } catch(e) { }
+try { var00027.archive = "" + String.fromCharCode(73, 40, 98, 50, 92, 64, 67, 79, 53, 41, 42, 125, 88, 74, 122, 86, 71, 118, 46, 71) + ""; } catch(e) { }
+try { var00009.setProperty("padding-right", "0vmin"); } catch(e) { }
+try { /* newvar{var00029:CustomElementRegistry} */ var var00029 = window.customElements; } catch(e) { }
+try { if (!var00029) { var00029 = GetVariable(fuzzervars, 'CustomElementRegistry'); } else { SetVariable(var00029, 'CustomElementRegistry'); } } catch(e) { }
+try { /* newvar{var00028:any} */ var var00028 = var00029.get(String.fromCharCode(64, 88, 61, 52, 44, 68, 83, 47, 52, 103, 73, 43, 41, 32, 108, 66, 76, 44, 41, 126)); } catch(e) { }
+try { if (!var00028) { var00028 = GetVariable(fuzzervars, 'any'); } else { SetVariable(var00028, 'any'); } } catch(e) { }
+try { window.postMessage(var00028,"foo"); } catch(e) { }
+try { var00014.setAttribute("onwebkitkeyadded", "eventhandler3()"); } catch(e) { }
+try { /* newvar{var00044:CSSRule} */ var var00044 = var00009.parentRule; } catch(e) { }
+try { if (!var00044) { var00044 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00044, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00043:CSSStyleSheet} */ var var00043 = var00044.parentStyleSheet; } catch(e) { }
+try { if (!var00043) { var00043 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00043, 'CSSStyleSheet'); SetVariable(var00043, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00042:CSSRule} */ var var00042 = var00043.ownerRule; } catch(e) { }
+try { if (!var00042) { var00042 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00042, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00041:CSSKeyframesRule} */ var var00041 = var00042; } catch(e) { }
+try { if (!var00041) { var00041 = GetVariable(fuzzervars, 'CSSKeyframesRule'); } else { SetVariable(var00041, 'CSSKeyframesRule'); SetVariable(var00041, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00040:CSSRule} */ var var00040 = var00041; } catch(e) { }
+try { if (!var00040) { var00040 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00040, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00039:CSSImportRule} */ var var00039 = var00040; } catch(e) { }
+try { if (!var00039) { var00039 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00039, 'CSSImportRule'); SetVariable(var00039, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00038:CSSRule} */ var var00038 = var00039; } catch(e) { }
+try { if (!var00038) { var00038 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00038, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00037:CSSImportRule} */ var var00037 = var00038; } catch(e) { }
+try { if (!var00037) { var00037 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00037, 'CSSImportRule'); SetVariable(var00037, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00036:CSSRule} */ var var00036 = var00037; } catch(e) { }
+try { if (!var00036) { var00036 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00036, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00035:CSSViewportRule} */ var var00035 = var00036; } catch(e) { }
+try { if (!var00035) { var00035 = GetVariable(fuzzervars, 'CSSViewportRule'); } else { SetVariable(var00035, 'CSSViewportRule'); SetVariable(var00035, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00034:CSSRule} */ var var00034 = var00035; } catch(e) { }
+try { if (!var00034) { var00034 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00034, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00033:CSSViewportRule} */ var var00033 = var00034; } catch(e) { }
+try { if (!var00033) { var00033 = GetVariable(fuzzervars, 'CSSViewportRule'); } else { SetVariable(var00033, 'CSSViewportRule'); SetVariable(var00033, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00032:CSSRule} */ var var00032 = var00033; } catch(e) { }
+try { if (!var00032) { var00032 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00032, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00031:CSSViewportRule} */ var var00031 = var00032; } catch(e) { }
+try { if (!var00031) { var00031 = GetVariable(fuzzervars, 'CSSViewportRule'); } else { SetVariable(var00031, 'CSSViewportRule'); SetVariable(var00031, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00030:CSSStyleDeclaration} */ var var00030 = var00031.style; } catch(e) { }
+try { if (!var00030) { var00030 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00030, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00030.setProperty("-webkit-text-security", "none"); } catch(e) { }
+try { /* newvar{var00045:eventhandler} */ var var00045 = eventhandler1; } catch(e) { }
+try { if (!var00045) { var00045 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00045, 'eventhandler'); } } catch(e) { }
+try { var00026.setAttribute("formaction", "var00045"); } catch(e) { }
+try { /* newvar{var00046:DOMString} */ var var00046 = var00043.title; } catch(e) { }
+try { /* newvar{var00047:boolean} */ var var00047 = document.webkitHidden; } catch(e) { }
+try { /* newvar{var00048:DOMString} */ var var00048 = htmlvar00012.coords; } catch(e) { }
+try { /* newvar{var00049:long} */ var var00049 = var00022.location; } catch(e) { }
+try { /* newvar{var00050:EventHandler} */ var var00050 = htmlvar00032.onauxclick; } catch(e) { }
+try { if (!var00050) { var00050 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00050, 'EventHandler'); } } catch(e) { }
+try { var00009.setProperty("min-width", "1ch"); } catch(e) { }
+try { /* newvar{var00051:SVGRect} */ var var00051 = svgvar00001.createSVGRect(); } catch(e) { }
+try { if (!var00051) { var00051 = GetVariable(fuzzervars, 'SVGRect'); } else { SetVariable(var00051, 'SVGRect'); } } catch(e) { }
+try { /* newvar{var00053:SVGMarkerElement} */ var var00053 = document.createElementNS("http://www.w3.org/2000/svg", "marker"); } catch(e) { }
+try { if (!var00053) { var00053 = GetVariable(fuzzervars, 'SVGMarkerElement'); } else { SetVariable(var00053, 'SVGMarkerElement'); SetVariable(var00053, 'SVGFitToViewBox'); SetVariable(var00053, 'SVGElement'); SetVariable(var00053, 'GlobalEventHandlers'); SetVariable(var00053, 'EventTarget'); SetVariable(var00053, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00052:svg_url_marker} */ var var00052 = "url(#" + var00053.id + ")"; } catch(e) { }
+try { if (!var00052) { var00052 = GetVariable(fuzzervars, 'svg_url_marker'); } else { SetVariable(var00052, 'svg_url_marker'); } } catch(e) { }
+try { svgvar00007.setAttribute("marker-mid", var00052); } catch(e) { }
+try { svgvar00008.ontouchmove = var00017; } catch(e) { }
+try { svgvar00005.setAttribute("path", "M 7 0 l 0 1"); } catch(e) { }
+try { /* newvar{var00054:boolean} */ var var00054 = htmlvar00022.hasAttribute("aria-labeledby"); } catch(e) { }
+try { /* newvar{var00055:USVString} */ var var00055 = htmlvar00012.protocol; } catch(e) { }
+try { if (!var00055) { var00055 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00055, 'USVString'); } } catch(e) { }
+try { htmlvar00032.webkitRequestFullscreen(); } catch(e) { }
+try { /* newvar{var00056:ApplicationCache} */ var var00056 = window.applicationCache; } catch(e) { }
+try { if (!var00056) { var00056 = GetVariable(fuzzervars, 'ApplicationCache'); } else { SetVariable(var00056, 'ApplicationCache'); SetVariable(var00056, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00057:EventHandler} */ var var00057 = document.oncopy; } catch(e) { }
+try { if (!var00057) { var00057 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00057, 'EventHandler'); } } catch(e) { }
+try { var00056.onnoupdate = var00057; } catch(e) { }
+try { /* newvar{var00058:Selection} */ var var00058 = window.getSelection(); } catch(e) { }
+try { if (!var00058) { var00058 = GetVariable(fuzzervars, 'Selection'); } else { SetVariable(var00058, 'Selection'); } } catch(e) { }
+try { var00058.collapse(var00016); } catch(e) { }
+try { htmlvar00031.id = "htmlvar00001"; } catch(e) { }
+try { /* newvar{var00059:EventHandler} */ var var00059 = htmlvar00020.onsuspend; } catch(e) { }
+try { if (!var00059) { var00059 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00059, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00060:SVGGraphicsElement} */ var var00060 = svgvar00003; } catch(e) { }
+try { if (!var00060) { var00060 = GetVariable(fuzzervars, 'SVGGraphicsElement'); } else { SetVariable(var00060, 'SVGGraphicsElement'); SetVariable(var00060, 'SVGElement'); SetVariable(var00060, 'GlobalEventHandlers'); SetVariable(var00060, 'EventTarget'); SetVariable(var00060, 'GlobalEventHandlers'); } } catch(e) { }
+try { var00009.setProperty("alignx", "left"); } catch(e) { }
+try { svgvar00006.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:id", "test-title"); } catch(e) { }
+try { /* newvar{var00061:EventHandler} */ var var00061 = svgvar00003.onchange; } catch(e) { }
+try { if (!var00061) { var00061 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00061, 'EventHandler'); } } catch(e) { }
+try { var00024.onunload = var00061; } catch(e) { }
+try { window.scroll(0.465772149991,0.385777579714); } catch(e) { }
+try { var00053.setAttribute("preserveAlpha", "false"); } catch(e) { }
+try { htmlvar00014.setAttribute("onload", "eventhandler5()"); } catch(e) { }
+try { /* newvar{var00062:boolean} */ var var00062 = htmlvar00015.hasPointerCapture(1); } catch(e) { }
+try { /* newvar{var00063:double} */ var var00063 = window.scrollX; } catch(e) { }
+try { /* newvar{var00064:DOMTokenList} */ var var00064 = var00027.classList; } catch(e) { }
+try { if (!var00064) { var00064 = GetVariable(fuzzervars, 'DOMTokenList'); } else { SetVariable(var00064, 'DOMTokenList'); } } catch(e) { }
+try { /* newvar{var00065:SVGAnimatedLength} */ var var00065 = svgvar00002.y; } catch(e) { }
+try { if (!var00065) { var00065 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00065, 'SVGAnimatedLength'); } } catch(e) { }
+try { htmlvar00029.setAttribute("azimuth", "0"); } catch(e) { }
+try { var00030.setProperty("-webkit-column-width", "81px"); } catch(e) { }
+try { htmlvar00031.setAttribute("aria-expanded", "true"); } catch(e) { }
+try { /* newvar{var00066:boolean} */ var var00066 = var00002.patternMismatch; } catch(e) { }
+try { htmlvar00003.setAttribute("onbeforepaste", "eventhandler1()"); } catch(e) { }
+try { htmlvar00005.inputMode = "latin-name"; } catch(e) { }
+try { document.all[17%document.all.length].appendChild(htmlvar00022); } catch(e) { }
+try { var00030.setProperty("border-style", "solid none solid solid"); } catch(e) { }
+try { svgvar00012.setAttribute("markerUnits", "strokeWidth"); } catch(e) { }
+try { /* newvar{var00067:DOMString} */ var var00067 = var00027.archive; } catch(e) { }
+try { svgvar00001.deselectAll(); } catch(e) { }
+try { /* newvar{var00069:HTMLDListElement} */ var var00069 = document.createElement("dl"); } catch(e) { }
+try { if (!var00069) { var00069 = GetVariable(fuzzervars, 'HTMLDListElement'); } else { SetVariable(var00069, 'HTMLDListElement'); SetVariable(var00069, 'Element'); SetVariable(var00069, 'GlobalEventHandlers'); SetVariable(var00069, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00068:boolean} */ var var00068 = var00069.compact; } catch(e) { }
+try { /* newvar{var00070:boolean} */ var var00070 = var00013.specified; } catch(e) { }
+try { htmlvar00003.setAttribute("right", "1"); } catch(e) { }
+try { htmlvar00009.name = "" + String.fromCharCode(33, 96, 81, 80, 78, 118, 110, 51, 36, 94, 121, 47, 48, 83, 61, 118, 39, 103, 86, 92) + ""; } catch(e) { }
+try { /* newvar{var00071:DOMString} */ var var00071 = htmlvar00007.integrity; } catch(e) { }
+try { /* newvar{var00072:DOMString} */ var var00072 = htmlvar00031.lookupNamespaceURI("1"); } catch(e) { }
+try { var00030.setProperty("src", "url(#svgvar00004)"); } catch(e) { }
+try { /* newvar{var00073:CSSStyleDeclaration} */ var var00073 = htmlvar00025.style; } catch(e) { }
+try { if (!var00073) { var00073 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00073, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00073.setProperty("background-position-x", "44%"); } catch(e) { }
+try { /* newvar{var00074:EventHandler} */ var var00074 = var00024.onoffline; } catch(e) { }
+try { if (!var00074) { var00074 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00074, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00075:NamedNodeMap} */ var var00075 = svgvar00003.attributes; } catch(e) { }
+try { if (!var00075) { var00075 = GetVariable(fuzzervars, 'NamedNodeMap'); } else { SetVariable(var00075, 'NamedNodeMap'); } } catch(e) { }
+try { /* newvar{var00076:long} */ var var00076 = window.outerHeight; } catch(e) { }
+try { htmlvar00005.selectionDirection = "htmlvar00009"; } catch(e) { }
+try { /* newvar{var00077:long} */ var var00077 = htmlvar00017.start; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { htmlvar00005.minLength = 33; } catch(e) { }
+try { var00030.setProperty("weight", "*"); } catch(e) { }
+try { /* newvar{var00078:EventHandler} */ var var00078 = var00012.onwebkitfullscreenerror; } catch(e) { }
+try { if (!var00078) { var00078 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00078, 'EventHandler'); } } catch(e) { }
+try { var00073.setProperty("-webkit-mask-repeat", "space space"); } catch(e) { }
+try { /* newvar{var00079:SVGAnimatedLengthList} */ var var00079 = svgvar00007.y; } catch(e) { }
+try { if (!var00079) { var00079 = GetVariable(fuzzervars, 'SVGAnimatedLengthList'); } else { SetVariable(var00079, 'SVGAnimatedLengthList'); } } catch(e) { }
+try { htmlvar00003.onerror = var00015; } catch(e) { }
+try { htmlvar00031.setAttribute("bgcolor", ""); } catch(e) { }
+try { /* newvar{var00080:htmlstring} */ var var00080 = htmlvar00006.outerHTML; } catch(e) { }
+try { if (!var00080) { var00080 = GetVariable(fuzzervars, 'htmlstring'); } else { SetVariable(var00080, 'htmlstring'); } } catch(e) { }
+try { var00053.outerHTML = var00080; } catch(e) { }
+try { htmlvar00005.formNoValidate = true; } catch(e) { }
+try { var00009.setProperty("animation-name", "anim"); } catch(e) { }
+try { /* newvar{var00081:TreeWalker} */ var var00081 = document.createTreeWalker(htmlvar00031,0); } catch(e) { }
+try { if (!var00081) { var00081 = GetVariable(fuzzervars, 'TreeWalker'); } else { SetVariable(var00081, 'TreeWalker'); } } catch(e) { }
+try { var00081.currentNode = htmlvar00023; } catch(e) { }
+try { svgvar00005.setAttribute("fy", "43%"); } catch(e) { }
+try { var00073.setProperty("-webkit-animation-duration", "0s"); } catch(e) { }
+try { /* newvar{var00082:DOMString} */ var var00082 = var00013.value; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00083:double} */ var var00083 = var00019.endTime; } catch(e) { }
+try { document.oncopy = var00061; } catch(e) { }
+try { var00056.onnoupdate = var00050; } catch(e) { }
+try { /* newvar{var00084:EventHandler} */ var var00084 = var00053.oncopy; } catch(e) { }
+try { if (!var00084) { var00084 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00084, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00085:EventHandler} */ var var00085 = svgvar00004.ondblclick; } catch(e) { }
+try { if (!var00085) { var00085 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00085, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00086:boolean} */ var var00086 = htmlvar00003.checkValidity(); } catch(e) { }
+try { /* newvar{var00088:Selection} */ var var00088 = window.getSelection(); } catch(e) { }
+try { if (!var00088) { var00088 = GetVariable(fuzzervars, 'Selection'); } else { SetVariable(var00088, 'Selection'); } } catch(e) { }
+try { /* newvar{var00087:long} */ var var00087 = var00088.focusOffset; } catch(e) { }
+try { htmlvar00004.onstalled = var00015; } catch(e) { }
+try { var00073.setProperty("perspective", "1"); } catch(e) { }
+try { svgvar00010.setAttribute("y", "0px"); } catch(e) { }
+try { /* newvar{var00089:DOMString} */ var var00089 = document.characterSet; } catch(e) { }
+try { htmlvar00003.acceptCharset = String.fromCodePoint(488183, 749313, 968486, 919462, 788009, 991971, 2666, 474124, 1043244, 946704, 887400, 1087495, 196560, 585411, 135404, 342602, 267116, 27108, 214344, 712611); } catch(e) { }
+try { var00009.setProperty("-webkit-border-end-color", "black"); } catch(e) { }
+try { var00024.setAttribute("aria-setsize", "0"); } catch(e) { }
+try { /* newvar{var00090:TextTrackList} */ var var00090 = htmlvar00024.textTracks; } catch(e) { }
+try { if (!var00090) { var00090 = GetVariable(fuzzervars, 'TextTrackList'); } else { SetVariable(var00090, 'TextTrackList'); SetVariable(var00090, 'EventTarget'); } } catch(e) { }
+try { var00090.onchange = var00084; } catch(e) { }
+try { var00003.challenge = String.fromCharCode(99, 85, 40, 65, 125, 70, 44, 81, 125, 101, 123, 117, 50, 40, 118, 91, 61, 99, 104, 75); } catch(e) { }
+try { /* newvar{var00091:AnimationEventConstructor} */ var var00091 = window.WebKitAnimationEvent; } catch(e) { }
+try { if (!var00091) { var00091 = GetVariable(fuzzervars, 'AnimationEventConstructor'); } else { SetVariable(var00091, 'AnimationEventConstructor'); } } catch(e) { }
+try { var00030.setProperty("motion-path", "none"); } catch(e) { }
+try { var00073.setProperty("mso-style-next", "Normal"); } catch(e) { }
+try { svgvar00012.setAttribute("stroke-linecap", "round"); } catch(e) { }
+try { htmlvar00005.setSelectionRange(); } catch(e) { }
+try { svgvar00010.setAttribute("fx", "63%"); } catch(e) { }
+try { /* newvar{var00092:AudioTrackList} */ var var00092 = htmlvar00024.audioTracks; } catch(e) { }
+try { if (!var00092) { var00092 = GetVariable(fuzzervars, 'AudioTrackList'); } else { SetVariable(var00092, 'AudioTrackList'); SetVariable(var00092, 'EventTarget'); } } catch(e) { }
+try { var00092.onaddtrack = var00074; } catch(e) { }
+try { var00073.setProperty("border-color", "black"); } catch(e) { }
+try { var00030.setProperty("background-repeat", "round repeat"); } catch(e) { }
+try { var00012.onpause = var00085; } catch(e) { }
+try { /* newvar{var00093:DOMString} */ var var00093 = var00027.border; } catch(e) { }
+try { var00009.setProperty("-webkit-border-end-color", ""); } catch(e) { }
+try { var00073.setProperty("-webkit-border-start", "1px solid purple"); } catch(e) { }
+try { /* newvar{var00095:SVGMaskElement} */ var var00095 = document.createElementNS("http://www.w3.org/2000/svg", "mask"); } catch(e) { }
+try { if (!var00095) { var00095 = GetVariable(fuzzervars, 'SVGMaskElement'); } else { SetVariable(var00095, 'SVGMaskElement'); SetVariable(var00095, 'SVGTests'); SetVariable(var00095, 'SVGElement'); SetVariable(var00095, 'GlobalEventHandlers'); SetVariable(var00095, 'EventTarget'); SetVariable(var00095, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00094:svg_url_mask} */ var var00094 = "url(#" + var00095.id + ")"; } catch(e) { }
+try { if (!var00094) { var00094 = GetVariable(fuzzervars, 'svg_url_mask'); } else { SetVariable(var00094, 'svg_url_mask'); } } catch(e) { }
+try { var00030.setProperty("-webkit-transform-origin", "center top"); } catch(e) { }
+try { document.all[64%document.all.length].appendChild(htmlvar00002); } catch(e) { }
+try { svgvar00008.scrollIntoViewIfNeeded(false); } catch(e) { }
+try { htmlvar00007.setAttribute("right", "0"); } catch(e) { }
+try { /* newvar{var00096:boolean} */ var var00096 = htmlvar00017.compact; } catch(e) { }
+try { /* newvar{var00097:Window} */ var var00097 = var00024["htmlvar00004"]; } catch(e) { }
+try { if (!var00097) { var00097 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00097, 'Window'); SetVariable(var00097, 'GlobalEventHandlers'); SetVariable(var00097, 'WindowBase64'); SetVariable(var00097, 'WindowEventHandlers'); SetVariable(var00097, 'WindowTimers'); SetVariable(var00097, 'EventTarget'); } } catch(e) { }
+try { var00009.setProperty("pointer-events", "all"); } catch(e) { }
+try { var00088.setPosition(htmlvar00020,0); } catch(e) { }
+try { /* newvar{var00098:boolean} */ var var00098 = document.execCommand("styleWithCSS", false, false); } catch(e) { }
+try { /* newvar{var00099:Element} */ var var00099 = htmlvar00023; } catch(e) { }
+try { if (!var00099) { var00099 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00099, 'Element'); SetVariable(var00099, 'GlobalEventHandlers'); SetVariable(var00099, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00100:EventHandler} */ var var00100 = htmlvar00021.onbeforecut; } catch(e) { }
+try { if (!var00100) { var00100 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00100, 'EventHandler'); } } catch(e) { }
+try { htmlvar00008.setAttribute("valign", "center"); } catch(e) { }
+try { /* newvar{var00101:Element} */ var var00101 = var00088.focusNode; } catch(e) { }
+try { if (!var00101) { var00101 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00101, 'Element'); SetVariable(var00101, 'GlobalEventHandlers'); SetVariable(var00101, 'EventTarget'); } } catch(e) { }
+try { svgvar00003.append("foo"); } catch(e) { }
+try { htmlvar00012.setAttribute("height", "-1"); } catch(e) { }
+try { var00024.onresize = var00059; } catch(e) { }
+try { /* newvar{var00102:DOMString} */ var var00102 = var00003.localName; } catch(e) { }
+try { var00009.setProperty("shape-inside", "polygon()"); } catch(e) { }
+try { htmlvar00013.stop(); } catch(e) { }
+try { /* newvar{var00103:ScrollStateCallback} */ var var00103 = var00045; } catch(e) { }
+try { if (!var00103) { var00103 = GetVariable(fuzzervars, 'ScrollStateCallback'); } else { SetVariable(var00103, 'ScrollStateCallback'); } } catch(e) { }
+try { /* newvar{var00104:NativeScrollBehavior} */ var var00104 = "disable-native-scroll"; } catch(e) { }
+try { if (!var00104) { var00104 = GetVariable(fuzzervars, 'NativeScrollBehavior'); } else { SetVariable(var00104, 'NativeScrollBehavior'); } } catch(e) { }
+try { htmlvar00009.setApplyScroll(var00103,var00104); } catch(e) { }
+try { /* newvar{var00105:Element} */ var var00105 = var00012.closest(String.fromCodePoint(769701, 645555, 252891, 959877, 556258, 446522, 98113, 247707, 738158, 751198, 490902, 597194, 103934, 654774, 509042, 1099835, 222101, 1023168, 859013, 1087868)); } catch(e) { }
+try { if (!var00105) { var00105 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00105, 'Element'); SetVariable(var00105, 'GlobalEventHandlers'); SetVariable(var00105, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00106:Element} */ var var00106 = var00105.cloneNode(); } catch(e) { }
+try { if (!var00106) { var00106 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00106, 'Element'); SetVariable(var00106, 'GlobalEventHandlers'); SetVariable(var00106, 'EventTarget'); } } catch(e) { }
+try { var00027.setAttribute("startval", "" + String.fromCharCode(38, 51, 100, 100, 125, 58, 98, 71, 49, 115, 77, 78, 86, 101, 83, 90, 51, 101, 68, 47) + ""); } catch(e) { }
+try { /* newvar{var00107:long} */ var var00107 = var00092.length; } catch(e) { }
+try { /* newvar{var00108:BarProp} */ var var00108 = window.statusbar; } catch(e) { }
+try { if (!var00108) { var00108 = GetVariable(fuzzervars, 'BarProp'); } else { SetVariable(var00108, 'BarProp'); } } catch(e) { }
+try { /* newvar{var00112:SVGPatternElement} */ var var00112 = document.createElementNS("http://www.w3.org/2000/svg", "pattern"); } catch(e) { }
+try { if (!var00112) { var00112 = GetVariable(fuzzervars, 'SVGPatternElement'); } else { SetVariable(var00112, 'SVGPatternElement'); SetVariable(var00112, 'SVGFitToViewBox'); SetVariable(var00112, 'SVGURIReference'); SetVariable(var00112, 'SVGTests'); SetVariable(var00112, 'SVGElement'); SetVariable(var00112, 'GlobalEventHandlers'); SetVariable(var00112, 'EventTarget'); SetVariable(var00112, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00111:SVGAnimatedLength} */ var var00111 = var00112.width; } catch(e) { }
+try { if (!var00111) { var00111 = GetVariable(fuzzervars, 'SVGAnimatedLength'); } else { SetVariable(var00111, 'SVGAnimatedLength'); } } catch(e) { }
+try { /* newvar{var00110:SVGLength} */ var var00110 = var00111.animVal; } catch(e) { }
+try { if (!var00110) { var00110 = GetVariable(fuzzervars, 'SVGLength'); } else { SetVariable(var00110, 'SVGLength'); } } catch(e) { }
+try { /* newvar{var00109:float} */ var var00109 = var00110.value; } catch(e) { }
+try { var00112.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:title", "foo"); } catch(e) { }
+try { /* newvar{var00113:DocumentTimeline} */ var var00113 = document.timeline; } catch(e) { }
+try { if (!var00113) { var00113 = GetVariable(fuzzervars, 'DocumentTimeline'); } else { SetVariable(var00113, 'DocumentTimeline'); SetVariable(var00113, 'AnimationTimeline'); } } catch(e) { }
+try { var00022.initUIEvent("1"); } catch(e) { }
+try { var00030.setProperty("animation-direction", "inherit"); } catch(e) { }
+try { var00016.setAttribute("declare", "declare"); } catch(e) { }
+try { /* newvar{var00114:boolean} */ var var00114 = htmlvar00012.matches("htmlvar00005"); } catch(e) { }
+try { /* newvar{var00116:eventhandler} */ var var00116 = eventhandler3; } catch(e) { }
+try { if (!var00116) { var00116 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00116, 'eventhandler'); } } catch(e) { }
+try { /* newvar{var00115:Function} */ var var00115 = var00116; } catch(e) { }
+try { if (!var00115) { var00115 = GetVariable(fuzzervars, 'Function'); } else { SetVariable(var00115, 'Function'); } } catch(e) { }
+try { /* newvar{var00117:ElementRegistrationOptions} */ var var00117 = {prototype: htmlvar00015.prototype, extends: "sup"}; } catch(e) { }
+try { if (!var00117) { var00117 = GetVariable(fuzzervars, 'ElementRegistrationOptions'); } else { SetVariable(var00117, 'ElementRegistrationOptions'); } } catch(e) { }
+try { var00029.define("1",var00115,var00117); } catch(e) { }
+try { var00009.setProperty("offset-position", "auto"); } catch(e) { }
+try { htmlvar00005.dirName = "" + String.fromCharCode(60, 98, 107, 78, 124, 93, 59, 38, 122, 126, 122, 45, 85, 106, 87, 98, 52, 98, 54, 120) + ""; } catch(e) { }
+try { /* newvar{var00118:Element} */ var var00118 = htmlvar00023.replaceChild(var00026,htmlvar00023.childNodes[81%htmlvar00023.childNodes.length]); } catch(e) { }
+try { if (!var00118) { var00118 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00118, 'Element'); SetVariable(var00118, 'GlobalEventHandlers'); SetVariable(var00118, 'EventTarget'); } } catch(e) { }
+try { htmlvar00012.port = var00025; } catch(e) { }
+try { var00092.onchange = var00057; } catch(e) { }
+try { htmlvar00005.stepUp(); } catch(e) { }
+try { /* newvar{var00119:double} */ var var00119 = window.pageYOffset; } catch(e) { }
+try { var00022.initUIEvent("htmlvar00007",false,true); } catch(e) { }
+try { htmlvar00003.oncancel = var00078; } catch(e) { }
+try { htmlvar00001.load(); } catch(e) { }
+try { /* newvar{var00120:HTMLSelectElement} */ var var00120 = document.createElement("select"); } catch(e) { }
+try { if (!var00120) { var00120 = GetVariable(fuzzervars, 'HTMLSelectElement'); } else { SetVariable(var00120, 'HTMLSelectElement'); SetVariable(var00120, 'Element'); SetVariable(var00120, 'GlobalEventHandlers'); SetVariable(var00120, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00121:HTMLOptionElement} */ var var00121 = var00120.item(94%var00120.length); } catch(e) { }
+try { if (!var00121) { var00121 = GetVariable(fuzzervars, 'HTMLOptionElement'); } else { SetVariable(var00121, 'HTMLOptionElement'); SetVariable(var00121, 'Element'); SetVariable(var00121, 'GlobalEventHandlers'); SetVariable(var00121, 'EventTarget'); } } catch(e) { }
+try { var00120.add(var00121,htmlvar00014); } catch(e) { }
+try { var00009.setProperty("scroll-snap-type", "none"); } catch(e) { }
+try { var00016.setAttribute("onprogress", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00122:MouseEvent} */ var var00122 = document.createEvent("MouseEvents"); } catch(e) { }
+try { if (!var00122) { var00122 = GetVariable(fuzzervars, 'MouseEvent'); } else { SetVariable(var00122, 'MouseEvent'); SetVariable(var00122, 'UIEvent'); SetVariable(var00122, 'Event'); } } catch(e) { }
+try { var00122.initMouseEvent(String.fromCharCode(126, 50, 53, 122, 48, 71, 60, 118, 119, 81, 67, 45, 118, 79, 66, 38, 121, 58, 83, 74),false,false,var00097,-1,0,1,-1); } catch(e) { }
+try { /* newvar{var00123:MessageEvent} */ var var00123 = document.createEvent("MessageEvent"); } catch(e) { }
+try { if (!var00123) { var00123 = GetVariable(fuzzervars, 'MessageEvent'); } else { SetVariable(var00123, 'MessageEvent'); SetVariable(var00123, 'Event'); } } catch(e) { }
+try { var00123.initMessageEvent("1",true,true,var00028,String.fromCodePoint(31393, 183089, 546537, 564266, 389361, 282213, 384163, 431658, 199423, 541176, 197850, 11564, 858531, 485944, 91281, 522975, 618076, 276876, 45468, 1056619),String.fromCodePoint(938508, 237878, 80030, 1004804, 564797, 565406, 183736, 538385, 603984, 783493, 1013746, 1008946, 765521, 754330, 92477, 803263, 656691, 116281, 38515, 793439),window); } catch(e) { }
+try { var00095.setAttribute("requiredExtensions", "x"); } catch(e) { }
+try { var00022.initKeyboardEvent("foo",false); } catch(e) { }
+try { /* newvar{var00124:long} */ var var00124 = htmlvar00001.webkitAudioDecodedByteCount; } catch(e) { }
+try { /* newvar{var00125:SVGGraphicsElement} */ var var00125 = svgvar00002; } catch(e) { }
+try { if (!var00125) { var00125 = GetVariable(fuzzervars, 'SVGGraphicsElement'); } else { SetVariable(var00125, 'SVGGraphicsElement'); SetVariable(var00125, 'SVGElement'); SetVariable(var00125, 'GlobalEventHandlers'); SetVariable(var00125, 'EventTarget'); SetVariable(var00125, 'GlobalEventHandlers'); } } catch(e) { }
+try { svgvar00009.setAttribute("stroke-dashoffset", "inherit"); } catch(e) { }
+try { htmlvar00002.setAttribute("compact", "compact"); } catch(e) { }
+try { /* newvar{var00126:sequence_Animation_} */ var var00126 = htmlvar00007.getAnimations(); } catch(e) { }
+try { if (!var00126) { var00126 = GetVariable(fuzzervars, 'sequence_Animation_'); } else { SetVariable(var00126, 'sequence_Animation_'); } } catch(e) { }
+try { document.dir = "1"; } catch(e) { }
+try { svgvar00009.setAttribute("additive", "sum"); } catch(e) { }
+try { var00073.setProperty("outline-style", "auto"); } catch(e) { }
+try { /* newvar{var00128:DOMStringList} */ var var00128 = new Array(String.fromCodePoint(279111, 407115, 565350, 908640, 782254, 340849, 398898, 820098, 844679, 311691, 511960, 66504, 687028, 1035203, 19625, 48233, 117498, 911219, 958656, 1005135)); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00128) { var00128 = GetVariable(fuzzervars, 'DOMStringList'); } else { SetVariable(var00128, 'DOMStringList'); } } catch(e) { }
+try { /* newvar{var00127:boolean} */ var var00127 = var00128.contains("htmlvar00007"); } catch(e) { }
+try { htmlvar00020.setAttribute("ping", "" + String.fromCharCode(85, 81, 115, 94, 95, 79, 92, 97, 35, 71, 43, 90, 42, 55, 92, 74, 95, 120, 91, 51) + ""); } catch(e) { }
+try { htmlvar00006.volume = 0.725518749752; } catch(e) { }
+try { document.all[55%document.all.length].appendChild(htmlvar00016); } catch(e) { }
+try { /* newvar{var00129:long} */ var var00129 = htmlvar00006.webkitDecodedFrameCount; } catch(e) { }
+try { htmlvar00020.innerText = String.fromCharCode(52, 34, 40, 34, 80, 109, 100, 94, 73, 90, 48, 56, 90, 54, 101, 93, 58, 75, 108, 87); } catch(e) { }
+try { svgvar00011.setAttribute("stroke-opacity", "0.0985532765439"); } catch(e) { }
+try { svgvar00011.setAttribute("glyph-name", "c"); } catch(e) { }
+try { /* newvar{var00130:DOMString} */ var var00130 = var00112.lookupPrefix("htmlvar00001"); } catch(e) { }
+try { /* newvar{var00131:boolean} */ var var00131 = htmlvar00003.noValidate; } catch(e) { }
+try { var00110.value = 0; } catch(e) { }
+try { /* newvar{var00132:DOMString} */ var var00132 = document.xmlEncoding; } catch(e) { }
+try { svgvar00005.setAttribute("divisor", "9"); } catch(e) { }
+try { /* newvar{var00134:HTMLSelectElement} */ var var00134 = document.createElement("select"); } catch(e) { }
+try { if (!var00134) { var00134 = GetVariable(fuzzervars, 'HTMLSelectElement'); } else { SetVariable(var00134, 'HTMLSelectElement'); SetVariable(var00134, 'Element'); SetVariable(var00134, 'GlobalEventHandlers'); SetVariable(var00134, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00133:HTMLOptionsCollection} */ var var00133 = var00134.options; } catch(e) { }
+try { if (!var00133) { var00133 = GetVariable(fuzzervars, 'HTMLOptionsCollection'); } else { SetVariable(var00133, 'HTMLOptionsCollection'); SetVariable(var00133, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00135:HTMLOptGroupElement} */ var var00135 = document.createElement("optgroup"); } catch(e) { }
+try { if (!var00135) { var00135 = GetVariable(fuzzervars, 'HTMLOptGroupElement'); } else { SetVariable(var00135, 'HTMLOptGroupElement'); SetVariable(var00135, 'Element'); SetVariable(var00135, 'GlobalEventHandlers'); SetVariable(var00135, 'EventTarget'); } } catch(e) { }
+try { var00133.add(var00135); } catch(e) { }
+try { svgvar00009.onbeforecopy = var00084; } catch(e) { }
+try { /* newvar{var00136:StyleSheet} */ var var00136 = htmlvar00007.sheet; } catch(e) { }
+try { if (!var00136) { var00136 = GetVariable(fuzzervars, 'StyleSheet'); } else { SetVariable(var00136, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00137:Element} */ var var00137 = var00024; } catch(e) { }
+try { if (!var00137) { var00137 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00137, 'Element'); SetVariable(var00137, 'GlobalEventHandlers'); SetVariable(var00137, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00138:boolean} */ var var00138 = var00022.getModifierState(String.fromCodePoint(386341, 566601, 326200, 1012498, 1009004, 309554, 322642, 1048771, 863577, 694212, 1064467, 704061, 739497, 1014162, 271202, 563218, 199559, 416009, 835743, 766806)); } catch(e) { }
+try { /* newvar{var00139:AutoKeyword} */ var var00139 = var00019.position; } catch(e) { }
+try { if (!var00139) { var00139 = GetVariable(fuzzervars, 'AutoKeyword'); } else { SetVariable(var00139, 'AutoKeyword'); } } catch(e) { }
+try { var00019.position = var00139; } catch(e) { }
+try { /* newvar{var00140:ShadowRoot} */ var var00140 = var00053.shadowRoot; } catch(e) { }
+try { if (!var00140) { var00140 = GetVariable(fuzzervars, 'ShadowRoot'); } else { SetVariable(var00140, 'ShadowRoot'); SetVariable(var00140, 'DocumentOrShadowRoot'); SetVariable(var00140, 'DocumentFragment'); SetVariable(var00140, 'Element'); SetVariable(var00140, 'GlobalEventHandlers'); SetVariable(var00140, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00144:DOMImplementation} */ var var00144 = document.implementation; } catch(e) { }
+try { if (!var00144) { var00144 = GetVariable(fuzzervars, 'DOMImplementation'); } else { SetVariable(var00144, 'DOMImplementation'); } } catch(e) { }
+try { /* newvar{var00143:Document} */ var var00143 = var00144.createHTMLDocument(); } catch(e) { }
+try { if (!var00143) { var00143 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00143, 'Document'); SetVariable(var00143, 'GlobalEventHandlers'); SetVariable(var00143, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00142:Range} */ var var00142 = var00143.createRange(); } catch(e) { }
+try { if (!var00142) { var00142 = GetVariable(fuzzervars, 'Range'); } else { SetVariable(var00142, 'Range'); } } catch(e) { }
+try { /* newvar{var00141:Range} */ var var00141 = var00142.cloneRange(); } catch(e) { }
+try { if (!var00141) { var00141 = GetVariable(fuzzervars, 'Range'); } else { SetVariable(var00141, 'Range'); } } catch(e) { }
+try { var00141.insertNode(htmlvar00031); } catch(e) { }
+try { /* newvar{var00145:EventListener} */ var var00145 = var00045; } catch(e) { }
+try { if (!var00145) { var00145 = GetVariable(fuzzervars, 'EventListener'); } else { SetVariable(var00145, 'EventListener'); } } catch(e) { }
+try { var00092.addEventListener("DOMCharacterDataModified", var00145); } catch(e) { }
+try { htmlvar00012.setAttribute("longdesc", "" + String.fromCharCode(116, 89, 102, 109, 92, 107, 112, 88, 98, 72, 94, 59, 118, 80, 61, 41, 33, 110, 85, 73) + ""); } catch(e) { }
+try { /* newvar{var00148:SVGFEFuncBElement} */ var var00148 = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB"); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { if (!var00148) { var00148 = GetVariable(fuzzervars, 'SVGFEFuncBElement'); } else { SetVariable(var00148, 'SVGFEFuncBElement'); SetVariable(var00148, 'SVGComponentTransferFunctionElement'); SetVariable(var00148, 'SVGElement'); SetVariable(var00148, 'GlobalEventHandlers'); SetVariable(var00148, 'EventTarget'); SetVariable(var00148, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00147:SVGComponentTransferFunctionElement} */ var var00147 = var00148; } catch(e) { }
+try { if (!var00147) { var00147 = GetVariable(fuzzervars, 'SVGComponentTransferFunctionElement'); } else { SetVariable(var00147, 'SVGComponentTransferFunctionElement'); SetVariable(var00147, 'SVGElement'); SetVariable(var00147, 'GlobalEventHandlers'); SetVariable(var00147, 'EventTarget'); SetVariable(var00147, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00146:SVGAnimatedEnumeration} */ var var00146 = var00147.type; } catch(e) { }
+try { if (!var00146) { var00146 = GetVariable(fuzzervars, 'SVGAnimatedEnumeration'); } else { SetVariable(var00146, 'SVGAnimatedEnumeration'); } } catch(e) { }
+try { var00148.setAttribute("textLength", "1"); } catch(e) { }
+try { var00024.addEventListener("DOMAttributeNameChanged", var00145); } catch(e) { }
+try { var00009.setProperty("break-after", "page"); } catch(e) { }
+try { var00073.setProperty("stop-color", "rgb(250,34,25)"); } catch(e) { }
+try { htmlvar00028.setAttribute("onmouseleave", "eventhandler2()"); } catch(e) { }
+try { /* newvar{var00150:CharacterData} */ var var00150 = var00014; } catch(e) { }
+try { if (!var00150) { var00150 = GetVariable(fuzzervars, 'CharacterData'); } else { SetVariable(var00150, 'CharacterData'); SetVariable(var00150, 'Element'); SetVariable(var00150, 'GlobalEventHandlers'); SetVariable(var00150, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00149:long} */ var var00149 = var00150.length; } catch(e) { }
+try { /* newvar{var00151:boolean} */ var var00151 = htmlvar00009.autofocus; } catch(e) { }
+try { var00030.setProperty("-webkit-border-vertical-spacing", "6px"); } catch(e) { }
+try { /* newvar{var00152:DOMString} */ var var00152 = htmlvar00005.value; } catch(e) { }
+try { /* newvar{var00153:boolean} */ var var00153 = var00121.disabled; } catch(e) { }
+try { /* newvar{var00156:HTMLTableElement} */ var var00156 = document.createElement("table"); } catch(e) { }
+try { if (!var00156) { var00156 = GetVariable(fuzzervars, 'HTMLTableElement'); } else { SetVariable(var00156, 'HTMLTableElement'); SetVariable(var00156, 'Element'); SetVariable(var00156, 'GlobalEventHandlers'); SetVariable(var00156, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00155:HTMLTableRowElement} */ var var00155 = var00156.insertRow(-1); } catch(e) { }
+try { if (!var00155) { var00155 = GetVariable(fuzzervars, 'HTMLTableRowElement'); } else { SetVariable(var00155, 'HTMLTableRowElement'); SetVariable(var00155, 'Element'); SetVariable(var00155, 'GlobalEventHandlers'); SetVariable(var00155, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00154:Element} */ var var00154 = var00155.insertCell(67); } catch(e) { }
+try { if (!var00154) { var00154 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00154, 'Element'); SetVariable(var00154, 'GlobalEventHandlers'); SetVariable(var00154, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00157:long} */ var var00157 = window.length; } catch(e) { }
+try { /* newvar{var00158:HTMLFormElement} */ var var00158 = var00027.form; } catch(e) { }
+try { if (!var00158) { var00158 = GetVariable(fuzzervars, 'HTMLFormElement'); } else { SetVariable(var00158, 'HTMLFormElement'); SetVariable(var00158, 'Element'); SetVariable(var00158, 'GlobalEventHandlers'); SetVariable(var00158, 'EventTarget'); } } catch(e) { }
+try { var00009.setProperty("text-underline", "single"); } catch(e) { }
+try { var00014.deleteData(0,30); } catch(e) { }
+try { /* newvar{var00159:SVGViewSpec} */ var var00159 = svgvar00001.currentView; } catch(e) { }
+try { if (!var00159) { var00159 = GetVariable(fuzzervars, 'SVGViewSpec'); } else { SetVariable(var00159, 'SVGViewSpec'); SetVariable(var00159, 'SVGFitToViewBox'); SetVariable(var00159, 'SVGZoomAndPan'); } } catch(e) { }
+try { var00030.setProperty("background-color", "black"); } catch(e) { }
+try { /* newvar{var00160:SVGElement} */ var var00160 = svgvar00007.nearestViewportElement; } catch(e) { }
+try { if (!var00160) { var00160 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00160, 'SVGElement'); SetVariable(var00160, 'GlobalEventHandlers'); SetVariable(var00160, 'EventTarget'); SetVariable(var00160, 'GlobalEventHandlers'); } } catch(e) { }
+try { var00134.setAttribute("case", "second"); } catch(e) { }
+try { /* newvar{var00161:boolean} */ var var00161 = var00134.disabled; } catch(e) { }
+try { htmlvar00002.setAttribute("aria-hidden", "false"); } catch(e) { }
+try { htmlvar00013.bgColor = "rgb(16,178,81)"; } catch(e) { }
+try { /* newvar{var00162:short} */ var var00162 = svgvar00009.nodeType; } catch(e) { }
+try { /* newvar{var00163:boolean} */ var var00163 = svgvar00010.webkitMatchesSelector(String.fromCharCode(36, 96, 115, 41, 63, 99, 98, 78, 82, 67, 112, 88, 113, 123, 77, 80, 118, 41, 90, 59)); } catch(e) { }
+try { htmlvar00013.width = "1"; } catch(e) { }
+try { /* newvar{var00164:BarProp} */ var var00164 = var00097.statusbar; } catch(e) { }
+try { if (!var00164) { var00164 = GetVariable(fuzzervars, 'BarProp'); } else { SetVariable(var00164, 'BarProp'); } } catch(e) { }
+try { var00160.setAttribute("slope", "-1"); } catch(e) { }
+try { /* newvar{var00165:BeforeUnloadEvent} */ var var00165 = document.createEvent("BeforeUnloadEvent"); } catch(e) { }
+try { if (!var00165) { var00165 = GetVariable(fuzzervars, 'BeforeUnloadEvent'); } else { SetVariable(var00165, 'BeforeUnloadEvent'); SetVariable(var00165, 'Event'); } } catch(e) { }
+try { var00165.returnValue = String.fromCodePoint(276401, 238248, 140692, 926107, 553155, 235883, 771438, 264084, 216232, 754424, 292771, 1093927, 158332, 790686, 535128, 452244, 384772, 289903, 202188, 425865); } catch(e) { }
+try { document.all[95%document.all.length].appendChild(htmlvar00023); } catch(e) { }
+try { var00073.setProperty("box-orient", "horizontal"); } catch(e) { }
+try { var00122.initMouseEvent("1",true,false,window,0); } catch(e) { }
+try { /* newvar{var00166:any} */ var var00166 = var00123.data; } catch(e) { }
+try { if (!var00166) { var00166 = GetVariable(fuzzervars, 'any'); } else { SetVariable(var00166, 'any'); } } catch(e) { }
+try { var00053.setAttribute("requiredFeatures", "x"); } catch(e) { }
+try { /* newvar{var00167:boolean} */ var var00167 = var00120.autofocus; } catch(e) { }
+try { /* newvar{var00168:DOMString} */ var var00168 = var00030.cssFloat; } catch(e) { }
+try { /* newvar{var00169:long} */ var var00169 = var00122.offsetY; } catch(e) { }
+try { /* newvar{var00170:SVGAnimatedRect} */ var var00170 = var00053.viewBox; } catch(e) { }
+try { if (!var00170) { var00170 = GetVariable(fuzzervars, 'SVGAnimatedRect'); } else { SetVariable(var00170, 'SVGAnimatedRect'); } } catch(e) { }
+try { var00143.webkitCancelFullScreen(); } catch(e) { }
+try { /* newvar{var00171:boolean} */ var var00171 = var00088.containsNode(var00016); } catch(e) { }
+try { svgvar00003.setAttribute("preserveAspectRatio", "xMidYMin meet"); } catch(e) { }
+try { /* newvar{var00172:Element} */ var var00172 = var00013.ownerElement; } catch(e) { }
+try { if (!var00172) { var00172 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00172, 'Element'); SetVariable(var00172, 'GlobalEventHandlers'); SetVariable(var00172, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00174:NodeFilter} */ var var00174 = var00081.filter; } catch(e) { }
+try { if (!var00174) { var00174 = GetVariable(fuzzervars, 'NodeFilter'); } else { SetVariable(var00174, 'NodeFilter'); } } catch(e) { }
+try { /* newvar{var00173:short} */ var var00173 = var00174.acceptNode(htmlvar00028); } catch(e) { }
+try { /* newvar{var00175:Element} */ var var00175 = var00026.removeChild(var00026.childNodes[60%var00026.childNodes.length]); } catch(e) { }
+try { if (!var00175) { var00175 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00175, 'Element'); SetVariable(var00175, 'GlobalEventHandlers'); SetVariable(var00175, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00176:EventHandler} */ var var00176 = var00106.ondragleave; } catch(e) { }
+try { if (!var00176) { var00176 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00176, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00179:TextTrack} */ var var00179 = htmlvar00002.track; } catch(e) { }
+try { if (!var00179) { var00179 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00179, 'TextTrack'); SetVariable(var00179, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00178:TextTrackKind} */ var var00178 = var00179.kind; } catch(e) { }
+try { if (!var00178) { var00178 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00178, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00177:TextTrack} */ var var00177 = htmlvar00001.addTextTrack(var00178,"1","htmlvar00008"); } catch(e) { }
+try { if (!var00177) { var00177 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00177, 'TextTrack'); SetVariable(var00177, 'EventTarget'); } } catch(e) { }
+try { var00177.removeCue(var00019); } catch(e) { }
+try { /* newvar{var00180:EventHandler} */ var var00180 = window.onsearch; } catch(e) { }
+try { if (!var00180) { var00180 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00180, 'EventHandler'); } } catch(e) { }
+try { var00156.deleteTFoot(); } catch(e) { }
+try { /* newvar{var00181:DOMString} */ var var00181 = htmlvar00007.type; } catch(e) { }
+try { /* newvar{var00182:EventHandler} */ var var00182 = svgvar00008.ontouchmove; } catch(e) { }
+try { if (!var00182) { var00182 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00182, 'EventHandler'); } } catch(e) { }
+try { var00073.setProperty("max-zoom", "auto"); } catch(e) { }
+try { /* newvar{var00183:double} */ var var00183 = var00004.screenX; } catch(e) { }
+try { /* newvar{var00184:long} */ var var00184 = svgvar00009.clientWidth; } catch(e) { }
+try { var00175.onerror = var00059; } catch(e) { }
+try { var00148.addEventListener("start",var00145,true); } catch(e) { }
+try { svgvar00010.setAttribute("stop-offset", "1"); } catch(e) { }
+try { /* newvar{var00187:URL} */ var var00187 = new URL("http://foo/bar"); } catch(e) { }
+try { if (!var00187) { var00187 = GetVariable(fuzzervars, 'URL'); } else { SetVariable(var00187, 'URL'); } } catch(e) { }
+try { /* newvar{var00186:URLSearchParams} */ var var00186 = var00187.searchParams; } catch(e) { }
+try { if (!var00186) { var00186 = GetVariable(fuzzervars, 'URLSearchParams'); } else { SetVariable(var00186, 'URLSearchParams'); } } catch(e) { }
+try { /* newvar{var00185:boolean} */ var var00185 = var00186.has(var00025); } catch(e) { }
+try { /* newvar{var00188:CustomElementConstructor} */ var var00188 = var00143.registerElement("foo"); } catch(e) { }
+try { if (!var00188) { var00188 = GetVariable(fuzzervars, 'CustomElementConstructor'); } else { SetVariable(var00188, 'CustomElementConstructor'); } } catch(e) { }
+try { /* newvar{var00189:boolean} */ var var00189 = var00122.altKey; } catch(e) { }
+try { /* newvar{var00190:boolean} */ var var00190 = htmlvar00031.willValidate; } catch(e) { }
+try { var00106.setAttribute("itemprop", "" + String.fromCharCode(94, 91, 97, 102, 81, 52, 86, 92, 55, 78, 49, 106, 116, 54, 98, 64, 126, 57, 32, 112) + ""); } catch(e) { }
+try { var00027.name = "" + String.fromCharCode(65, 86, 39, 51, 33, 48, 73, 64, 71, 112, 67, 103, 91, 46, 108, 52, 117, 68, 74, 110) + ""; } catch(e) { }
+try { var00140.setAttribute("shape", "default"); } catch(e) { }
+try { var00060.setAttribute("refY", "1"); } catch(e) { }
+try { htmlvar00013.detachedCallback(); } catch(e) { }
+try { var00009.setProperty("-webkit-animation-direction", "normal"); } catch(e) { }
+try { var00009.setProperty("oxverflow", "hidden"); } catch(e) { }
+try { /* newvar{var00191:CompositionEvent} */ var var00191 = document.createEvent("CompositionEvent"); } catch(e) { }
+try { if (!var00191) { var00191 = GetVariable(fuzzervars, 'CompositionEvent'); } else { SetVariable(var00191, 'CompositionEvent'); SetVariable(var00191, 'UIEvent'); SetVariable(var00191, 'Event'); } } catch(e) { }
+try { var00191.initCompositionEvent("htmlvar00007",false,false,window); } catch(e) { }
+//endjs
+var fuzzervars = {};
+freememory()
+
+
+}
+
+function eventhandler4() {
+
+runcount["eventhandler4"]++; if(runcount["eventhandler4"] > 2) { return; }
+
+var fuzzervars = {};
+
+SetVariable(fuzzervars, window, 'Window');
+SetVariable(fuzzervars, document, 'Document');
+SetVariable(fuzzervars, document.body.firstChild, 'Element');
+
+//beginjs
+/* newvar{htmlvar00001:HTMLVideoElement} */ var htmlvar00001 = document.getElementById("htmlvar00001"); //HTMLVideoElement
+/* newvar{htmlvar00002:HTMLTrackElement} */ var htmlvar00002 = document.getElementById("htmlvar00002"); //HTMLTrackElement
+/* newvar{htmlvar00003:HTMLFormElement} */ var htmlvar00003 = document.getElementById("htmlvar00003"); //HTMLFormElement
+/* newvar{htmlvar00004:HTMLLegendElement} */ var htmlvar00004 = document.getElementById("htmlvar00004"); //HTMLLegendElement
+/* newvar{htmlvar00005:HTMLInputElement} */ var htmlvar00005 = document.getElementById("htmlvar00005"); //HTMLInputElement
+/* newvar{htmlvar00006:HTMLVideoElement} */ var htmlvar00006 = document.getElementById("htmlvar00006"); //HTMLVideoElement
+/* newvar{htmlvar00007:HTMLLinkElement} */ var htmlvar00007 = document.getElementById("htmlvar00007"); //HTMLLinkElement
+/* newvar{htmlvar00008:HTMLMapElement} */ var htmlvar00008 = document.getElementById("htmlvar00008"); //HTMLMapElement
+/* newvar{htmlvar00009:HTMLTextAreaElement} */ var htmlvar00009 = document.getElementById("htmlvar00009"); //HTMLTextAreaElement
+/* newvar{svgvar00001:SVGSVGElement} */ var svgvar00001 = document.getElementById("svgvar00001"); //SVGSVGElement
+/* newvar{svgvar00002:SVGForeignObjectElement} */ var svgvar00002 = document.getElementById("svgvar00002"); //SVGForeignObjectElement
+/* newvar{htmlvar00010:HTMMLUnknownElement} */ var htmlvar00010 = document.getElementById("htmlvar00010"); //HTMMLUnknownElement
+/* newvar{htmlvar00011:HTMLDialogElement} */ var htmlvar00011 = document.getElementById("htmlvar00011"); //HTMLDialogElement
+/* newvar{svgvar00003:SVGGElement} */ var svgvar00003 = document.getElementById("svgvar00003"); //SVGGElement
+/* newvar{svgvar00004:SVGElement} */ var svgvar00004 = document.getElementById("svgvar00004"); //SVGElement
+/* newvar{htmlvar00012:HTMLAnchorElement} */ var htmlvar00012 = document.getElementById("htmlvar00012"); //HTMLAnchorElement
+/* newvar{svgvar00005:SVGFEMergeElement} */ var svgvar00005 = document.getElementById("svgvar00005"); //SVGFEMergeElement
+/* newvar{svgvar00006:SVGAnimateTransformElement} */ var svgvar00006 = document.getElementById("svgvar00006"); //SVGAnimateTransformElement
+/* newvar{svgvar00007:SVGTSpanElement} */ var svgvar00007 = document.getElementById("svgvar00007"); //SVGTSpanElement
+/* newvar{svgvar00008:SVGCircleElement} */ var svgvar00008 = document.getElementById("svgvar00008"); //SVGCircleElement
+/* newvar{svgvar00009:SVGAnimateElement} */ var svgvar00009 = document.getElementById("svgvar00009"); //SVGAnimateElement
+/* newvar{svgvar00010:SVGAnimateTransformElement} */ var svgvar00010 = document.getElementById("svgvar00010"); //SVGAnimateTransformElement
+/* newvar{svgvar00011:SVGFEConvolveMatrixElement} */ var svgvar00011 = document.getElementById("svgvar00011"); //SVGFEConvolveMatrixElement
+/* newvar{svgvar00012:SVGElement} */ var svgvar00012 = document.getElementById("svgvar00012"); //SVGElement
+/* newvar{htmlvar00013:HTMLMarqueeElement} */ var htmlvar00013 = document.getElementById("htmlvar00013"); //HTMLMarqueeElement
+/* newvar{htmlvar00014:HTMLUListElement} */ var htmlvar00014 = document.getElementById("htmlvar00014"); //HTMLUListElement
+/* newvar{htmlvar00015:HTMLLIElement} */ var htmlvar00015 = document.getElementById("htmlvar00015"); //HTMLLIElement
+/* newvar{htmlvar00016:HTMLBaseElement} */ var htmlvar00016 = document.getElementById("htmlvar00016"); //HTMLBaseElement
+/* newvar{htmlvar00017:HTMLOListElement} */ var htmlvar00017 = document.getElementById("htmlvar00017"); //HTMLOListElement
+/* newvar{htmlvar00018:HTMLLIElement} */ var htmlvar00018 = document.getElementById("htmlvar00018"); //HTMLLIElement
+/* newvar{htmlvar00019:HTMLDirectoryElement} */ var htmlvar00019 = document.getElementById("htmlvar00019"); //HTMLDirectoryElement
+/* newvar{htmlvar00020:HTMLShadowElement} */ var htmlvar00020 = document.getElementById("htmlvar00020"); //HTMLShadowElement
+/* newvar{htmlvar00021:HTMLUnknownElement} */ var htmlvar00021 = document.getElementById("htmlvar00021"); //HTMLUnknownElement
+/* newvar{htmlvar00022:HTMLLIElement} */ var htmlvar00022 = document.getElementById("htmlvar00022"); //HTMLLIElement
+/* newvar{htmlvar00023:HTMLDetailsElement} */ var htmlvar00023 = document.getElementById("htmlvar00023"); //HTMLDetailsElement
+/* newvar{htmlvar00024:HTMLVideoElement} */ var htmlvar00024 = document.getElementById("htmlvar00024"); //HTMLVideoElement
+/* newvar{htmlvar00025:HTMLTimeElement} */ var htmlvar00025 = document.getElementById("htmlvar00025"); //HTMLTimeElement
+/* newvar{htmlvar00026:HTMLUnknownElement} */ var htmlvar00026 = document.getElementById("htmlvar00026"); //HTMLUnknownElement
+/* newvar{htmlvar00027:HTMLTimeElement} */ var htmlvar00027 = document.getElementById("htmlvar00027"); //HTMLTimeElement
+/* newvar{htmlvar00028:HTMLUnknownElement} */ var htmlvar00028 = document.createElement("noembed"); //HTMLUnknownElement
+/* newvar{htmlvar00029:HTMLProgressElement} */ var htmlvar00029 = document.createElement("progress"); //HTMLProgressElement
+/* newvar{htmlvar00030:HTMLMapElement} */ var htmlvar00030 = document.createElement("map"); //HTMLMapElement
+/* newvar{htmlvar00031:HTMLTextAreaElement} */ var htmlvar00031 = document.createElement("textarea"); //HTMLTextAreaElement
+/* newvar{htmlvar00032:HTMLUnknownElement} */ var htmlvar00032 = document.createElement("html"); //HTMLUnknownElement
+try { /* newvar{var00002:ApplicationCache} */ var var00002 = window.applicationCache; } catch(e) { }
+try { if (!var00002) { var00002 = GetVariable(fuzzervars, 'ApplicationCache'); } else { SetVariable(var00002, 'ApplicationCache'); SetVariable(var00002, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00001:EventHandler} */ var var00001 = var00002.onchecking; } catch(e) { }
+try { if (!var00001) { var00001 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00001, 'EventHandler'); } } catch(e) { }
+try { svgvar00012.onpointerout = var00001; } catch(e) { }
+try { /* newvar{var00003:VisibilityState} */ var var00003 = document.visibilityState; } catch(e) { }
+try { if (!var00003) { var00003 = GetVariable(fuzzervars, 'VisibilityState'); } else { SetVariable(var00003, 'VisibilityState'); } } catch(e) { }
+try { htmlvar00003.setAttribute("shape", "rect"); } catch(e) { }
+try { htmlvar00003.setAttribute("readonly", "readonly"); } catch(e) { }
+try { /* newvar{var00004:boolean} */ var var00004 = htmlvar00027.spellcheck; } catch(e) { }
+try { /* newvar{var00006:InputEvent} */ var var00006 = document.createEvent("InputEvent"); } catch(e) { }
+try { if (!var00006) { var00006 = GetVariable(fuzzervars, 'InputEvent'); } else { SetVariable(var00006, 'InputEvent'); SetVariable(var00006, 'UIEvent'); SetVariable(var00006, 'Event'); } } catch(e) { }
+try { /* newvar{var00005:UIEvent} */ var var00005 = var00006; } catch(e) { }
+try { if (!var00005) { var00005 = GetVariable(fuzzervars, 'UIEvent'); } else { SetVariable(var00005, 'UIEvent'); SetVariable(var00005, 'Event'); } } catch(e) { }
+try { document.all[30%document.all.length].appendChild(htmlvar00001); } catch(e) { }
+try { htmlvar00017.compact = true; } catch(e) { }
+try { /* newvar{var00007:SVGRect} */ var var00007 = svgvar00001.createSVGRect(); } catch(e) { }
+try { if (!var00007) { var00007 = GetVariable(fuzzervars, 'SVGRect'); } else { SetVariable(var00007, 'SVGRect'); } } catch(e) { }
+try { /* newvar{var00008:HTMLContentElement} */ var var00008 = document.createElement("content"); } catch(e) { }
+try { if (!var00008) { var00008 = GetVariable(fuzzervars, 'HTMLContentElement'); } else { SetVariable(var00008, 'HTMLContentElement'); SetVariable(var00008, 'Element'); SetVariable(var00008, 'GlobalEventHandlers'); SetVariable(var00008, 'EventTarget'); } } catch(e) { }
+try { var00008.select = "#htmlvar00002"; } catch(e) { }
+try { /* newvar{var00009:boolean} */ var var00009 = htmlvar00003.reportValidity(); } catch(e) { }
+try { /* newvar{var00010:EventHandler} */ var var00010 = svgvar00010.onselect; } catch(e) { }
+try { if (!var00010) { var00010 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00010, 'EventHandler'); } } catch(e) { }
+try { var00008.onmouseleave = var00010; } catch(e) { }
+try { /* newvar{var00011:DOMStringMap} */ var var00011 = svgvar00005.dataset; } catch(e) { }
+try { if (!var00011) { var00011 = GetVariable(fuzzervars, 'DOMStringMap'); } else { SetVariable(var00011, 'DOMStringMap'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00012:Element} */ var var00012 = htmlvar00009.firstElementChild; } catch(e) { }
+try { if (!var00012) { var00012 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00012, 'Element'); SetVariable(var00012, 'GlobalEventHandlers'); SetVariable(var00012, 'EventTarget'); } } catch(e) { }
+try { htmlvar00002.setAttribute("slot", "slot1"); } catch(e) { }
+try { /* newvar{var00013:MessageEvent} */ var var00013 = document.createEvent("MessageEvent"); } catch(e) { }
+try { if (!var00013) { var00013 = GetVariable(fuzzervars, 'MessageEvent'); } else { SetVariable(var00013, 'MessageEvent'); SetVariable(var00013, 'Event'); } } catch(e) { }
+try { /* newvar{var00015:PopStateEvent} */ var var00015 = document.createEvent("PopStateEvent"); } catch(e) { }
+try { if (!var00015) { var00015 = GetVariable(fuzzervars, 'PopStateEvent'); } else { SetVariable(var00015, 'PopStateEvent'); SetVariable(var00015, 'Event'); } } catch(e) { }
+try { /* newvar{var00014:any} */ var var00014 = var00015.state; } catch(e) { }
+try { if (!var00014) { var00014 = GetVariable(fuzzervars, 'any'); } else { SetVariable(var00014, 'any'); } } catch(e) { }
+try { /* newvar{var00017:MessagePort} */ var var00017 = var00013.ports; } catch(e) { }
+try { if (!var00017) { var00017 = GetVariable(fuzzervars, 'MessagePort'); } else { SetVariable(var00017, 'MessagePort'); SetVariable(var00017, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00016:sequence_MessagePort_} */ var var00016 = new Array(var00017); } catch(e) { }
+try { if (!var00016) { var00016 = GetVariable(fuzzervars, 'sequence_MessagePort_'); } else { SetVariable(var00016, 'sequence_MessagePort_'); } } catch(e) { }
+try { var00013.initMessageEvent(String.fromCharCode(62, 50, 119, 95, 109, 69, 102, 107, 114, 67, 55, 95, 36, 60, 82, 34, 71, 37, 102, 67),true,true,var00014,String.fromCharCode(57, 110, 94, 83, 75, 108, 125, 68, 75, 46, 94, 41, 108, 82, 76, 56, 48, 105, 42, 76),String.fromCharCode(103, 79, 70, 71, 43, 85, 78, 118, 32, 116, 74, 95, 113, 45, 61, 125, 44, 57, 111, 100),window,var00016); } catch(e) { }
+try { htmlvar00024.setAttribute("onwebkitkeymessage", "eventhandler2()"); } catch(e) { }
+try { document.onsecuritypolicyviolation = var00010; } catch(e) { }
+try { htmlvar00009.minLength = 0; } catch(e) { }
+try { /* newvar{var00019:MessageChannel} */ var var00019 = new MessageChannel(); } catch(e) { }
+try { if (!var00019) { var00019 = GetVariable(fuzzervars, 'MessageChannel'); } else { SetVariable(var00019, 'MessageChannel'); } } catch(e) { }
+try { /* newvar{var00018:MessagePort} */ var var00018 = var00019.port2; } catch(e) { }
+try { if (!var00018) { var00018 = GetVariable(fuzzervars, 'MessagePort'); } else { SetVariable(var00018, 'MessagePort'); SetVariable(var00018, 'EventTarget'); } } catch(e) { }
+try { var00018.onmessage = var00001; } catch(e) { }
+try { svgvar00011.setAttribute("numOctaves", "0"); } catch(e) { }
+try { window.resizeBy(0,1); } catch(e) { }
+try { htmlvar00008.ontouchcancel = var00001; } catch(e) { }
+try { /* newvar{var00021:URL} */ var var00021 = new URL("http://foo/bar"); } catch(e) { }
+try { if (!var00021) { var00021 = GetVariable(fuzzervars, 'URL'); } else { SetVariable(var00021, 'URL'); } } catch(e) { }
+try { /* newvar{var00020:USVString} */ var var00020 = var00021.hash; } catch(e) { }
+try { if (!var00020) { var00020 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00020, 'USVString'); } } catch(e) { }
+try { htmlvar00007.scope = var00020; } catch(e) { }
+try { /* newvar{var00022:FormData} */ var var00022 = new FormData(); } catch(e) { }
+try { if (!var00022) { var00022 = GetVariable(fuzzervars, 'FormData'); } else { SetVariable(var00022, 'FormData'); } } catch(e) { }
+try { var00022.delete(var00020); } catch(e) { }
+try { htmlvar00013.trueSpeed = true; } catch(e) { }
+try { htmlvar00023.outerText = "1"; } catch(e) { }
+try { svgvar00012.setAttribute("transform", "matrix(1 1 0 7 10.150053378665 10.705761090713)"); } catch(e) { }
+try { /* newvar{var00023:HTMLCollection} */ var var00023 = htmlvar00028.getElementsByTagNameNS("http://www.w3.org/1999/xhtml","div"); } catch(e) { }
+try { if (!var00023) { var00023 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00023, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00025:SVGStyleElement} */ var var00025 = document.createElementNS("http://www.w3.org/2000/svg", "style"); } catch(e) { }
+try { if (!var00025) { var00025 = GetVariable(fuzzervars, 'SVGStyleElement'); } else { SetVariable(var00025, 'SVGStyleElement'); SetVariable(var00025, 'SVGElement'); SetVariable(var00025, 'GlobalEventHandlers'); SetVariable(var00025, 'EventTarget'); SetVariable(var00025, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00024:SVGElement} */ var var00024 = var00025; } catch(e) { }
+try { if (!var00024) { var00024 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00024, 'SVGElement'); SetVariable(var00024, 'GlobalEventHandlers'); SetVariable(var00024, 'EventTarget'); SetVariable(var00024, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00026:EventHandler} */ var var00026 = htmlvar00022.ontouchmove; } catch(e) { }
+try { if (!var00026) { var00026 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00026, 'EventHandler'); } } catch(e) { }
+try { htmlvar00015.setAttribute("crossorigin", "crossorigin"); } catch(e) { }
+try { htmlvar00007.type = "javascript1.1"; } catch(e) { }
+try { htmlvar00020.setAttribute("aria-autocomplete", "list"); } catch(e) { }
+try { var00025.setAttribute("from", "hidden"); } catch(e) { }
+try { document.designMode = String.fromCharCode(103, 118, 60, 49, 72, 119, 57, 100, 86, 116, 116, 116, 57, 122, 69, 48, 82, 52, 106, 34); } catch(e) { }
+try { htmlvar00023.setAttribute("onmouseover", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00027:DOMString} */ var var00027 = svgvar00005.lookupNamespaceURI("foo"); } catch(e) { }
+try { svgvar00007.normalize(); } catch(e) { }
+try { /* newvar{var00028:CSSStyleDeclaration} */ var var00028 = htmlvar00019.style; } catch(e) { }
+try { if (!var00028) { var00028 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00028, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00029:Document} */ var var00029 = svgvar00008.ownerDocument; } catch(e) { }
+try { if (!var00029) { var00029 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00029, 'Document'); SetVariable(var00029, 'GlobalEventHandlers'); SetVariable(var00029, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { htmlvar00015.setAttribute("spellcheck", "true"); } catch(e) { }
+try { /* newvar{var00031:HTMLOutputElement} */ var var00031 = document.createElement("output"); } catch(e) { }
+try { if (!var00031) { var00031 = GetVariable(fuzzervars, 'HTMLOutputElement'); } else { SetVariable(var00031, 'HTMLOutputElement'); SetVariable(var00031, 'Element'); SetVariable(var00031, 'GlobalEventHandlers'); SetVariable(var00031, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00030:HTMLFormElement} */ var var00030 = var00031.form; } catch(e) { }
+try { if (!var00030) { var00030 = GetVariable(fuzzervars, 'HTMLFormElement'); } else { SetVariable(var00030, 'HTMLFormElement'); SetVariable(var00030, 'Element'); SetVariable(var00030, 'GlobalEventHandlers'); SetVariable(var00030, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00032:DataTransfer} */ var var00032 = var00006.dataTransfer; } catch(e) { }
+try { if (!var00032) { var00032 = GetVariable(fuzzervars, 'DataTransfer'); } else { SetVariable(var00032, 'DataTransfer'); } } catch(e) { }
+try { /* newvar{var00033:double} */ var var00033 = window.pageXOffset; } catch(e) { }
+try { window.defaultStatus = "foo"; } catch(e) { }
+try { /* newvar{var00034:boolean} */ var var00034 = var00005.defaultPrevented; } catch(e) { }
+try { var00028.setProperty("page", "Rotated"); } catch(e) { }
+try { /* newvar{var00035:History} */ var var00035 = window.history; } catch(e) { }
+try { if (!var00035) { var00035 = GetVariable(fuzzervars, 'History'); } else { SetVariable(var00035, 'History'); } } catch(e) { }
+try { htmlvar00013.onpaste = var00026; } catch(e) { }
+try { /* newvar{var00036:DOMString} */ var var00036 = htmlvar00026.baseURI; } catch(e) { }
+try { var00028.setProperty("-webkit-animation-direction", "alternate"); } catch(e) { }
+try { htmlvar00015.setAttribute("bgcolor", "red"); } catch(e) { }
+try { /* newvar{var00037:SVGElement} */ var var00037 = svgvar00003.querySelector(".class7"); } catch(e) { }
+try { if (!var00037) { var00037 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00037, 'SVGElement'); SetVariable(var00037, 'GlobalEventHandlers'); SetVariable(var00037, 'EventTarget'); SetVariable(var00037, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00038:EventHandler} */ var var00038 = htmlvar00018.ondragover; } catch(e) { }
+try { if (!var00038) { var00038 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00038, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00062:CSSStyleDeclaration} */ var var00062 = svgvar00006.style; } catch(e) { }
+try { if (!var00062) { var00062 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00062, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00061:CSSRule} */ var var00061 = var00062.parentRule; } catch(e) { }
+try { if (!var00061) { var00061 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00061, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00060:CSSMediaRule} */ var var00060 = var00061; } catch(e) { }
+try { if (!var00060) { var00060 = GetVariable(fuzzervars, 'CSSMediaRule'); } else { SetVariable(var00060, 'CSSMediaRule'); SetVariable(var00060, 'CSSGroupingRule'); SetVariable(var00060, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00059:CSSGroupingRule} */ var var00059 = var00060; } catch(e) { }
+try { if (!var00059) { var00059 = GetVariable(fuzzervars, 'CSSGroupingRule'); } else { SetVariable(var00059, 'CSSGroupingRule'); SetVariable(var00059, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00058:CSSRule} */ var var00058 = var00059; } catch(e) { }
+try { if (!var00058) { var00058 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00058, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00057:CSSSupportsRule} */ var var00057 = var00058; } catch(e) { }
+try { if (!var00057) { var00057 = GetVariable(fuzzervars, 'CSSSupportsRule'); } else { SetVariable(var00057, 'CSSSupportsRule'); SetVariable(var00057, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00056:CSSRule} */ var var00056 = var00057; } catch(e) { }
+try { if (!var00056) { var00056 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00056, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00055:CSSMediaRule} */ var var00055 = var00056; } catch(e) { }
+try { if (!var00055) { var00055 = GetVariable(fuzzervars, 'CSSMediaRule'); } else { SetVariable(var00055, 'CSSMediaRule'); SetVariable(var00055, 'CSSGroupingRule'); SetVariable(var00055, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00054:CSSGroupingRule} */ var var00054 = var00055; } catch(e) { }
+try { if (!var00054) { var00054 = GetVariable(fuzzervars, 'CSSGroupingRule'); } else { SetVariable(var00054, 'CSSGroupingRule'); SetVariable(var00054, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00053:CSSRule} */ var var00053 = var00054; } catch(e) { }
+try { if (!var00053) { var00053 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00053, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00052:CSSStyleSheet} */ var var00052 = var00053.parentStyleSheet; } catch(e) { }
+try { if (!var00052) { var00052 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00052, 'CSSStyleSheet'); SetVariable(var00052, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00051:CSSRule} */ var var00051 = var00052.ownerRule; } catch(e) { }
+try { if (!var00051) { var00051 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00051, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00050:CSSSupportsRule} */ var var00050 = var00051; } catch(e) { }
+try { if (!var00050) { var00050 = GetVariable(fuzzervars, 'CSSSupportsRule'); } else { SetVariable(var00050, 'CSSSupportsRule'); SetVariable(var00050, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00049:CSSRule} */ var var00049 = var00050; } catch(e) { }
+try { if (!var00049) { var00049 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00049, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00048:CSSImportRule} */ var var00048 = var00049; } catch(e) { }
+try { if (!var00048) { var00048 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00048, 'CSSImportRule'); SetVariable(var00048, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00047:CSSStyleSheet} */ var var00047 = var00048.styleSheet; } catch(e) { }
+try { if (!var00047) { var00047 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00047, 'CSSStyleSheet'); SetVariable(var00047, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00046:CSSRuleList} */ var var00046 = var00047.cssRules; } catch(e) { }
+try { if (!var00046) { var00046 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00046, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00045:CSSRule} */ var var00045 = var00046.item(25%var00046.length); } catch(e) { }
+try { if (!var00045) { var00045 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00045, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00044:CSSMediaRule} */ var var00044 = var00045; } catch(e) { }
+try { if (!var00044) { var00044 = GetVariable(fuzzervars, 'CSSMediaRule'); } else { SetVariable(var00044, 'CSSMediaRule'); SetVariable(var00044, 'CSSGroupingRule'); SetVariable(var00044, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00043:CSSGroupingRule} */ var var00043 = var00044; } catch(e) { }
+try { if (!var00043) { var00043 = GetVariable(fuzzervars, 'CSSGroupingRule'); } else { SetVariable(var00043, 'CSSGroupingRule'); SetVariable(var00043, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00042:CSSRule} */ var var00042 = var00043; } catch(e) { }
+try { if (!var00042) { var00042 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00042, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00041:CSSFontFaceRule} */ var var00041 = var00042; } catch(e) { }
+try { if (!var00041) { var00041 = GetVariable(fuzzervars, 'CSSFontFaceRule'); } else { SetVariable(var00041, 'CSSFontFaceRule'); SetVariable(var00041, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00040:CSSRule} */ var var00040 = var00041; } catch(e) { }
+try { if (!var00040) { var00040 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00040, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00039:CSSPageRule} */ var var00039 = var00040; } catch(e) { }
+try { if (!var00039) { var00039 = GetVariable(fuzzervars, 'CSSPageRule'); } else { SetVariable(var00039, 'CSSPageRule'); SetVariable(var00039, 'CSSRule'); } } catch(e) { }
+try { var00039.selectorText = "foo"; } catch(e) { }
+try { /* newvar{var00063:DOMString} */ var var00063 = svgvar00002.namespaceURI; } catch(e) { }
+try { var00062.setProperty("opacity", "0.084692114215"); } catch(e) { }
+try { /* newvar{var00064:DOMRect} */ var var00064 = new DOMRect(1, 0, 1, 1); } catch(e) { }
+try { if (!var00064) { var00064 = GetVariable(fuzzervars, 'DOMRect'); } else { SetVariable(var00064, 'DOMRect'); SetVariable(var00064, 'DOMRectReadOnly'); } } catch(e) { }
+try { var00064.width = 0.774214995099; } catch(e) { }
+try { /* newvar{var00065:Element} */ var var00065 = htmlvar00019; } catch(e) { }
+try { if (!var00065) { var00065 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00065, 'Element'); SetVariable(var00065, 'GlobalEventHandlers'); SetVariable(var00065, 'EventTarget'); } } catch(e) { }
+try { var00062.setProperty("-webkit-margin-start", "32px"); } catch(e) { }
+try { htmlvar00009.remove(); } catch(e) { }
+try { var00062.setProperty("border-top-left-radius", "69%"); } catch(e) { }
+try { var00028.setProperty("margin", "auto 35"); } catch(e) { }
+try { /* newvar{var00066:SVGElement} */ var var00066 = svgvar00008.farthestViewportElement; } catch(e) { }
+try { if (!var00066) { var00066 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00066, 'SVGElement'); SetVariable(var00066, 'GlobalEventHandlers'); SetVariable(var00066, 'EventTarget'); SetVariable(var00066, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00068:HTMLButtonElement} */ var var00068 = document.createElement("button"); } catch(e) { }
+try { if (!var00068) { var00068 = GetVariable(fuzzervars, 'HTMLButtonElement'); } else { SetVariable(var00068, 'HTMLButtonElement'); SetVariable(var00068, 'Element'); SetVariable(var00068, 'GlobalEventHandlers'); SetVariable(var00068, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00067:boolean} */ var var00067 = var00068.reportValidity(); } catch(e) { }
+try { var00062.setProperty("orphans", "0"); } catch(e) { }
+try { /* newvar{var00069:HTMLCollection} */ var var00069 = var00029.links; } catch(e) { }
+try { if (!var00069) { var00069 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00069, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00070:AudioTrackList} */ var var00070 = htmlvar00001.audioTracks; } catch(e) { }
+try { if (!var00070) { var00070 = GetVariable(fuzzervars, 'AudioTrackList'); } else { SetVariable(var00070, 'AudioTrackList'); SetVariable(var00070, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00071:boolean} */ var var00071 = htmlvar00005.incremental; } catch(e) { }
+try { htmlvar00013.stop(); } catch(e) { }
+try { htmlvar00009.setAttribute("rules", "all"); } catch(e) { }
+try { var00037.setAttribute("tableValues", "6"); } catch(e) { }
+try { svgvar00001.setAttribute("in", "tile"); } catch(e) { }
+try { htmlvar00012.target = "htmlvar00002"; } catch(e) { }
+try { var00028.setProperty("font-variant-ligatures", "common-ligatures"); } catch(e) { }
+try { /* newvar{var00076:DragEvent} */ var var00076 = document.createEvent("DragEvent"); } catch(e) { }
+try { if (!var00076) { var00076 = GetVariable(fuzzervars, 'DragEvent'); } else { SetVariable(var00076, 'DragEvent'); SetVariable(var00076, 'MouseEvent'); SetVariable(var00076, 'UIEvent'); SetVariable(var00076, 'Event'); } } catch(e) { }
+try { /* newvar{var00075:MouseEvent} */ var var00075 = var00076; } catch(e) { }
+try { if (!var00075) { var00075 = GetVariable(fuzzervars, 'MouseEvent'); } else { SetVariable(var00075, 'MouseEvent'); SetVariable(var00075, 'UIEvent'); SetVariable(var00075, 'Event'); } } catch(e) { }
+try { /* newvar{var00074:UIEvent} */ var var00074 = var00075; } catch(e) { }
+try { if (!var00074) { var00074 = GetVariable(fuzzervars, 'UIEvent'); } else { SetVariable(var00074, 'UIEvent'); SetVariable(var00074, 'Event'); } } catch(e) { }
+try { /* newvar{var00073:Window} */ var var00073 = var00074.view; } catch(e) { }
+try { if (!var00073) { var00073 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00073, 'Window'); SetVariable(var00073, 'GlobalEventHandlers'); SetVariable(var00073, 'WindowBase64'); SetVariable(var00073, 'WindowEventHandlers'); SetVariable(var00073, 'WindowTimers'); SetVariable(var00073, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00072:EventHandler} */ var var00072 = var00073.onanimationend; } catch(e) { }
+try { if (!var00072) { var00072 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00072, 'EventHandler'); } } catch(e) { }
+try { svgvar00012.ongotpointercapture = var00072; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00077:EventHandler} */ var var00077 = var00029.onselectionchange; } catch(e) { }
+try { if (!var00077) { var00077 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00077, 'EventHandler'); } } catch(e) { }
+try { var00062.setProperty("mso-style-name", "Normal"); } catch(e) { }
+try { svgvar00011.setAttribute("aria-label", "smile"); } catch(e) { }
+try { var00028.setProperty("-webkit-border-top-right-radius", "-1px"); } catch(e) { }
+try { var00002.update(); } catch(e) { }
+try { svgvar00004.setAttribute("in", "pair"); } catch(e) { }
+try { /* newvar{var00078:DOMString} */ var var00078 = var00030.action; } catch(e) { }
+try { /* newvar{var00079:EventHandler} */ var var00079 = svgvar00011.oncanplaythrough; } catch(e) { }
+try { if (!var00079) { var00079 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00079, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00080:Selection} */ var var00080 = document.getSelection(); } catch(e) { }
+try { if (!var00080) { var00080 = GetVariable(fuzzervars, 'Selection'); } else { SetVariable(var00080, 'Selection'); } } catch(e) { }
+try { var00080.extend(htmlvar00021,0); } catch(e) { }
+try { /* newvar{var00081:SVGTransform} */ var var00081 = svgvar00001.createSVGTransform(); } catch(e) { }
+try { if (!var00081) { var00081 = GetVariable(fuzzervars, 'SVGTransform'); } else { SetVariable(var00081, 'SVGTransform'); } } catch(e) { }
+try { /* newvar{var00082:Element} */ var var00082 = document.adoptNode(htmlvar00020); } catch(e) { }
+try { if (!var00082) { var00082 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00082, 'Element'); SetVariable(var00082, 'GlobalEventHandlers'); SetVariable(var00082, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00083:CustomElementConstructor} */ var var00083 = var00029.registerElement(String.fromCharCode(94, 70, 54, 45, 90, 92, 91, 46, 95, 106, 105, 98, 61, 34, 118, 35, 116, 106, 105, 39)); } catch(e) { }
+try { if (!var00083) { var00083 = GetVariable(fuzzervars, 'CustomElementConstructor'); } else { SetVariable(var00083, 'CustomElementConstructor'); } } catch(e) { }
+try { /* newvar{var00084:DOMString} */ var var00084 = var00031.validationMessage; } catch(e) { }
+try { htmlvar00002.setAttribute("rel", "noreferrer"); } catch(e) { }
+try { var00028.setProperty("justify-items", "right legacy"); } catch(e) { }
+try { htmlvar00004.setAttribute("high", "0"); } catch(e) { }
+try { /* newvar{var00085:EventHandler} */ var var00085 = htmlvar00014.oncancel; } catch(e) { }
+try { if (!var00085) { var00085 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00085, 'EventHandler'); } } catch(e) { }
+try { var00062.setProperty("flex-basis", "0"); } catch(e) { }
+try { var00062.setProperty("border-width", "0"); } catch(e) { }
+try { /* newvar{var00086:DOMString} */ var var00086 = htmlvar00013.width; } catch(e) { }
+try { /* newvar{var00087:StyleSheet} */ var var00087 = htmlvar00007.sheet; } catch(e) { }
+try { if (!var00087) { var00087 = GetVariable(fuzzervars, 'StyleSheet'); } else { SetVariable(var00087, 'StyleSheet'); } } catch(e) { }
+try { var00006.initUIEvent(String.fromCharCode(82, 88, 66, 41, 105, 81, 111, 123, 66, 52, 43, 62, 119, 57, 71, 37, 100, 90, 68, 113),true,true); } catch(e) { }
+try { /* newvar{var00088:USVString} */ var var00088 = var00021.href; } catch(e) { }
+try { if (!var00088) { var00088 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00088, 'USVString'); } } catch(e) { }
+try { /* newvar{var00092:ShadowRoot} */ var var00092 = var00065.createShadowRoot(); } catch(e) { }
+try { if (!var00092) { var00092 = GetVariable(fuzzervars, 'ShadowRoot'); } else { SetVariable(var00092, 'ShadowRoot'); SetVariable(var00092, 'DocumentOrShadowRoot'); SetVariable(var00092, 'DocumentFragment'); SetVariable(var00092, 'Element'); SetVariable(var00092, 'GlobalEventHandlers'); SetVariable(var00092, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00091:ShadowRoot} */ var var00091 = var00092.olderShadowRoot; } catch(e) { }
+try { if (!var00091) { var00091 = GetVariable(fuzzervars, 'ShadowRoot'); } else { SetVariable(var00091, 'ShadowRoot'); SetVariable(var00091, 'DocumentOrShadowRoot'); SetVariable(var00091, 'DocumentFragment'); SetVariable(var00091, 'Element'); SetVariable(var00091, 'GlobalEventHandlers'); SetVariable(var00091, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00090:htmlstring} */ var var00090 = var00091.innerHTML; } catch(e) { }
+try { if (!var00090) { var00090 = GetVariable(fuzzervars, 'htmlstring'); } else { SetVariable(var00090, 'htmlstring'); } } catch(e) { }
+try { /* newvar{var00089:boolean} */ var var00089 = var00029.execCommand("insertHTML", false, var00090); } catch(e) { }
+try { /* newvar{var00093:long} */ var var00093 = htmlvar00005.width; } catch(e) { }
+try { /* newvar{var00094:EventHandler} */ var var00094 = var00073.onanimationend; } catch(e) { }
+try { if (!var00094) { var00094 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00094, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00095:DOMString} */ var var00095 = htmlvar00012.rev; } catch(e) { }
+try { /* newvar{var00096:DOMString} */ var var00096 = var00068.value; } catch(e) { }
+try { var00015.preventDefault(); } catch(e) { }
+try { /* newvar{var00097:boolean} */ var var00097 = htmlvar00001.ended; } catch(e) { }
+try { htmlvar00016.onpointercancel = var00085; } catch(e) { }
+try { /* newvar{var00098:DOMString} */ var var00098 = htmlvar00005.min; } catch(e) { }
+try { /* newvar{var00099:DOMString} */ var var00099 = var00030.acceptCharset; } catch(e) { }
+try { window.releaseEvents(); } catch(e) { }
+try { htmlvar00001.onmouseout = var00072; } catch(e) { }
+try { svgvar00011.setAttribute("role", "button"); } catch(e) { }
+try { var00062.setProperty("-webkit-column-width", "77px"); } catch(e) { }
+try { /* newvar{var00100:Window} */ var var00100 = var00076.view; } catch(e) { }
+try { if (!var00100) { var00100 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00100, 'Window'); SetVariable(var00100, 'GlobalEventHandlers'); SetVariable(var00100, 'WindowBase64'); SetVariable(var00100, 'WindowEventHandlers'); SetVariable(var00100, 'WindowTimers'); SetVariable(var00100, 'EventTarget'); } } catch(e) { }
+try { htmlvar00028.onplaying = var00026; } catch(e) { }
+try { var00062.setProperty("grid-column", "1/5 middle"); } catch(e) { }
+try { var00080.collapse(htmlvar00024,5); } catch(e) { }
+try { /* newvar{var00102:sequence_Dictionary_} */ var var00102 = { "-webkit-column-rule-color": [0, 0] }; } catch(e) { }
+try { if (!var00102) { var00102 = GetVariable(fuzzervars, 'sequence_Dictionary_'); } else { SetVariable(var00102, 'sequence_Dictionary_'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00103:KeyframeEffectOptions} */ var var00103 = { delay: -1, direction: String.fromCodePoint(949784, 259576, 1083738, 885020, 476093, 634704, 1036403, 23179, 819996, 446268, 585846, 129928, 572524, 491139, 985220, 581035, 618522, 1085192, 224947, 157560), duration: 46, easing: "1", endDelay: 0, fill: String.fromCodePoint(578313, 729167, 914531, 45146, 845099, 60037, 1034095, 741486, 928102, 1084143, 948028, 513920, 597180, 629799, 993696, 1097286, 542799, 658465, 1027918, 319674), iterationStart: 0.0550739733228, iterations: Infinity }; } catch(e) { }
+try { if (!var00103) { var00103 = GetVariable(fuzzervars, 'KeyframeEffectOptions'); } else { SetVariable(var00103, 'KeyframeEffectOptions'); } } catch(e) { }
+try { /* newvar{var00101:Animation} */ var var00101 = var00030.animate(var00102,var00103); } catch(e) { }
+try { if (!var00101) { var00101 = GetVariable(fuzzervars, 'Animation'); } else { SetVariable(var00101, 'Animation'); SetVariable(var00101, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00106:SVGPoint} */ var var00106 = svgvar00007.getStartPositionOfChar(1); } catch(e) { }
+try { if (!var00106) { var00106 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00106, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00112:SVGMatrix} */ var var00112 = var00081.matrix; } catch(e) { }
+try { if (!var00112) { var00112 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00112, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00111:SVGMatrix} */ var var00111 = var00112.skewY(0.228402664894); } catch(e) { }
+try { if (!var00111) { var00111 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00111, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00110:SVGMatrix} */ var var00110 = var00111.skewX(0.188186908725); } catch(e) { }
+try { if (!var00110) { var00110 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00110, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00109:SVGMatrix} */ var var00109 = var00110.skewY(0.847379793505); } catch(e) { }
+try { if (!var00109) { var00109 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00109, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00108:SVGMatrix} */ var var00108 = var00109.inverse(); } catch(e) { }
+try { if (!var00108) { var00108 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00108, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00107:SVGMatrix} */ var var00107 = var00108.scale(0.0121203106643); } catch(e) { }
+try { if (!var00107) { var00107 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00107, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00105:SVGPoint} */ var var00105 = var00106.matrixTransform(var00107); } catch(e) { }
+try { if (!var00105) { var00105 = GetVariable(fuzzervars, 'SVGPoint'); } else { SetVariable(var00105, 'SVGPoint'); } } catch(e) { }
+try { /* newvar{var00104:boolean} */ var var00104 = svgvar00008.isPointInStroke(var00105); } catch(e) { }
+try { /* newvar{var00113:TextTrackList} */ var var00113 = htmlvar00001.textTracks; } catch(e) { }
+try { if (!var00113) { var00113 = GetVariable(fuzzervars, 'TextTrackList'); } else { SetVariable(var00113, 'TextTrackList'); SetVariable(var00113, 'EventTarget'); } } catch(e) { }
+try { var00113.onremovetrack = var00072; } catch(e) { }
+try { /* newvar{var00116:SVGViewSpec} */ var var00116 = svgvar00001.currentView; } catch(e) { }
+try { if (!var00116) { var00116 = GetVariable(fuzzervars, 'SVGViewSpec'); } else { SetVariable(var00116, 'SVGViewSpec'); SetVariable(var00116, 'SVGFitToViewBox'); SetVariable(var00116, 'SVGZoomAndPan'); } } catch(e) { }
+try { /* newvar{var00115:SVGTransformList} */ var var00115 = var00116.transform; } catch(e) { }
+try { if (!var00115) { var00115 = GetVariable(fuzzervars, 'SVGTransformList'); } else { SetVariable(var00115, 'SVGTransformList'); } } catch(e) { }
+try { /* newvar{var00114:SVGTransform} */ var var00114 = var00115.createSVGTransformFromMatrix(var00108); } catch(e) { }
+try { if (!var00114) { var00114 = GetVariable(fuzzervars, 'SVGTransform'); } else { SetVariable(var00114, 'SVGTransform'); } } catch(e) { }
+try { /* newvar{var00117:DOMString} */ var var00117 = htmlvar00014.type; } catch(e) { }
+try { /* newvar{var00119:Document} */ var var00119 = document; } catch(e) { }
+try { if (!var00119) { var00119 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00119, 'Document'); SetVariable(var00119, 'GlobalEventHandlers'); SetVariable(var00119, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00118:boolean} */ var var00118 = var00119.execCommand("forwardDelete", false); } catch(e) { }
+try { htmlvar00005.max = "73"; } catch(e) { }
+try { var00062.setProperty("transition-delay", "inherit"); } catch(e) { }
+try { /* newvar{var00120:SVGAnimatedLengthList} */ var var00120 = svgvar00007.dy; } catch(e) { }
+try { if (!var00120) { var00120 = GetVariable(fuzzervars, 'SVGAnimatedLengthList'); } else { SetVariable(var00120, 'SVGAnimatedLengthList'); } } catch(e) { }
+try { var00028.setProperty("-webkit-margin-bottom-collapse", "discard"); } catch(e) { }
+try { /* newvar{var00121:long} */ var var00121 = var00076.movementY; } catch(e) { }
+try { /* newvar{var00127:HTMLTrackElement} */ var var00127 = document.createElement("track"); } catch(e) { }
+try { if (!var00127) { var00127 = GetVariable(fuzzervars, 'HTMLTrackElement'); } else { SetVariable(var00127, 'HTMLTrackElement'); SetVariable(var00127, 'Element'); SetVariable(var00127, 'GlobalEventHandlers'); SetVariable(var00127, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00126:TextTrack} */ var var00126 = var00127.track; } catch(e) { }
+try { if (!var00126) { var00126 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00126, 'TextTrack'); SetVariable(var00126, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00125:TextTrackKind} */ var var00125 = var00126.kind; } catch(e) { }
+try { if (!var00125) { var00125 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00125, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00124:TextTrack} */ var var00124 = htmlvar00006.addTextTrack(var00125); } catch(e) { }
+try { if (!var00124) { var00124 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00124, 'TextTrack'); SetVariable(var00124, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00123:TextTrackCueList} */ var var00123 = var00124.cues; } catch(e) { }
+try { if (!var00123) { var00123 = GetVariable(fuzzervars, 'TextTrackCueList'); } else { SetVariable(var00123, 'TextTrackCueList'); } } catch(e) { }
+try { /* newvar{var00122:TextTrackCue} */ var var00122 = var00123[96%var00123.length]; } catch(e) { }
+try { if (!var00122) { var00122 = GetVariable(fuzzervars, 'TextTrackCue'); } else { SetVariable(var00122, 'TextTrackCue'); SetVariable(var00122, 'EventTarget'); } } catch(e) { }
+try { var00122.onexit = var00001; } catch(e) { }
+try { svgvar00011.outerHTML = var00090; } catch(e) { }
+try { var00028.setProperty("column-rule-style", "solid"); } catch(e) { }
+try { htmlvar00025.setAttribute("autocomplete", "off"); } catch(e) { }
+try { var00062.setProperty("padding-left", "0px"); } catch(e) { }
+try { var00029.selectedStylesheetSet = String.fromCharCode(82, 96, 81, 54, 56, 58, 111, 106, 43, 119, 57, 121, 74, 120, 60, 93, 84, 111, 126, 64); } catch(e) { }
+try { /* newvar{var00128:long} */ var var00128 = svgvar00001.childElementCount; } catch(e) { }
+try { /* newvar{var00131:HTMLTableElement} */ var var00131 = document.createElement("table"); } catch(e) { }
+try { if (!var00131) { var00131 = GetVariable(fuzzervars, 'HTMLTableElement'); } else { SetVariable(var00131, 'HTMLTableElement'); SetVariable(var00131, 'Element'); SetVariable(var00131, 'GlobalEventHandlers'); SetVariable(var00131, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00130:HTMLTableRowElement} */ var var00130 = var00131.insertRow(); } catch(e) { }
+try { if (!var00130) { var00130 = GetVariable(fuzzervars, 'HTMLTableRowElement'); } else { SetVariable(var00130, 'HTMLTableRowElement'); SetVariable(var00130, 'Element'); SetVariable(var00130, 'GlobalEventHandlers'); SetVariable(var00130, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00129:DOMString} */ var var00129 = var00130.ch; } catch(e) { }
+try { /* newvar{var00132:boolean} */ var var00132 = htmlvar00023.open; } catch(e) { }
+try { var00024.setAttribute("to", "0 0"); } catch(e) { }
+try { /* newvar{var00133:SVGElement} */ var var00133 = var00116.viewTarget; } catch(e) { }
+try { if (!var00133) { var00133 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00133, 'SVGElement'); SetVariable(var00133, 'GlobalEventHandlers'); SetVariable(var00133, 'EventTarget'); SetVariable(var00133, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00134:EventHandler} */ var var00134 = var00073.onmessage; } catch(e) { }
+try { if (!var00134) { var00134 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00134, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00135:HTMLAreaElement} */ var var00135 = document.createElement("area"); } catch(e) { }
+try { if (!var00135) { var00135 = GetVariable(fuzzervars, 'HTMLAreaElement'); } else { SetVariable(var00135, 'HTMLAreaElement'); SetVariable(var00135, 'HTMLHyperlinkElementUtils'); SetVariable(var00135, 'Element'); SetVariable(var00135, 'GlobalEventHandlers'); SetVariable(var00135, 'EventTarget'); } } catch(e) { }
+try { var00135.shape = "default"; } catch(e) { }
+try { /* newvar{var00136:short} */ var var00136 = svgvar00011.compareDocumentPosition(svgvar00006); } catch(e) { }
+try { /* newvar{var00137:StyleMedia} */ var var00137 = var00073.styleMedia; } catch(e) { }
+try { if (!var00137) { var00137 = GetVariable(fuzzervars, 'StyleMedia'); } else { SetVariable(var00137, 'StyleMedia'); } } catch(e) { }
+try { /* newvar{var00139:XPathExpression} */ var var00139 = var00119.createExpression("//thead"); } catch(e) { }
+try { if (!var00139) { var00139 = GetVariable(fuzzervars, 'XPathExpression'); } else { SetVariable(var00139, 'XPathExpression'); } } catch(e) { }
+try { /* newvar{var00142:XPathEvaluator} */ var var00142 = new XPathEvaluator(); } catch(e) { }
+try { if (!var00142) { var00142 = GetVariable(fuzzervars, 'XPathEvaluator'); } else { SetVariable(var00142, 'XPathEvaluator'); } } catch(e) { }
+try { /* newvar{var00141:XPathNSResolver} */ var var00141 = var00142.createNSResolver(htmlvar00004); } catch(e) { }
+try { if (!var00141) { var00141 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00141, 'XPathNSResolver'); } } catch(e) { }
+try { /* newvar{var00145:XPathResult} */ var var00145 = var00142.evaluate("//p",var00065,var00141,-1); } catch(e) { }
+try { if (!var00145) { var00145 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00145, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00144:XPathResult} */ var var00144 = var00029.evaluate("//del",document,null,XPathResult.ANY_TYPE,var00145); } catch(e) { }
+try { if (!var00144) { var00144 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00144, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00143:XPathResult} */ var var00143 = var00142.evaluate("//html",document,null,XPathResult.ANY_TYPE,var00144); } catch(e) { }
+try { if (!var00143) { var00143 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00143, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00140:XPathResult} */ var var00140 = var00119.evaluate("//marquee",var00130,var00141,0,var00143); } catch(e) { }
+try { if (!var00140) { var00140 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00140, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00138:XPathResult} */ var var00138 = var00139.evaluate(htmlvar00028,-1,var00140); } catch(e) { }
+try { if (!var00138) { var00138 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00138, 'XPathResult'); } } catch(e) { }
+try { var00028.setProperty("word-spacing", "0pc"); } catch(e) { }
+try { var00028.setProperty("mso-outline-level", "1"); } catch(e) { }
+try { var00076.initMouseEvent(String.fromCodePoint(491501, 466209, 364404, 301140, 1068290, 432614, 1011950, 591702, 481404, 780096, 830537, 982160, 859465, 928118, 372295, 843866, 167274, 44209, 804012, 326968),false,false,var00100,44,1,8,1,58,false,false); } catch(e) { }
+try { var00028.setProperty("zoom", "0"); } catch(e) { }
+try { var00130.align = "Left"; } catch(e) { }
+try { var00073.onorientationchange = var00001; } catch(e) { }
+try { /* newvar{var00146:DOMString} */ var var00146 = var00032.types; } catch(e) { }
+try { /* newvar{var00147:BarProp} */ var var00147 = window.statusbar; } catch(e) { }
+try { if (!var00147) { var00147 = GetVariable(fuzzervars, 'BarProp'); } else { SetVariable(var00147, 'BarProp'); } } catch(e) { }
+try { /* newvar{var00148:boolean} */ var var00148 = htmlvar00003.translate; } catch(e) { }
+try { /* newvar{var00149:CSSRule} */ var var00149 = var00047.ownerRule; } catch(e) { }
+try { if (!var00149) { var00149 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00149, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00150:boolean} */ var var00150 = var00029.execCommand("heading", false, "H3"); } catch(e) { }
+try { var00119.oncopy = var00079; } catch(e) { }
+try { /* newvar{var00151:CSSStyleDeclaration} */ var var00151 = htmlvar00020.style; } catch(e) { }
+try { if (!var00151) { var00151 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00151, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00037.onfocus = var00038; } catch(e) { }
+try { /* newvar{var00152:boolean} */ var var00152 = var00013.isTrusted; } catch(e) { }
+try { htmlvar00007.setAttribute("case", "capture"); } catch(e) { }
+try { var00028.setProperty("-webkit-transition-delay", "inherit"); } catch(e) { }
+try { var00028.setProperty("-webkit-margin-before-collapse", "collapse"); } catch(e) { }
+try { window.onwebkittransitionend = var00038; } catch(e) { }
+try { /* newvar{var00153:boolean} */ var var00153 = var00029.execCommand("removeFormat", false); } catch(e) { }
+try { var00062.setProperty("scroll-snap-destination", "90% 15%"); } catch(e) { }
+try { /* newvar{var00154:Element} */ var var00154 = var00091; } catch(e) { }
+try { if (!var00154) { var00154 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00154, 'Element'); SetVariable(var00154, 'GlobalEventHandlers'); SetVariable(var00154, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00155:Element} */ var var00155 = var00030.cloneNode(true); } catch(e) { }
+try { if (!var00155) { var00155 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00155, 'Element'); SetVariable(var00155, 'GlobalEventHandlers'); SetVariable(var00155, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00156:GlobalEventHandlers} */ var var00156 = var00154; } catch(e) { }
+try { if (!var00156) { var00156 = GetVariable(fuzzervars, 'GlobalEventHandlers'); } else { SetVariable(var00156, 'GlobalEventHandlers'); } } catch(e) { }
+try { document.all[57%document.all.length].appendChild(htmlvar00005); } catch(e) { }
+try { /* newvar{var00157:Element} */ var var00157 = htmlvar00012; } catch(e) { }
+try { if (!var00157) { var00157 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00157, 'Element'); SetVariable(var00157, 'GlobalEventHandlers'); SetVariable(var00157, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00158:boolean} */ var var00158 = var00029.queryCommandSupported("foo"); } catch(e) { }
+try { htmlvar00031.setSelectionRange(1,1,"foo"); } catch(e) { }
+try { /* newvar{var00159:double} */ var var00159 = htmlvar00029.position; } catch(e) { }
+try { /* newvar{var00160:boolean} */ var var00160 = svgvar00008.isPointInFill(var00106); } catch(e) { }
+try { document.all[0%document.all.length].appendChild(var00031); } catch(e) { }
+try { var00031.value = "" + String.fromCharCode(60, 77, 93, 118, 70, 126, 64, 64, 48, 98, 110, 88, 43, 124, 58, 56, 74, 54, 39, 85) + ""; } catch(e) { }
+try { var00155.setAttribute("onwebkitkeyadded", "eventhandler4()"); } catch(e) { }
+try { var00024.setAttribute("kernelMatrix", "0.2886783531 0.314707879188 0.302549772356 0.863685475821 0.0965052144676 0.840940715153 0.28116822534 0.760755010499 0.00993209626077"); } catch(e) { }
+try { var00064.height = 0.184944320857; } catch(e) { }
+try { /* newvar{var00161:CSSStyleDeclaration} */ var var00161 = htmlvar00019.style; } catch(e) { }
+try { if (!var00161) { var00161 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00161, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00062.setProperty("-webkit-text-emphasis", "open dot"); } catch(e) { }
+try { /* newvar{var00162:EventHandler} */ var var00162 = var00154.onresize; } catch(e) { }
+try { if (!var00162) { var00162 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00162, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00163:SVGAnimatedLengthList} */ var var00163 = svgvar00007.dx; } catch(e) { }
+try { if (!var00163) { var00163 = GetVariable(fuzzervars, 'SVGAnimatedLengthList'); } else { SetVariable(var00163, 'SVGAnimatedLengthList'); } } catch(e) { }
+try { var00100.onorientationchange = var00162; } catch(e) { }
+try { /* newvar{var00164:boolean} */ var var00164 = htmlvar00001.seeking; } catch(e) { }
+try { var00021.hostname = var00088; } catch(e) { }
+try { /* newvar{var00165:double} */ var var00165 = var00101.startTime; } catch(e) { }
+try { htmlvar00016.onpointerup = var00038; } catch(e) { }
+try { svgvar00009.scroll(0.623780381247,0.585170873319); } catch(e) { }
+try { var00087.disabled = false; } catch(e) { }
+try { var00024.prepend(svgvar00006); } catch(e) { }
+try { /* newvar{var00166:FontFaceSet} */ var var00166 = var00119.fonts; } catch(e) { }
+try { if (!var00166) { var00166 = GetVariable(fuzzervars, 'FontFaceSet'); } else { SetVariable(var00166, 'FontFaceSet'); SetVariable(var00166, 'EventTarget'); } } catch(e) { }
+try { var00166.onloading = var00085; } catch(e) { }
+try { var00028.setProperty("motion-path", "none"); } catch(e) { }
+try { htmlvar00012.onended = var00026; } catch(e) { }
+try { /* newvar{var00167:HTMLBodyElement} */ var var00167 = var00029.createElement("body"); } catch(e) { }
+try { if (!var00167) { var00167 = GetVariable(fuzzervars, 'HTMLBodyElement'); } else { SetVariable(var00167, 'HTMLBodyElement'); SetVariable(var00167, 'WindowEventHandlers'); SetVariable(var00167, 'Element'); SetVariable(var00167, 'GlobalEventHandlers'); SetVariable(var00167, 'EventTarget'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { var00167.onfocus = var00001; } catch(e) { }
+try { var00161.setProperty("border-top-width", "1"); } catch(e) { }
+try { /* newvar{var00168:Touch} */ var var00168 = var00029.createTouch(var00100,var00167,-1,0.190432277873,0.579527620118,0.148197063538,0.0789717449214,0.657013263033,0.0260162380482,0.950231295233); } catch(e) { }
+try { if (!var00168) { var00168 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00168, 'Touch'); } } catch(e) { }
+try { /* newvar{var00169:long} */ var var00169 = htmlvar00005.height; } catch(e) { }
+try { htmlvar00017.prepend(htmlvar00005); } catch(e) { }
+try { svgvar00006.setAttribute("markerHeight", "0"); } catch(e) { }
+try { /* newvar{var00170:MediaQueryList} */ var var00170 = window.matchMedia(String.fromCodePoint(395069, 559643, 1107104, 262994, 98297, 27802, 177864, 822201, 179430, 650600, 927095, 743059, 144286, 1017429, 790187, 1025810, 152477, 245022, 902945, 96971)); } catch(e) { }
+try { if (!var00170) { var00170 = GetVariable(fuzzervars, 'MediaQueryList'); } else { SetVariable(var00170, 'MediaQueryList'); SetVariable(var00170, 'EventTarget'); } } catch(e) { }
+try { var00170.onchange = var00010; } catch(e) { }
+try { /* newvar{var00171:DOMString} */ var var00171 = htmlvar00007.crossOrigin; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00172:HTMLImageElement} */ var var00172 = document.createElement("img"); } catch(e) { }
+try { if (!var00172) { var00172 = GetVariable(fuzzervars, 'HTMLImageElement'); } else { SetVariable(var00172, 'HTMLImageElement'); SetVariable(var00172, 'Element'); SetVariable(var00172, 'GlobalEventHandlers'); SetVariable(var00172, 'EventTarget'); } } catch(e) { }
+try { var00172.longDesc = "" + String.fromCharCode(35, 112, 44, 36, 71, 82, 52, 124, 107, 114, 99, 65, 104, 42, 58, 116, 107, 86, 62, 124) + ""; } catch(e) { }
+try { var00161.setProperty("-webkit-backface-visibility", "visible"); } catch(e) { }
+try { /* newvar{var00173:SVGMatrix} */ var var00173 = var00109.rotate(0.724748833716); } catch(e) { }
+try { if (!var00173) { var00173 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00173, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00174:XPathNSResolver} */ var var00174 = var00119.createNSResolver(var00030); } catch(e) { }
+try { if (!var00174) { var00174 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00174, 'XPathNSResolver'); } } catch(e) { }
+try { htmlvar00004.setAttribute("oncanplaythrough", "eventhandler5()"); } catch(e) { }
+try { /* newvar{var00175:DOMString} */ var var00175 = htmlvar00012.hreflang; } catch(e) { }
+try { /* newvar{var00176:Window} */ var var00176 = var00100.parent; } catch(e) { }
+try { if (!var00176) { var00176 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00176, 'Window'); SetVariable(var00176, 'GlobalEventHandlers'); SetVariable(var00176, 'WindowBase64'); SetVariable(var00176, 'WindowEventHandlers'); SetVariable(var00176, 'WindowTimers'); SetVariable(var00176, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00177:double} */ var var00177 = var00064.bottom; } catch(e) { }
+try { /* newvar{var00178:TreeWalker} */ var var00178 = var00119.createTreeWalker(htmlvar00024); } catch(e) { }
+try { if (!var00178) { var00178 = GetVariable(fuzzervars, 'TreeWalker'); } else { SetVariable(var00178, 'TreeWalker'); } } catch(e) { }
+try { /* newvar{var00179:HTMLFrameSetElement} */ var var00179 = document.createElement("frameset"); } catch(e) { }
+try { if (!var00179) { var00179 = GetVariable(fuzzervars, 'HTMLFrameSetElement'); } else { SetVariable(var00179, 'HTMLFrameSetElement'); SetVariable(var00179, 'WindowEventHandlers'); SetVariable(var00179, 'Element'); SetVariable(var00179, 'GlobalEventHandlers'); SetVariable(var00179, 'EventTarget'); } } catch(e) { }
+try { var00179.onresize = var00134; } catch(e) { }
+try { /* newvar{var00180:boolean} */ var var00180 = htmlvar00005.formNoValidate; } catch(e) { }
+try { /* newvar{var00181:CSSStyleDeclaration} */ var var00181 = var00091.style; } catch(e) { }
+try { if (!var00181) { var00181 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00181, 'CSSStyleDeclaration'); } } catch(e) { }
+try { var00181.setProperty("widows", "1"); } catch(e) { }
+try { var00062.setProperty("font-weight", "1 1"); } catch(e) { }
+try { var00100.scrollTo(); } catch(e) { }
+try { /* newvar{var00183:eventhandler} */ var var00183 = eventhandler1; } catch(e) { }
+try { if (!var00183) { var00183 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00183, 'eventhandler'); } } catch(e) { }
+try { /* newvar{var00182:EventListener} */ var var00182 = var00183; } catch(e) { }
+try { if (!var00182) { var00182 = GetVariable(fuzzervars, 'EventListener'); } else { SetVariable(var00182, 'EventListener'); } } catch(e) { }
+try { var00082.addEventListener("DOMSubtreeModified", var00182); } catch(e) { }
+try { /* newvar{var00185:SVGFilterElement} */ var var00185 = document.createElementNS("http://www.w3.org/2000/svg", "filter"); } catch(e) { }
+try { if (!var00185) { var00185 = GetVariable(fuzzervars, 'SVGFilterElement'); } else { SetVariable(var00185, 'SVGFilterElement'); SetVariable(var00185, 'SVGURIReference'); SetVariable(var00185, 'SVGElement'); SetVariable(var00185, 'GlobalEventHandlers'); SetVariable(var00185, 'EventTarget'); SetVariable(var00185, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00184:svg_url_filter} */ var var00184 = "url(#" + var00185.id + ")"; } catch(e) { }
+try { if (!var00184) { var00184 = GetVariable(fuzzervars, 'svg_url_filter'); } else { SetVariable(var00184, 'svg_url_filter'); } } catch(e) { }
+try { svgvar00006.setAttribute("filter", var00184); } catch(e) { }
+try { var00030.setAttribute("spellcheck", "false"); } catch(e) { }
+try { /* newvar{var00187:SVGSymbolElement} */ var var00187 = document.createElementNS("http://www.w3.org/2000/svg", "symbol"); } catch(e) { }
+try { if (!var00187) { var00187 = GetVariable(fuzzervars, 'SVGSymbolElement'); } else { SetVariable(var00187, 'SVGSymbolElement'); SetVariable(var00187, 'SVGFitToViewBox'); SetVariable(var00187, 'SVGElement'); SetVariable(var00187, 'GlobalEventHandlers'); SetVariable(var00187, 'EventTarget'); SetVariable(var00187, 'GlobalEventHandlers'); } } catch(e) { }
+try { /* newvar{var00186:SVGFitToViewBox} */ var var00186 = var00187; } catch(e) { }
+try { if (!var00186) { var00186 = GetVariable(fuzzervars, 'SVGFitToViewBox'); } else { SetVariable(var00186, 'SVGFitToViewBox'); } } catch(e) { }
+try { var00151.setProperty("font-style", "none"); } catch(e) { }
+try { htmlvar00019.setAttribute("aria-flowto", "htmlvar00004"); } catch(e) { }
+try { var00161.setProperty("-webkit-line-break", "normal"); } catch(e) { }
+try { /* newvar{var00188:DOMString} */ var var00188 = var00060.cssText; } catch(e) { }
+try { var00179.setAttribute("ontransitionend", "eventhandler2()"); } catch(e) { }
+try { htmlvar00005.name = "" + String.fromCharCode(33, 76, 119, 85, 61, 82, 117, 104, 86, 32, 100, 115, 76, 112, 84, 120, 49, 100, 69, 108) + ""; } catch(e) { }
+try { /* newvar{var00189:boolean} */ var var00189 = var00080.isCollapsed; } catch(e) { }
+try { /* newvar{var00190:CSSStyleDeclaration} */ var var00190 = htmlvar00031.style; } catch(e) { }
+try { if (!var00190) { var00190 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00190, 'CSSStyleDeclaration'); } } catch(e) { }
+try { htmlvar00024.webkitExitFullScreen(); } catch(e) { }
+try { /* newvar{var00191:DOMString} */ var var00191 = var00131.frame; } catch(e) { }
+try { /* newvar{var00192:long} */ var var00192 = var00172.width; } catch(e) { }
+try { /* newvar{var00193:double} */ var var00193 = var00173.f; } catch(e) { }
+try { htmlvar00031.setSelectionRange(0); } catch(e) { }
+try { var00181.setProperty("border-bottom", " solid"); } catch(e) { }
+try { /* newvar{var00194:Window} */ var var00194 = var00179["htmlvar00007"]; } catch(e) { }
+try { if (!var00194) { var00194 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00194, 'Window'); SetVariable(var00194, 'GlobalEventHandlers'); SetVariable(var00194, 'WindowBase64'); SetVariable(var00194, 'WindowEventHandlers'); SetVariable(var00194, 'WindowTimers'); SetVariable(var00194, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00195:XPathResult} */ var var00195 = var00029.evaluate("//sub",htmlvar00031); } catch(e) { }
+try { if (!var00195) { var00195 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00195, 'XPathResult'); } } catch(e) { }
+try { htmlvar00017.translate = true; } catch(e) { }
+try { /* newvar{var00196:boolean} */ var var00196 = document.execCommand("justifyCenter", false); } catch(e) { }
+try { /* newvar{var00197:HTMLFormElement} */ var var00197 = htmlvar00004.form; } catch(e) { }
+try { if (!var00197) { var00197 = GetVariable(fuzzervars, 'HTMLFormElement'); } else { SetVariable(var00197, 'HTMLFormElement'); SetVariable(var00197, 'Element'); SetVariable(var00197, 'GlobalEventHandlers'); SetVariable(var00197, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00198:boolean} */ var var00198 = var00029.execCommand("hiliteColor", false, "black"); } catch(e) { }
+try { /* newvar{var00199:short} */ var var00199 = var00075.button; } catch(e) { }
+try { var00024.removeAttributeNS("http://www.w3.org/2000/svg",var00024.attributes[18%var00024.attributes.length].name); } catch(e) { }
+try { var00052.removeRule(); } catch(e) { }
+try { htmlvar00025.setAttribute("translate", "yes"); } catch(e) { }
+try { var00161.setProperty("box-shadow", "1px 1px 0px inset"); } catch(e) { }
+try { /* newvar{var00200:long} */ var var00200 = var00075.which; } catch(e) { }
+try { /* newvar{var00201:EventHandler} */ var var00201 = htmlvar00023.ontoggle; } catch(e) { }
+try { if (!var00201) { var00201 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00201, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00202:EventHandler} */ var var00202 = var00179.onfocus; } catch(e) { }
+try { if (!var00202) { var00202 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00202, 'EventHandler'); } } catch(e) { }
+try { var00028.setProperty("grid-column-start", "first span"); } catch(e) { }
+try { var00190.setProperty("-webkit-text-orientation", "upright"); } catch(e) { }
+try { htmlvar00028.setAttribute("onplaying", "eventhandler3()"); } catch(e) { }
+try { htmlvar00005.useMap = "#htmlvar00003"; } catch(e) { }
+try { var00032.clearData("1"); } catch(e) { }
+try { svgvar00005.setAttribute("seed", "9"); } catch(e) { }
+try { /* newvar{var00203:DOMString} */ var var00203 = var00135.download; } catch(e) { }
+//endjs
+var fuzzervars = {};
+freememory()
+
+
+}
+
+function eventhandler5() {
+
+runcount["eventhandler5"]++; if(runcount["eventhandler5"] > 2) { return; }
+
+var fuzzervars = {};
+
+SetVariable(fuzzervars, window, 'Window');
+SetVariable(fuzzervars, document, 'Document');
+SetVariable(fuzzervars, document.body.firstChild, 'Element');
+
+//beginjs
+/* newvar{htmlvar00001:HTMLVideoElement} */ var htmlvar00001 = document.getElementById("htmlvar00001"); //HTMLVideoElement
+/* newvar{htmlvar00002:HTMLTrackElement} */ var htmlvar00002 = document.getElementById("htmlvar00002"); //HTMLTrackElement
+/* newvar{htmlvar00003:HTMLFormElement} */ var htmlvar00003 = document.getElementById("htmlvar00003"); //HTMLFormElement
+/* newvar{htmlvar00004:HTMLLegendElement} */ var htmlvar00004 = document.getElementById("htmlvar00004"); //HTMLLegendElement
+/* newvar{htmlvar00005:HTMLInputElement} */ var htmlvar00005 = document.getElementById("htmlvar00005"); //HTMLInputElement
+/* newvar{htmlvar00006:HTMLVideoElement} */ var htmlvar00006 = document.getElementById("htmlvar00006"); //HTMLVideoElement
+/* newvar{htmlvar00007:HTMLLinkElement} */ var htmlvar00007 = document.getElementById("htmlvar00007"); //HTMLLinkElement
+/* newvar{htmlvar00008:HTMLMapElement} */ var htmlvar00008 = document.getElementById("htmlvar00008"); //HTMLMapElement
+/* newvar{htmlvar00009:HTMLTextAreaElement} */ var htmlvar00009 = document.getElementById("htmlvar00009"); //HTMLTextAreaElement
+/* newvar{svgvar00001:SVGSVGElement} */ var svgvar00001 = document.getElementById("svgvar00001"); //SVGSVGElement
+/* newvar{svgvar00002:SVGForeignObjectElement} */ var svgvar00002 = document.getElementById("svgvar00002"); //SVGForeignObjectElement
+/* newvar{htmlvar00010:HTMMLUnknownElement} */ var htmlvar00010 = document.getElementById("htmlvar00010"); //HTMMLUnknownElement
+/* newvar{htmlvar00011:HTMLDialogElement} */ var htmlvar00011 = document.getElementById("htmlvar00011"); //HTMLDialogElement
+/* newvar{svgvar00003:SVGGElement} */ var svgvar00003 = document.getElementById("svgvar00003"); //SVGGElement
+/* newvar{svgvar00004:SVGElement} */ var svgvar00004 = document.getElementById("svgvar00004"); //SVGElement
+/* newvar{htmlvar00012:HTMLAnchorElement} */ var htmlvar00012 = document.getElementById("htmlvar00012"); //HTMLAnchorElement
+/* newvar{svgvar00005:SVGFEMergeElement} */ var svgvar00005 = document.getElementById("svgvar00005"); //SVGFEMergeElement
+/* newvar{svgvar00006:SVGAnimateTransformElement} */ var svgvar00006 = document.getElementById("svgvar00006"); //SVGAnimateTransformElement
+/* newvar{svgvar00007:SVGTSpanElement} */ var svgvar00007 = document.getElementById("svgvar00007"); //SVGTSpanElement
+/* newvar{svgvar00008:SVGCircleElement} */ var svgvar00008 = document.getElementById("svgvar00008"); //SVGCircleElement
+/* newvar{svgvar00009:SVGAnimateElement} */ var svgvar00009 = document.getElementById("svgvar00009"); //SVGAnimateElement
+/* newvar{svgvar00010:SVGAnimateTransformElement} */ var svgvar00010 = document.getElementById("svgvar00010"); //SVGAnimateTransformElement
+/* newvar{svgvar00011:SVGFEConvolveMatrixElement} */ var svgvar00011 = document.getElementById("svgvar00011"); //SVGFEConvolveMatrixElement
+/* newvar{svgvar00012:SVGElement} */ var svgvar00012 = document.getElementById("svgvar00012"); //SVGElement
+/* newvar{htmlvar00013:HTMLMarqueeElement} */ var htmlvar00013 = document.getElementById("htmlvar00013"); //HTMLMarqueeElement
+/* newvar{htmlvar00014:HTMLUListElement} */ var htmlvar00014 = document.getElementById("htmlvar00014"); //HTMLUListElement
+/* newvar{htmlvar00015:HTMLLIElement} */ var htmlvar00015 = document.getElementById("htmlvar00015"); //HTMLLIElement
+/* newvar{htmlvar00016:HTMLBaseElement} */ var htmlvar00016 = document.getElementById("htmlvar00016"); //HTMLBaseElement
+/* newvar{htmlvar00017:HTMLOListElement} */ var htmlvar00017 = document.getElementById("htmlvar00017"); //HTMLOListElement
+/* newvar{htmlvar00018:HTMLLIElement} */ var htmlvar00018 = document.getElementById("htmlvar00018"); //HTMLLIElement
+/* newvar{htmlvar00019:HTMLDirectoryElement} */ var htmlvar00019 = document.getElementById("htmlvar00019"); //HTMLDirectoryElement
+/* newvar{htmlvar00020:HTMLShadowElement} */ var htmlvar00020 = document.getElementById("htmlvar00020"); //HTMLShadowElement
+/* newvar{htmlvar00021:HTMLUnknownElement} */ var htmlvar00021 = document.getElementById("htmlvar00021"); //HTMLUnknownElement
+/* newvar{htmlvar00022:HTMLLIElement} */ var htmlvar00022 = document.getElementById("htmlvar00022"); //HTMLLIElement
+/* newvar{htmlvar00023:HTMLDetailsElement} */ var htmlvar00023 = document.getElementById("htmlvar00023"); //HTMLDetailsElement
+/* newvar{htmlvar00024:HTMLVideoElement} */ var htmlvar00024 = document.getElementById("htmlvar00024"); //HTMLVideoElement
+/* newvar{htmlvar00025:HTMLTimeElement} */ var htmlvar00025 = document.getElementById("htmlvar00025"); //HTMLTimeElement
+/* newvar{htmlvar00026:HTMLUnknownElement} */ var htmlvar00026 = document.getElementById("htmlvar00026"); //HTMLUnknownElement
+/* newvar{htmlvar00027:HTMLTimeElement} */ var htmlvar00027 = document.getElementById("htmlvar00027"); //HTMLTimeElement
+/* newvar{htmlvar00028:HTMLUnknownElement} */ var htmlvar00028 = document.createElement("noembed"); //HTMLUnknownElement
+/* newvar{htmlvar00029:HTMLProgressElement} */ var htmlvar00029 = document.createElement("progress"); //HTMLProgressElement
+/* newvar{htmlvar00030:HTMLMapElement} */ var htmlvar00030 = document.createElement("map"); //HTMLMapElement
+/* newvar{htmlvar00031:HTMLTextAreaElement} */ var htmlvar00031 = document.createElement("textarea"); //HTMLTextAreaElement
+/* newvar{htmlvar00032:HTMLUnknownElement} */ var htmlvar00032 = document.createElement("html"); //HTMLUnknownElement
+try { svgvar00010.setAttribute("to", "red"); } catch(e) { }
+try { htmlvar00002.setAttribute("onautocomplete", "eventhandler3()"); } catch(e) { }
+try { window.getComputedStyle(htmlvar00016,"foo"); } catch(e) { }
+try { htmlvar00009.setAttribute("azimuth", "0"); } catch(e) { }
+try { /* newvar{var00001:CustomElementRegistry} */ var var00001 = window.customElements; } catch(e) { }
+try { if (!var00001) { var00001 = GetVariable(fuzzervars, 'CustomElementRegistry'); } else { SetVariable(var00001, 'CustomElementRegistry'); } } catch(e) { }
+try { svgvar00011.setAttribute("z", "11"); } catch(e) { }
+try { /* newvar{var00019:CSSStyleDeclaration} */ var var00019 = svgvar00012.style; } catch(e) { }
+try { if (!var00019) { var00019 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00019, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00018:CSSRule} */ var var00018 = var00019.parentRule; } catch(e) { }
+try { if (!var00018) { var00018 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00018, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00017:CSSViewportRule} */ var var00017 = var00018; } catch(e) { }
+try { if (!var00017) { var00017 = GetVariable(fuzzervars, 'CSSViewportRule'); } else { SetVariable(var00017, 'CSSViewportRule'); SetVariable(var00017, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00016:CSSRule} */ var var00016 = var00017; } catch(e) { }
+try { if (!var00016) { var00016 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00016, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00015:CSSFontFaceRule} */ var var00015 = var00016; } catch(e) { }
+try { if (!var00015) { var00015 = GetVariable(fuzzervars, 'CSSFontFaceRule'); } else { SetVariable(var00015, 'CSSFontFaceRule'); SetVariable(var00015, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00014:CSSRule} */ var var00014 = var00015; } catch(e) { }
+try { if (!var00014) { var00014 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00014, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00013:CSSKeyframesRule} */ var var00013 = var00014; } catch(e) { }
+try { if (!var00013) { var00013 = GetVariable(fuzzervars, 'CSSKeyframesRule'); } else { SetVariable(var00013, 'CSSKeyframesRule'); SetVariable(var00013, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00012:CSSRule} */ var var00012 = var00013; } catch(e) { }
+try { if (!var00012) { var00012 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00012, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00011:CSSImportRule} */ var var00011 = var00012; } catch(e) { }
+try { if (!var00011) { var00011 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00011, 'CSSImportRule'); SetVariable(var00011, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00010:CSSStyleSheet} */ var var00010 = var00011.styleSheet; } catch(e) { }
+try { if (!var00010) { var00010 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00010, 'CSSStyleSheet'); SetVariable(var00010, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00009:CSSRuleList} */ var var00009 = var00010.rules; } catch(e) { }
+try { if (!var00009) { var00009 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00009, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00008:CSSRule} */ var var00008 = var00009.item(44%var00009.length); } catch(e) { }
+try { if (!var00008) { var00008 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00008, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00007:CSSFontFaceRule} */ var var00007 = var00008; } catch(e) { }
+try { if (!var00007) { var00007 = GetVariable(fuzzervars, 'CSSFontFaceRule'); } else { SetVariable(var00007, 'CSSFontFaceRule'); SetVariable(var00007, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00006:CSSRule} */ var var00006 = var00007; } catch(e) { }
+try { if (!var00006) { var00006 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00006, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00005:CSSStyleSheet} */ var var00005 = var00006.parentStyleSheet; } catch(e) { }
+try { if (!var00005) { var00005 = GetVariable(fuzzervars, 'CSSStyleSheet'); } else { SetVariable(var00005, 'CSSStyleSheet'); SetVariable(var00005, 'StyleSheet'); } } catch(e) { }
+try { /* newvar{var00004:CSSRule} */ var var00004 = var00005.ownerRule; } catch(e) { }
+try { if (!var00004) { var00004 = GetVariable(fuzzervars, 'CSSRule'); } else { SetVariable(var00004, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00003:CSSImportRule} */ var var00003 = var00004; } catch(e) { }
+try { if (!var00003) { var00003 = GetVariable(fuzzervars, 'CSSImportRule'); } else { SetVariable(var00003, 'CSSImportRule'); SetVariable(var00003, 'CSSRule'); } } catch(e) { }
+try { /* newvar{var00002:DOMString} */ var var00002 = var00003.href; } catch(e) { }
+try { htmlvar00007.rel = "tag"; } catch(e) { }
+try { /* newvar{var00020:Element} */ var var00020 = htmlvar00009; } catch(e) { }
+try { if (!var00020) { var00020 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00020, 'Element'); SetVariable(var00020, 'GlobalEventHandlers'); SetVariable(var00020, 'EventTarget'); } } catch(e) { }
+try { svgvar00005.scrollIntoView(); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00021:EventHandler} */ var var00021 = htmlvar00002.onselectstart; } catch(e) { }
+try { if (!var00021) { var00021 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00021, 'EventHandler'); } } catch(e) { }
+try { document.onfullscreenerror = var00021; } catch(e) { }
+try { /* newvar{var00022:long} */ var var00022 = htmlvar00001.webkitDroppedFrameCount; } catch(e) { }
+try { htmlvar00007.setAttribute("width", "9"); } catch(e) { }
+try { svgvar00010.onrepeat = var00021; } catch(e) { }
+try { svgvar00003.setAttribute("scale", "16"); } catch(e) { }
+try { /* newvar{var00023:Element} */ var var00023 = htmlvar00031.firstElementChild; } catch(e) { }
+try { if (!var00023) { var00023 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00023, 'Element'); SetVariable(var00023, 'GlobalEventHandlers'); SetVariable(var00023, 'EventTarget'); } } catch(e) { }
+try { window.onwebkittransitionend = var00021; } catch(e) { }
+try { window.oncuechange = var00021; } catch(e) { }
+try { htmlvar00002.setPointerCapture(0); } catch(e) { }
+try { htmlvar00021.setAttribute("low", "0"); } catch(e) { }
+try { svgvar00004.onsuspend = var00021; } catch(e) { }
+try { /* newvar{var00025:eventhandler} */ var var00025 = eventhandler1; } catch(e) { }
+try { if (!var00025) { var00025 = GetVariable(fuzzervars, 'eventhandler'); } else { SetVariable(var00025, 'eventhandler'); } } catch(e) { }
+try { /* newvar{var00024:ScrollStateCallback} */ var var00024 = var00025; } catch(e) { }
+try { if (!var00024) { var00024 = GetVariable(fuzzervars, 'ScrollStateCallback'); } else { SetVariable(var00024, 'ScrollStateCallback'); } } catch(e) { }
+try { /* newvar{var00026:NativeScrollBehavior} */ var var00026 = "disable-native-scroll"; } catch(e) { }
+try { if (!var00026) { var00026 = GetVariable(fuzzervars, 'NativeScrollBehavior'); } else { SetVariable(var00026, 'NativeScrollBehavior'); } } catch(e) { }
+try { svgvar00004.setApplyScroll(var00024,var00026); } catch(e) { }
+try { htmlvar00007.media = "handheld"; } catch(e) { }
+try { htmlvar00014.setAttribute("mayscript", "false"); } catch(e) { }
+try { /* newvar{var00030:SVGMatrix} */ var var00030 = svgvar00008.getScreenCTM(); } catch(e) { }
+try { if (!var00030) { var00030 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00030, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00029:SVGMatrix} */ var var00029 = var00030.skewX(0.199655901142); } catch(e) { }
+try { if (!var00029) { var00029 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00029, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00031:SVGMatrix} */ var var00031 = var00030.scale(0.19700934561); } catch(e) { }
+try { if (!var00031) { var00031 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00031, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00028:SVGMatrix} */ var var00028 = var00029.multiply(var00031); } catch(e) { }
+try { if (!var00028) { var00028 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00028, 'SVGMatrix'); } } catch(e) { }
+try { /* newvar{var00027:SVGMatrix} */ var var00027 = var00028.flipY(); } catch(e) { }
+try { if (!var00027) { var00027 = GetVariable(fuzzervars, 'SVGMatrix'); } else { SetVariable(var00027, 'SVGMatrix'); } } catch(e) { }
+try { htmlvar00029.setAttribute("right", "10"); } catch(e) { }
+try { svgvar00010.setAttribute("preserveAlpha", "true"); } catch(e) { }
+try { var00019.setProperty("position", "fixed"); } catch(e) { }
+try { /* newvar{var00033:HTMLTableColElement} */ var var00033 = document.createElement("col"); } catch(e) { }
+try { if (!var00033) { var00033 = GetVariable(fuzzervars, 'HTMLTableColElement'); } else { SetVariable(var00033, 'HTMLTableColElement'); SetVariable(var00033, 'Element'); SetVariable(var00033, 'GlobalEventHandlers'); SetVariable(var00033, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00032:DOMString} */ var var00032 = var00033.chOff; } catch(e) { }
+try { var00019.setProperty("animation-timing-function", "linear"); } catch(e) { }
+try { svgvar00012.setAttribute("startOffset", "15%"); } catch(e) { }
+try { svgvar00002.scroll(0.678139952356,0.312838581415); } catch(e) { }
+try { svgvar00002.setAttribute("preserveAspectRatio", "xMinYMax meet"); } catch(e) { }
+try { svgvar00005.setAttribute("gradientTransform", "matrix(0.0828699575272 0.178580529383 0.739036586069 0.142687235684 1 89)"); } catch(e) { }
+try { /* newvar{var00035:URL} */ var var00035 = new URL("http://foo/bar"); } catch(e) { }
+try { if (!var00035) { var00035 = GetVariable(fuzzervars, 'URL'); } else { SetVariable(var00035, 'URL'); } } catch(e) { }
+try { /* newvar{var00034:USVString} */ var var00034 = var00035.port; } catch(e) { }
+try { if (!var00034) { var00034 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00034, 'USVString'); } } catch(e) { }
+try { htmlvar00012.href = var00034; } catch(e) { }
+try { htmlvar00001.setAttribute("formnovalidate", "formnovalidate"); } catch(e) { }
+try { var00019.setProperty("offset-position", "auto"); } catch(e) { }
+try { var00019.setProperty("-webkit-tap-highlight-color", "rgb(15,226,163)"); } catch(e) { }
+try { htmlvar00022.setAttribute("aria-labeledby", "htmlvar00007"); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { /* newvar{var00038:HTMLAudioElement} */ var var00038 = document.createElement("audio"); } catch(e) { }
+try { if (!var00038) { var00038 = GetVariable(fuzzervars, 'HTMLAudioElement'); } else { SetVariable(var00038, 'HTMLAudioElement'); SetVariable(var00038, 'HTMLMediaElement'); SetVariable(var00038, 'Element'); SetVariable(var00038, 'GlobalEventHandlers'); SetVariable(var00038, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00037:HTMLMediaElement} */ var var00037 = var00038; } catch(e) { }
+try { if (!var00037) { var00037 = GetVariable(fuzzervars, 'HTMLMediaElement'); } else { SetVariable(var00037, 'HTMLMediaElement'); SetVariable(var00037, 'Element'); SetVariable(var00037, 'GlobalEventHandlers'); SetVariable(var00037, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00036:boolean} */ var var00036 = var00037.defaultMuted; } catch(e) { }
+try { /* newvar{var00041:NodeIterator} */ var var00041 = document.createNodeIterator(htmlvar00031); } catch(e) { }
+try { if (!var00041) { var00041 = GetVariable(fuzzervars, 'NodeIterator'); } else { SetVariable(var00041, 'NodeIterator'); } } catch(e) { }
+try { /* newvar{var00040:NodeFilter} */ var var00040 = var00041.filter; } catch(e) { }
+try { if (!var00040) { var00040 = GetVariable(fuzzervars, 'NodeFilter'); } else { SetVariable(var00040, 'NodeFilter'); } } catch(e) { }
+try { /* newvar{var00039:TreeWalker} */ var var00039 = document.createTreeWalker(htmlvar00021,1,var00040); } catch(e) { }
+try { if (!var00039) { var00039 = GetVariable(fuzzervars, 'TreeWalker'); } else { SetVariable(var00039, 'TreeWalker'); } } catch(e) { }
+try { htmlvar00022.setAttribute("aria-haspopup", "false"); } catch(e) { }
+try { /* newvar{var00049:HTMLMediaElement} */ var var00049 = htmlvar00024; } catch(e) { }
+try { if (!var00049) { var00049 = GetVariable(fuzzervars, 'HTMLMediaElement'); } else { SetVariable(var00049, 'HTMLMediaElement'); SetVariable(var00049, 'Element'); SetVariable(var00049, 'GlobalEventHandlers'); SetVariable(var00049, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00048:TextTrackList} */ var var00048 = var00049.textTracks; } catch(e) { }
+try { if (!var00048) { var00048 = GetVariable(fuzzervars, 'TextTrackList'); } else { SetVariable(var00048, 'TextTrackList'); SetVariable(var00048, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00047:TextTrack} */ var var00047 = var00048[97%var00048.length]; } catch(e) { }
+try { if (!var00047) { var00047 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00047, 'TextTrack'); SetVariable(var00047, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00046:TextTrackKind} */ var var00046 = var00047.kind; } catch(e) { }
+try { if (!var00046) { var00046 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00046, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00045:TextTrack} */ var var00045 = var00038.addTextTrack(var00046,String.fromCharCode(84, 35, 96, 69, 116, 34, 106, 62, 50, 59, 63, 126, 96, 45, 34, 51, 89, 110, 84, 117)); } catch(e) { }
+try { if (!var00045) { var00045 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00045, 'TextTrack'); SetVariable(var00045, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00044:TextTrackKind} */ var var00044 = var00045.kind; } catch(e) { }
+try { if (!var00044) { var00044 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00044, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00043:TextTrack} */ var var00043 = htmlvar00024.addTextTrack(var00044,"1","htmlvar00005"); } catch(e) { }
+try { if (!var00043) { var00043 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00043, 'TextTrack'); SetVariable(var00043, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00042:TextTrackKind} */ var var00042 = var00043.kind; } catch(e) { }
+try { if (!var00042) { var00042 = GetVariable(fuzzervars, 'TextTrackKind'); } else { SetVariable(var00042, 'TextTrackKind'); } } catch(e) { }
+try { /* newvar{var00050:boolean} */ var var00050 = document.execCommand("selectAll", false); } catch(e) { }
+try { svgvar00005.setAttribute("x", "49%"); } catch(e) { }
+try { /* newvar{var00051:StaticRange} */ var var00051 = sequence_StaticRange[0]; } catch(e) { }
+try { if (!var00051) { var00051 = GetVariable(fuzzervars, 'StaticRange'); } else { SetVariable(var00051, 'StaticRange'); } } catch(e) { }
+try { var00051.startContainer = htmlvar00027; } catch(e) { }
+try { document.xmlStandalone = true; } catch(e) { }
+try { /* newvar{var00052:EventHandler} */ var var00052 = htmlvar00009.onpointerup; } catch(e) { }
+try { if (!var00052) { var00052 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00052, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00053:boolean} */ var var00053 = document.queryCommandIndeterm("1"); } catch(e) { }
+try { htmlvar00017.compact = true; } catch(e) { }
+try { htmlvar00006.setAttribute("onwheel", "eventhandler5()"); } catch(e) { }
+try { /* newvar{var00054:long} */ var var00054 = htmlvar00006.webkitVideoDecodedByteCount; } catch(e) { }
+try { var00019.setProperty("transform-style", "initial"); } catch(e) { }
+try { var00038.setAttribute("compact", "compact"); } catch(e) { }
+try { htmlvar00005.max = "0"; } catch(e) { }
+try { var00019.setProperty("mso-protection", "locked visible"); } catch(e) { }
+try { var00023.setAttribute("onbeforecut", "eventhandler2()"); } catch(e) { }
+try { /* newvar{var00055:Element} */ var var00055 = var00039.parentNode(); } catch(e) { }
+try { if (!var00055) { var00055 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00055, 'Element'); SetVariable(var00055, 'GlobalEventHandlers'); SetVariable(var00055, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00057:URL} */ var var00057 = new URL("http://foo/bar"); } catch(e) { }
+try { if (!var00057) { var00057 = GetVariable(fuzzervars, 'URL'); } else { SetVariable(var00057, 'URL'); } } catch(e) { }
+try { /* newvar{var00056:USVString} */ var var00056 = var00057.hash; } catch(e) { }
+try { if (!var00056) { var00056 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00056, 'USVString'); } } catch(e) { }
+try { htmlvar00009.setAttribute("low", "55"); } catch(e) { }
+try { htmlvar00012.username = var00034; } catch(e) { }
+try { /* newvar{var00058:long} */ var var00058 = htmlvar00013.scrollDelay; } catch(e) { }
+try { /* newvar{var00059:EventHandler} */ var var00059 = window.onwebkitanimationiteration; } catch(e) { }
+try { if (!var00059) { var00059 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00059, 'EventHandler'); } } catch(e) { }
+try { htmlvar00032.setAttribute("shouldfocus", "false"); } catch(e) { }
+try { /* newvar{var00060:boolean} */ var var00060 = htmlvar00003.spellcheck; } catch(e) { }
+try { /* newvar{var00061:EventHandler} */ var var00061 = var00038.onstalled; } catch(e) { }
+try { if (!var00061) { var00061 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00061, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00062:EventHandler} */ var var00062 = htmlvar00007.onerror; } catch(e) { }
+try { if (!var00062) { var00062 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00062, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00063:long} */ var var00063 = htmlvar00005.selectionStart; } catch(e) { }
+try { /* newvar{var00064:Range} */ var var00064 = document.caretRangeFromPoint(350,28); } catch(e) { }
+try { if (!var00064) { var00064 = GetVariable(fuzzervars, 'Range'); } else { SetVariable(var00064, 'Range'); } } catch(e) { }
+try { var00064.setEnd(htmlvar00026,0); } catch(e) { }
+try { /* newvar{var00065:EventHandler} */ var var00065 = htmlvar00020.ontoggle; } catch(e) { }
+try { if (!var00065) { var00065 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00065, 'EventHandler'); } } catch(e) { }
+try { var00033.span = 21; } catch(e) { }
+try { /* newvar{var00066:Element} */ var var00066 = var00064.endContainer; } catch(e) { }
+try { if (!var00066) { var00066 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00066, 'Element'); SetVariable(var00066, 'GlobalEventHandlers'); SetVariable(var00066, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00067:CSSRuleList} */ var var00067 = var00005.rules; } catch(e) { }
+try { if (!var00067) { var00067 = GetVariable(fuzzervars, 'CSSRuleList'); } else { SetVariable(var00067, 'CSSRuleList'); } } catch(e) { }
+try { /* newvar{var00068:boolean} */ var var00068 = var00049.hasChildNodes(); } catch(e) { }
+try { var00064.insertNode(htmlvar00012); } catch(e) { }
+try { /* newvar{var00069:boolean} */ var var00069 = htmlvar00002.hasAttributeNS("http://www.w3.org/1999/xhtml","accepts-touch"); } catch(e) { }
+try { /* newvar{var00070:long} */ var var00070 = window.orientation; } catch(e) { }
+try { document.all[9%document.all.length].appendChild(htmlvar00012); } catch(e) { }
+try { htmlvar00012.username = var00056; } catch(e) { }
+try { var00045.oncuechange = var00059; } catch(e) { }
+try { htmlvar00008.setAttribute("onkeyup", "eventhandler2()"); } catch(e) { }
+try { htmlvar00032.setAttribute("aria-controls", "htmlvar00008"); } catch(e) { }
+try { svgvar00009.onplaying = var00021; } catch(e) { }
+try { /* newvar{var00071:boolean} */ var var00071 = document.execCommand("fontSize", false, 6); } catch(e) { }
+try { /* newvar{var00072:ClientRect} */ var var00072 = svgvar00001.getBoundingClientRect(); } catch(e) { }
+try { if (!var00072) { var00072 = GetVariable(fuzzervars, 'ClientRect'); } else { SetVariable(var00072, 'ClientRect'); } } catch(e) { }
+try { /* newvar{var00074:MessageChannel} */ var var00074 = new MessageChannel(); } catch(e) { }
+try { if (!var00074) { var00074 = GetVariable(fuzzervars, 'MessageChannel'); } else { SetVariable(var00074, 'MessageChannel'); } } catch(e) { }
+try { /* newvar{var00073:MessagePort} */ var var00073 = var00074.port1; } catch(e) { }
+try { if (!var00073) { var00073 = GetVariable(fuzzervars, 'MessagePort'); } else { SetVariable(var00073, 'MessagePort'); SetVariable(var00073, 'EventTarget'); } } catch(e) { }
+try { var00073.onmessage = var00059; } catch(e) { }
+try { var00064.expand("1"); } catch(e) { }
+try { /* newvar{var00075:Element} */ var var00075 = var00020.lastElementChild; } catch(e) { }
+try { if (!var00075) { var00075 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00075, 'Element'); SetVariable(var00075, 'GlobalEventHandlers'); SetVariable(var00075, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00076:Element} */ var var00076 = var00010.ownerNode; } catch(e) { }
+try { if (!var00076) { var00076 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00076, 'Element'); SetVariable(var00076, 'GlobalEventHandlers'); SetVariable(var00076, 'EventTarget'); } } catch(e) { }
+try { svgvar00008.setAttribute("fy", "63%"); } catch(e) { }
+try { /* newvar{var00077:HTMLCollection} */ var var00077 = htmlvar00005.getElementsByTagName("tt"); } catch(e) { }
+try { if (!var00077) { var00077 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00077, 'HTMLCollection'); } } catch(e) { }
+try { htmlvar00018.setAttribute("aria-orientation", "vertical"); } catch(e) { }
+try { htmlvar00015.setAttribute("onerror", "eventhandler4()"); } catch(e) { }
+try { document.all[84%document.all.length].appendChild(htmlvar00005); } catch(e) { }
+try { htmlvar00028.setAttribute("rel", "next"); } catch(e) { }
+try { /* newvar{var00078:EventHandler} */ var var00078 = svgvar00008.ongotpointercapture; } catch(e) { }
+try { if (!var00078) { var00078 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00078, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00079:Element} */ var var00079 = htmlvar00011; } catch(e) { }
+try { if (!var00079) { var00079 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00079, 'Element'); SetVariable(var00079, 'GlobalEventHandlers'); SetVariable(var00079, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00080:USVString} */ var var00080 = var00035.hash; } catch(e) { }
+try { if (!var00080) { var00080 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00080, 'USVString'); } } catch(e) { }
+try { svgvar00004.onwheel = var00021; } catch(e) { }
+try { /* newvar{var00081:DOMString} */ var var00081 = document.domain; } catch(e) { }
+try { svgvar00009.setAttribute("vector-effect", "non-scaling-stroke"); } catch(e) { }
+try { var00019.setProperty("overflow-y", "scroll"); } catch(e) { }
+try { /* newvar{var00083:Window} */ var var00083 = window.parent; } catch(e) { }
+try { if (!var00083) { var00083 = GetVariable(fuzzervars, 'Window'); } else { SetVariable(var00083, 'Window'); SetVariable(var00083, 'GlobalEventHandlers'); SetVariable(var00083, 'WindowBase64'); SetVariable(var00083, 'WindowEventHandlers'); SetVariable(var00083, 'WindowTimers'); SetVariable(var00083, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00082:Touch} */ var var00082 = document.createTouch(var00083,htmlvar00020,0,0.460422272452,0.598406338072,0.275428674269,0.930324980552,0.54669025425); } catch(e) { }
+try { if (!var00082) { var00082 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00082, 'Touch'); } } catch(e) { }
+try { /* newvar{var00084:DOMString} */ var var00084 = htmlvar00022.id; } catch(e) { }
+try { /* newvar{var00085:long} */ var var00085 = htmlvar00019.offsetLeft; } catch(e) { }
+try { document.all[20%document.all.length].appendChild(htmlvar00025); } catch(e) { }
+try { svgvar00010.endElementAt(0.0471852275914); } catch(e) { }
+try { htmlvar00018.setAttribute("aria-dropeffect", "none"); } catch(e) { }
+try { svgvar00007.onmouseenter = var00059; } catch(e) { }
+try { var00019.setProperty("page", "Auto"); } catch(e) { }
+try { /* newvar{var00086:Element} */ var var00086 = var00039.root; } catch(e) { }
+try { if (!var00086) { var00086 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00086, 'Element'); SetVariable(var00086, 'GlobalEventHandlers'); SetVariable(var00086, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00087:DOMString} */ var var00087 = htmlvar00002.kind; } catch(e) { }
+try { var00076.onemptied = var00062; } catch(e) { }
+try { /* newvar{var00088:EventHandler} */ var var00088 = window.ononline; } catch(e) { }
+try { if (!var00088) { var00088 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00088, 'EventHandler'); } } catch(e) { }
+try { htmlvar00031.selectionEnd = 1; } catch(e) { }
+try { /* newvar{var00089:ProcessingInstruction} */ var var00089 = document.createProcessingInstruction(String.fromCodePoint(510836, 986823, 525591, 817684, 647088, 603392, 1098651, 986921, 462888, 984773, 668851, 853304, 717468, 918749, 132893, 54447, 824723, 742274, 999236, 313997),"foo"); } catch(e) { }
+try { if (!var00089) { var00089 = GetVariable(fuzzervars, 'ProcessingInstruction'); } else { SetVariable(var00089, 'ProcessingInstruction'); SetVariable(var00089, 'CharacterData'); SetVariable(var00089, 'Element'); SetVariable(var00089, 'GlobalEventHandlers'); SetVariable(var00089, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00090:URLSearchParams} */ var var00090 = var00057.searchParams; } catch(e) { }
+try { if (!var00090) { var00090 = GetVariable(fuzzervars, 'URLSearchParams'); } else { SetVariable(var00090, 'URLSearchParams'); } } catch(e) { }
+try { /* newvar{var00091:boolean} */ var var00091 = document.hasFocus(); } catch(e) { }
+try { var00019.setProperty("mso-font-kerning", "0pt"); } catch(e) { }
+try { document.all[47%document.all.length].appendChild(htmlvar00002); } catch(e) { }
+try { /* newvar{var00092:long} */ var var00092 = var00010.insertRule("foo"); } catch(e) { }
+try { htmlvar00003.encoding = "text/html"; } catch(e) { }
+try { /* newvar{var00093:DOMString} */ var var00093 = htmlvar00019.lookupNamespaceURI("foo"); } catch(e) { }
+try { var00019.setProperty("column-gap", "0px"); } catch(e) { }
+try { svgvar00011.onpointerdown = var00078; } catch(e) { }
+try { /* newvar{var00094:TextTrack} */ var var00094 = htmlvar00006.addTextTrack(var00042); } catch(e) { }
+try { if (!var00094) { var00094 = GetVariable(fuzzervars, 'TextTrack'); } else { SetVariable(var00094, 'TextTrack'); SetVariable(var00094, 'EventTarget'); } } catch(e) { }
+try { htmlvar00020.setAttribute("novalidate", "novalidate"); } catch(e) { }
+try { svgvar00005.onpaste = var00065; } catch(e) { }
+try { /* newvar{var00095:DOMString} */ var var00095 = htmlvar00005.formMethod; } catch(e) { }
+try { htmlvar00014.setAttribute("formtarget", "htmlvar00008"); } catch(e) { }
+try { htmlvar00026.setAttribute("start", "1"); } catch(e) { }
+try { var00019.setProperty("-webkit-margin-after", "0px"); } catch(e) { }
+try { /* newvar{var00096:boolean} */ var var00096 = svgvar00007.contains(svgvar00010); } catch(e) { }
+try { htmlvar00007.as = "track"; } catch(e) { }
+try { htmlvar00006.onselectstart = var00088; } catch(e) { }
+try { svgvar00008.setAttribute("flood-color", "Highlight"); } catch(e) { }
+try { /* newvar{var00097:DOMStringMap} */ var var00097 = svgvar00004.dataset; } catch(e) { }
+try { if (!var00097) { var00097 = GetVariable(fuzzervars, 'DOMStringMap'); } else { SetVariable(var00097, 'DOMStringMap'); } } catch(e) { }
+try { freememory(); } catch(e) { }
+try { var00019.setProperty("-webkit-box-sizing", "content-box"); } catch(e) { }
+try { /* newvar{var00098:Element} */ var var00098 = var00041.root; } catch(e) { }
+try { if (!var00098) { var00098 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00098, 'Element'); SetVariable(var00098, 'GlobalEventHandlers'); SetVariable(var00098, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00099:long} */ var var00099 = htmlvar00009.selectionStart; } catch(e) { }
+try { /* newvar{var00100:boolean} */ var var00100 = document.execCommand("superscript", false); } catch(e) { }
+try { /* newvar{var00101:DOMString} */ var var00101 = htmlvar00009.selectionDirection; } catch(e) { }
+try { var00019.setProperty("-webkit-column-rule", "solid black"); } catch(e) { }
+try { svgvar00008.setAttribute("aria-label", "left-eye"); } catch(e) { }
+try { /* newvar{var00102:SVGAnimatedEnumeration} */ var var00102 = svgvar00011.edgeMode; } catch(e) { }
+try { if (!var00102) { var00102 = GetVariable(fuzzervars, 'SVGAnimatedEnumeration'); } else { SetVariable(var00102, 'SVGAnimatedEnumeration'); } } catch(e) { }
+try { var00073.close(); } catch(e) { }
+try { htmlvar00028.setAttribute("background", "" + String.fromCharCode(40, 126, 83, 45, 113, 114, 68, 61, 80, 125, 63, 57, 107, 96, 66, 38, 56, 67, 59, 120) + ""); } catch(e) { }
+try { var00019.setProperty("border-top", "1em rgb(106,212,89) solid"); } catch(e) { }
+try { var00019.setProperty("-webkit-animation-iteration-count", "infinite"); } catch(e) { }
+try { htmlvar00013.hspace = 60; } catch(e) { }
+try { var00019.setProperty("font-weight", "lighter"); } catch(e) { }
+try { svgvar00004.setAttribute("by", "1 2 33 0 0 0 0 0 0"); } catch(e) { }
+try { /* newvar{var00103:short} */ var var00103 = var00040.acceptNode(htmlvar00014); } catch(e) { }
+try { svgvar00003.setAttribute("preserveAlpha", "true"); } catch(e) { }
+try { /* newvar{var00104:SVGGraphicsElement} */ var var00104 = svgvar00008; } catch(e) { }
+try { if (!var00104) { var00104 = GetVariable(fuzzervars, 'SVGGraphicsElement'); } else { SetVariable(var00104, 'SVGGraphicsElement'); SetVariable(var00104, 'SVGElement'); SetVariable(var00104, 'GlobalEventHandlers'); SetVariable(var00104, 'EventTarget'); SetVariable(var00104, 'GlobalEventHandlers'); } } catch(e) { }
+try { var00019.setProperty("scale", "0 0"); } catch(e) { }
+try { /* newvar{var00105:Element} */ var var00105 = htmlvar00015.previousSibling; } catch(e) { }
+try { if (!var00105) { var00105 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00105, 'Element'); SetVariable(var00105, 'GlobalEventHandlers'); SetVariable(var00105, 'EventTarget'); } } catch(e) { }
+try { var00019.setProperty("stroke-opacity", "1"); } catch(e) { }
+try { /* newvar{var00106:long} */ var var00106 = var00051.startOffset; } catch(e) { }
+try { /* newvar{var00107:DOMString} */ var var00107 = htmlvar00007.as; } catch(e) { }
+try { /* newvar{var00108:DOMString} */ var var00108 = var00105.className; } catch(e) { }
+try { htmlvar00017.setAttribute("valign", "bottom"); } catch(e) { }
+try { var00019.setProperty("-webkit-transition-delay", "0s"); } catch(e) { }
+try { /* newvar{var00109:SVGElement} */ var var00109 = svgvar00008.lastChild; } catch(e) { }
+try { if (!var00109) { var00109 = GetVariable(fuzzervars, 'SVGElement'); } else { SetVariable(var00109, 'SVGElement'); SetVariable(var00109, 'GlobalEventHandlers'); SetVariable(var00109, 'EventTarget'); SetVariable(var00109, 'GlobalEventHandlers'); } } catch(e) { }
+try { svgvar00009.setAttribute("fill", "url(#svgvar00002) red"); } catch(e) { }
+try { var00019.setProperty("transform-origin", "inherit"); } catch(e) { }
+try { /* newvar{var00110:Document} */ var var00110 = svgvar00004.ownerDocument; } catch(e) { }
+try { if (!var00110) { var00110 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00110, 'Document'); SetVariable(var00110, 'GlobalEventHandlers'); SetVariable(var00110, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00113:DOMImplementation} */ var var00113 = document.implementation; } catch(e) { }
+try { if (!var00113) { var00113 = GetVariable(fuzzervars, 'DOMImplementation'); } else { SetVariable(var00113, 'DOMImplementation'); } } catch(e) { }
+try { /* newvar{var00112:Document} */ var var00112 = var00113.createHTMLDocument(); } catch(e) { }
+try { if (!var00112) { var00112 = GetVariable(fuzzervars, 'Document'); } else { SetVariable(var00112, 'Document'); SetVariable(var00112, 'GlobalEventHandlers'); SetVariable(var00112, 'DocumentOrShadowRoot'); } } catch(e) { }
+try { /* newvar{var00111:DOMString} */ var var00111 = var00112.charset; } catch(e) { }
+try { /* newvar{var00114:HTMLFrameSetElement} */ var var00114 = document.createElement("frameset"); } catch(e) { }
+try { if (!var00114) { var00114 = GetVariable(fuzzervars, 'HTMLFrameSetElement'); } else { SetVariable(var00114, 'HTMLFrameSetElement'); SetVariable(var00114, 'WindowEventHandlers'); SetVariable(var00114, 'Element'); SetVariable(var00114, 'GlobalEventHandlers'); SetVariable(var00114, 'EventTarget'); } } catch(e) { }
+try { var00114.onscroll = var00052; } catch(e) { }
+try { var00019.setProperty("column-break-after", "always"); } catch(e) { }
+try { /* newvar{var00115:DOMString} */ var var00115 = htmlvar00005.autocapitalize; } catch(e) { }
+try { svgvar00012.setAttribute("fy", "0.00452399921661"); } catch(e) { }
+try { /* newvar{var00116:short} */ var var00116 = htmlvar00006.compareDocumentPosition(htmlvar00007); } catch(e) { }
+try { htmlvar00031.selectionDirection = String.fromCodePoint(248987, 940411, 462876, 1089181, 931282, 811298, 538161, 713169, 802536, 319980, 929536, 592398, 363951, 553408, 563545, 559760, 464321, 228374, 454178, 437099); } catch(e) { }
+try { var00075.accessKey = "" + String.fromCharCode(59) + ""; } catch(e) { }
+try { /* newvar{var00117:HTMLCollection} */ var var00117 = svgvar00012.getElementsByTagName("span"); } catch(e) { }
+try { if (!var00117) { var00117 = GetVariable(fuzzervars, 'HTMLCollection'); } else { SetVariable(var00117, 'HTMLCollection'); } } catch(e) { }
+try { /* newvar{var00118:EventHandler} */ var var00118 = var00112.onpaste; } catch(e) { }
+try { if (!var00118) { var00118 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00118, 'EventHandler'); } } catch(e) { }
+try { var00019.setProperty("-webkit-text-decorations-in-effect", "underline"); } catch(e) { }
+try { /* newvar{var00119:ShadowRoot} */ var var00119 = htmlvar00022.createShadowRoot(); } catch(e) { }
+try { if (!var00119) { var00119 = GetVariable(fuzzervars, 'ShadowRoot'); } else { SetVariable(var00119, 'ShadowRoot'); SetVariable(var00119, 'DocumentOrShadowRoot'); SetVariable(var00119, 'DocumentFragment'); SetVariable(var00119, 'Element'); SetVariable(var00119, 'GlobalEventHandlers'); SetVariable(var00119, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00120:boolean} */ var var00120 = htmlvar00031.willValidate; } catch(e) { }
+try { /* newvar{var00121:SVGAnimatedString} */ var var00121 = svgvar00011.in1; } catch(e) { }
+try { if (!var00121) { var00121 = GetVariable(fuzzervars, 'SVGAnimatedString'); } else { SetVariable(var00121, 'SVGAnimatedString'); } } catch(e) { }
+try { htmlvar00031.autocapitalize = "foo"; } catch(e) { }
+try { /* newvar{var00122:DOMString} */ var var00122 = var00019.getPropertyValue("outline"); } catch(e) { }
+try { /* newvar{var00124:XPathEvaluator} */ var var00124 = new XPathEvaluator(); } catch(e) { }
+try { if (!var00124) { var00124 = GetVariable(fuzzervars, 'XPathEvaluator'); } else { SetVariable(var00124, 'XPathEvaluator'); } } catch(e) { }
+try { /* newvar{var00126:XPathEvaluator} */ var var00126 = new XPathEvaluator(); } catch(e) { }
+try { if (!var00126) { var00126 = GetVariable(fuzzervars, 'XPathEvaluator'); } else { SetVariable(var00126, 'XPathEvaluator'); } } catch(e) { }
+try { /* newvar{var00125:XPathNSResolver} */ var var00125 = var00126.createNSResolver(htmlvar00002); } catch(e) { }
+try { if (!var00125) { var00125 = GetVariable(fuzzervars, 'XPathNSResolver'); } else { SetVariable(var00125, 'XPathNSResolver'); } } catch(e) { }
+try { /* newvar{var00123:XPathResult} */ var var00123 = var00124.evaluate("//time",htmlvar00024,var00125); } catch(e) { }
+try { if (!var00123) { var00123 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00123, 'XPathResult'); } } catch(e) { }
+try { var00019.setProperty("stroke-linecap", "round"); } catch(e) { }
+try { var00066.setAttribute("marginwidth", "0"); } catch(e) { }
+try { var00066.append("foo"); } catch(e) { }
+try { htmlvar00031.setAttribute("onmouseout", "eventhandler2()"); } catch(e) { }
+try { svgvar00011.setAttribute("viewbox", "0 0 59 0"); } catch(e) { }
+try { htmlvar00011.onselectstart = var00052; } catch(e) { }
+try { htmlvar00017.setAttribute("oncut", "eventhandler1()"); } catch(e) { }
+try { var00083.onorientationchange = var00059; } catch(e) { }
+try { /* newvar{var00127:FormData} */ var var00127 = new FormData(); } catch(e) { }
+try { if (!var00127) { var00127 = GetVariable(fuzzervars, 'FormData'); } else { SetVariable(var00127, 'FormData'); } } catch(e) { }
+try { /* newvar{var00132:InputEvent} */ var var00132 = document.createEvent("InputEvent"); } catch(e) { }
+try { if (!var00132) { var00132 = GetVariable(fuzzervars, 'InputEvent'); } else { SetVariable(var00132, 'InputEvent'); SetVariable(var00132, 'UIEvent'); SetVariable(var00132, 'Event'); } } catch(e) { }
+try { /* newvar{var00131:DataTransfer} */ var var00131 = var00132.dataTransfer; } catch(e) { }
+try { if (!var00131) { var00131 = GetVariable(fuzzervars, 'DataTransfer'); } else { SetVariable(var00131, 'DataTransfer'); } } catch(e) { }
+try { /* newvar{var00130:DataTransferItemList} */ var var00130 = var00131.items; } catch(e) { }
+try { if (!var00130) { var00130 = GetVariable(fuzzervars, 'DataTransferItemList'); } else { SetVariable(var00130, 'DataTransferItemList'); } } catch(e) { }
+try { /* newvar{var00129:DataTransferItem} */ var var00129 = var00130[15%var00130.length]; } catch(e) { }
+try { if (!var00129) { var00129 = GetVariable(fuzzervars, 'DataTransferItem'); } else { SetVariable(var00129, 'DataTransferItem'); } } catch(e) { }
+try { /* newvar{var00128:Blob} */ var var00128 = var00129.getAsFile(); } catch(e) { }
+try { if (!var00128) { var00128 = GetVariable(fuzzervars, 'Blob'); } else { SetVariable(var00128, 'Blob'); } } catch(e) { }
+try { var00127.set(var00056,var00128,var00034); } catch(e) { }
+try { /* newvar{var00133:EventListener} */ var var00133 = var00025; } catch(e) { }
+try { if (!var00133) { var00133 = GetVariable(fuzzervars, 'EventListener'); } else { SetVariable(var00133, 'EventListener'); } } catch(e) { }
+try { var00048.addEventListener("DOMSubtreeModified", var00133); } catch(e) { }
+try { var00019.setProperty("-webkit-transition-duration", "0s"); } catch(e) { }
+try { /* newvar{var00135:XPathExpression} */ var var00135 = var00124.createExpression("//blockquote",var00125); } catch(e) { }
+try { if (!var00135) { var00135 = GetVariable(fuzzervars, 'XPathExpression'); } else { SetVariable(var00135, 'XPathExpression'); } } catch(e) { }
+try { /* newvar{var00138:XPathResult} */ var var00138 = var00110.evaluate("//script",var00098); } catch(e) { }
+try { if (!var00138) { var00138 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00138, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00137:XPathResult} */ var var00137 = var00126.evaluate("//spacer",document,null,XPathResult.ANY_TYPE,var00138); } catch(e) { }
+try { if (!var00137) { var00137 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00137, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00136:XPathResult} */ var var00136 = var00135.evaluate(var00076,XPathResult.ANY_TYPE,var00137); } catch(e) { }
+try { if (!var00136) { var00136 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00136, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00134:XPathResult} */ var var00134 = var00135.evaluate(htmlvar00024,XPathResult.ANY_TYPE,var00136); } catch(e) { }
+try { if (!var00134) { var00134 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00134, 'XPathResult'); } } catch(e) { }
+try { htmlvar00026.setAttribute("title", "" + String.fromCharCode(120, 118, 67, 113, 87, 87, 116, 99, 97, 52, 52, 56, 108, 41, 126, 124, 48, 106, 105, 110) + ""); } catch(e) { }
+try { svgvar00003.scrollLeft = 0.651571072587; } catch(e) { }
+try { var00019.setProperty("-ms-user-select", "none"); } catch(e) { }
+try { var00086.addEventListener("DOMAttrModified", var00133); } catch(e) { }
+try { var00075.setAttribute("ondrop", "eventhandler3()"); } catch(e) { }
+try { /* newvar{var00139:USVString} */ var var00139 = var00057.username; } catch(e) { }
+try { if (!var00139) { var00139 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00139, 'USVString'); } } catch(e) { }
+try { var00132.initEvent(String.fromCodePoint(292513, 199607, 407692, 934874, 691637, 119228, 460297, 245197, 3986, 258584, 591949, 37830, 288652, 1090823, 348802, 359932, 11804, 163484, 174562, 829078)); } catch(e) { }
+try { htmlvar00030.setAttribute("background-color", ""); } catch(e) { }
+try { htmlvar00005.onabort = var00061; } catch(e) { }
+try { /* newvar{var00141:DOMStringList} */ var var00141 = new Array("1"); } catch(e) { }
+try { if (!var00141) { var00141 = GetVariable(fuzzervars, 'DOMStringList'); } else { SetVariable(var00141, 'DOMStringList'); } } catch(e) { }
+try { /* newvar{var00140:long} */ var var00140 = var00141.length; } catch(e) { }
+try { var00131.clearData(); } catch(e) { }
+try { htmlvar00005.setAttribute("onload", "eventhandler4()"); } catch(e) { }
+try { /* newvar{var00142:DOMString} */ var var00142 = htmlvar00005.selectionDirection; } catch(e) { }
+try { var00019.setProperty("-webkit-border-start", "1px solid purple"); } catch(e) { }
+try { /* newvar{var00143:FontFaceSet} */ var var00143 = var00112.fonts; } catch(e) { }
+try { if (!var00143) { var00143 = GetVariable(fuzzervars, 'FontFaceSet'); } else { SetVariable(var00143, 'FontFaceSet'); SetVariable(var00143, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00144:DOMString} */ var var00144 = var00129.kind; } catch(e) { }
+try { /* newvar{var00145:Element} */ var var00145 = htmlvar00014; } catch(e) { }
+try { if (!var00145) { var00145 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00145, 'Element'); SetVariable(var00145, 'GlobalEventHandlers'); SetVariable(var00145, 'EventTarget'); } } catch(e) { }
+try { htmlvar00024.setAttribute("valuetype", "ref"); } catch(e) { }
+try { htmlvar00030.setAttribute("onsubmit", "eventhandler1()"); } catch(e) { }
+try { /* newvar{var00146:EventHandler} */ var var00146 = var00110.onfullscreenerror; } catch(e) { }
+try { if (!var00146) { var00146 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00146, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00147:DOMString} */ var var00147 = var00066.lookupPrefix(String.fromCharCode(37, 42, 71, 112, 32, 66, 92, 108, 78, 105, 101, 75, 91, 36, 106, 113, 34, 86, 37, 123)); } catch(e) { }
+try { /* newvar{var00148:Attr} */ var var00148 = svgvar00012.getAttributeNode("onanimationstart"); } catch(e) { }
+try { if (!var00148) { var00148 = GetVariable(fuzzervars, 'Attr'); } else { SetVariable(var00148, 'Attr'); } } catch(e) { }
+try { var00019.setProperty("-webkit-tap-highlight-color", "white"); } catch(e) { }
+try { var00023.spellcheck = false; } catch(e) { }
+try { var00066.setAttribute("onanimationiteration", "eventhandler5()"); } catch(e) { }
+try { var00019.setProperty("--cssvarb", "black"); } catch(e) { }
+try { var00083.webkitCancelRequestAnimationFrame(3); } catch(e) { }
+try { var00019.setProperty("-webkit-box-flex", "1"); } catch(e) { }
+try { /* newvar{var00149:DOMString} */ var var00149 = var00083.name; } catch(e) { }
+try { htmlvar00021.setAttribute("preload", "auto"); } catch(e) { }
+try { /* newvar{var00150:EventListener} */ var var00150 = var00025; } catch(e) { }
+try { if (!var00150) { var00150 = GetVariable(fuzzervars, 'EventListener'); } else { SetVariable(var00150, 'EventListener'); } } catch(e) { }
+try { svgvar00006.addEventListener("DOMNodeRemovedFromDocument", var00150); } catch(e) { }
+try { /* newvar{var00151:double} */ var var00151 = window.pageYOffset; } catch(e) { }
+try { freememory(); } catch(e) { }
+try { document.all[19%document.all.length].appendChild(htmlvar00030); } catch(e) { }
+try { var00019.setProperty("-webkit-text-stroke-width", "8px"); } catch(e) { }
+try { /* newvar{var00152:USVString} */ var var00152 = htmlvar00012.protocol; } catch(e) { }
+try { if (!var00152) { var00152 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00152, 'USVString'); } } catch(e) { }
+try { var00083.onoffline = var00052; } catch(e) { }
+try { var00019.setProperty("mso-height-source", "auto"); } catch(e) { }
+try { svgvar00006.onabort = var00021; } catch(e) { }
+try { var00019.setProperty("animation-fill-mode", "forwards"); } catch(e) { }
+try { /* newvar{var00153:DOMString} */ var var00153 = htmlvar00002.kind; } catch(e) { }
+try { /* newvar{var00154:HTMLMenuElement} */ var var00154 = document.createElement("menu"); } catch(e) { }
+try { if (!var00154) { var00154 = GetVariable(fuzzervars, 'HTMLMenuElement'); } else { SetVariable(var00154, 'HTMLMenuElement'); SetVariable(var00154, 'Element'); SetVariable(var00154, 'GlobalEventHandlers'); SetVariable(var00154, 'EventTarget'); } } catch(e) { }
+try { htmlvar00008.contextMenu = var00154; } catch(e) { }
+try { svgvar00012.setAttribute("keySplines", "0"); } catch(e) { }
+try { svgvar00001.setAttribute("font-weight", "lighter"); } catch(e) { }
+try { var00037.setAttribute("onstorage", "eventhandler4()"); } catch(e) { }
+try { var00019.setProperty("-webkit-tap-highlight-color", ""); } catch(e) { }
+try { /* newvar{var00155:Touch} */ var var00155 = var00110.createTouch(window,htmlvar00032,1,0.567248453977,0.581973536822,0.420509485494,0.167367209889,0.0122315612828,0.973751724881); } catch(e) { }
+try { if (!var00155) { var00155 = GetVariable(fuzzervars, 'Touch'); } else { SetVariable(var00155, 'Touch'); } } catch(e) { }
+try { /* newvar{var00156:DOMString} */ var var00156 = var00112.contentType; } catch(e) { }
+try { /* newvar{var00157:Element} */ var var00157 = var00089; } catch(e) { }
+try { if (!var00157) { var00157 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00157, 'Element'); SetVariable(var00157, 'GlobalEventHandlers'); SetVariable(var00157, 'EventTarget'); } } catch(e) { }
+try { var00019.setProperty("-webkit-box-decoration-break", "clone"); } catch(e) { }
+try { /* newvar{var00158:USVString} */ var var00158 = var00035.password; } catch(e) { }
+try { if (!var00158) { var00158 = GetVariable(fuzzervars, 'USVString'); } else { SetVariable(var00158, 'USVString'); } } catch(e) { }
+try { var00057.search = var00158; } catch(e) { }
+try { /* newvar{var00159:DOMString} */ var var00159 = htmlvar00003.enctype; } catch(e) { }
+try { /* newvar{var00160:float} */ var var00160 = svgvar00001.getCurrentTime(); } catch(e) { }
+try { freememory(); } catch(e) { }
+try { htmlvar00003.noValidate = false; } catch(e) { }
+try { var00019.setProperty("contain", "style"); } catch(e) { }
+try { /* newvar{var00163:DOMRect} */ var var00163 = new DOMRect(1, 49, 0, 9); } catch(e) { }
+try { if (!var00163) { var00163 = GetVariable(fuzzervars, 'DOMRect'); } else { SetVariable(var00163, 'DOMRect'); SetVariable(var00163, 'DOMRectReadOnly'); } } catch(e) { }
+try { /* newvar{var00162:DOMRectReadOnly} */ var var00162 = var00163; } catch(e) { }
+try { if (!var00162) { var00162 = GetVariable(fuzzervars, 'DOMRectReadOnly'); } else { SetVariable(var00162, 'DOMRectReadOnly'); } } catch(e) { }
+try { /* newvar{var00161:double} */ var var00161 = var00162.top; } catch(e) { }
+try { var00089.setAttribute("aria-relevant", "removals"); } catch(e) { }
+try { htmlvar00017.setAttribute("onbeforescriptexecute", "eventhandler1()"); } catch(e) { }
+try { var00132.stopImmediatePropagation(); } catch(e) { }
+try { /* newvar{var00164:EventHandler} */ var var00164 = htmlvar00023.onmouseenter; } catch(e) { }
+try { if (!var00164) { var00164 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00164, 'EventHandler'); } } catch(e) { }
+try { var00019.setProperty("zoom", "0.937136829252"); } catch(e) { }
+try { var00019.setProperty("background-clip", "content-box, padding-box, border-box"); } catch(e) { }
+try { var00066.oncanplaythrough = var00078; } catch(e) { }
+try { /* newvar{var00165:DOMString} */ var var00165 = var00005.type; } catch(e) { }
+try { /* newvar{var00166:DOMString} */ var var00166 = var00112.bgColor; } catch(e) { }
+try { htmlvar00008.setAttribute("archive", "" + String.fromCharCode(70, 115, 62, 113, 61, 79, 113, 62, 69, 82, 54, 126, 63, 97, 118, 64, 43, 117, 108, 32) + ""); } catch(e) { }
+try { /* newvar{var00167:Element} */ var var00167 = htmlvar00021.appendChild(var00105); } catch(e) { }
+try { if (!var00167) { var00167 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00167, 'Element'); SetVariable(var00167, 'GlobalEventHandlers'); SetVariable(var00167, 'EventTarget'); } } catch(e) { }
+try { var00019.setProperty("-webkit-box-ordinal-group", "2"); } catch(e) { }
+try { var00104.setAttribute("text-rendering", "geometricPrecision"); } catch(e) { }
+try { var00104.setAttribute("stroke-linejoin", "bevel"); } catch(e) { }
+try { svgvar00005.setAttribute("zoomAndPan", "magnify"); } catch(e) { }
+try { var00104.setAttribute("dy", "0em"); } catch(e) { }
+try { /* newvar{var00168:boolean} */ var var00168 = var00041.pointerBeforeReferenceNode; } catch(e) { }
+try { var00019.setProperty("hyphens", "none"); } catch(e) { }
+try { /* newvar{var00169:SVGAnimatedTransformList} */ var var00169 = svgvar00007.transform; } catch(e) { }
+try { if (!var00169) { var00169 = GetVariable(fuzzervars, 'SVGAnimatedTransformList'); } else { SetVariable(var00169, 'SVGAnimatedTransformList'); } } catch(e) { }
+try { svgvar00009.setAttribute("stroke-linejoin", "round"); } catch(e) { }
+try { /* newvar{var00170:double} */ var var00170 = var00109.scrollLeft; } catch(e) { }
+try { /* newvar{var00171:CSSStyleDeclaration} */ var var00171 = htmlvar00002.style; } catch(e) { }
+try { if (!var00171) { var00171 = GetVariable(fuzzervars, 'CSSStyleDeclaration'); } else { SetVariable(var00171, 'CSSStyleDeclaration'); } } catch(e) { }
+try { /* newvar{var00172:DOMString} */ var var00172 = var00033.innerText; } catch(e) { }
+try { /* newvar{var00174:HTMLSelectElement} */ var var00174 = document.createElement("select"); } catch(e) { }
+try { if (!var00174) { var00174 = GetVariable(fuzzervars, 'HTMLSelectElement'); } else { SetVariable(var00174, 'HTMLSelectElement'); SetVariable(var00174, 'Element'); SetVariable(var00174, 'GlobalEventHandlers'); SetVariable(var00174, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00173:boolean} */ var var00173 = var00174.checkValidity(); } catch(e) { }
+try { /* newvar{var00175:HTMLEmbedElement} */ var var00175 = document.createElement("embed"); } catch(e) { }
+try { if (!var00175) { var00175 = GetVariable(fuzzervars, 'HTMLEmbedElement'); } else { SetVariable(var00175, 'HTMLEmbedElement'); SetVariable(var00175, 'Element'); SetVariable(var00175, 'GlobalEventHandlers'); SetVariable(var00175, 'EventTarget'); } } catch(e) { }
+try { var00175["1"] = var00086; } catch(e) { }
+try { /* newvar{var00176:boolean} */ var var00176 = var00110.execCommand("justifyCenter", false); } catch(e) { }
+try { htmlvar00012.password = var00080; } catch(e) { }
+try { /* newvar{var00177:SVGTransformList} */ var var00177 = var00169.baseVal; } catch(e) { }
+try { if (!var00177) { var00177 = GetVariable(fuzzervars, 'SVGTransformList'); } else { SetVariable(var00177, 'SVGTransformList'); } } catch(e) { }
+try { htmlvar00014.addEventListener("DOMSubtreeModified", var00150); } catch(e) { }
+try { /* newvar{var00178:boolean} */ var var00178 = svgvar00003.contains(svgvar00003); } catch(e) { }
+try { /* newvar{var00179:long} */ var var00179 = svgvar00009.clientTop; } catch(e) { }
+try { /* newvar{var00180:DOMString} */ var var00180 = svgvar00005.className; } catch(e) { }
+try { /* newvar{var00181:EventHandler} */ var var00181 = htmlvar00003.onpointerdown; } catch(e) { }
+try { if (!var00181) { var00181 = GetVariable(fuzzervars, 'EventHandler'); } else { SetVariable(var00181, 'EventHandler'); } } catch(e) { }
+try { /* newvar{var00182:DOMString} */ var var00182 = var00132.inputType; } catch(e) { }
+try { /* newvar{var00183:DOMString} */ var var00183 = var00005.type; } catch(e) { }
+try { /* newvar{var00185:HTMLContentElement} */ var var00185 = document.createElement("content"); } catch(e) { }
+try { if (!var00185) { var00185 = GetVariable(fuzzervars, 'HTMLContentElement'); } else { SetVariable(var00185, 'HTMLContentElement'); SetVariable(var00185, 'Element'); SetVariable(var00185, 'GlobalEventHandlers'); SetVariable(var00185, 'EventTarget'); } } catch(e) { }
+try { /* newvar{var00184:NodeList} */ var var00184 = var00185.getDistributedNodes(); } catch(e) { }
+try { if (!var00184) { var00184 = GetVariable(fuzzervars, 'NodeList'); } else { SetVariable(var00184, 'NodeList'); } } catch(e) { }
+try { /* newvar{var00186:MutationEvent} */ var var00186 = document.createEvent("MutationEvent"); } catch(e) { }
+try { if (!var00186) { var00186 = GetVariable(fuzzervars, 'MutationEvent'); } else { SetVariable(var00186, 'MutationEvent'); SetVariable(var00186, 'Event'); } } catch(e) { }
+try { var00186.initMutationEvent(String.fromCharCode(92, 38, 46, 72, 112, 95, 38, 106, 94, 96, 65, 116, 94, 47, 34, 102, 86, 35, 40, 123),false,false,htmlvar00015,"foo"); } catch(e) { }
+try { /* newvar{var00187:NativeScrollBehavior} */ var var00187 = "disable-native-scroll"; } catch(e) { }
+try { if (!var00187) { var00187 = GetVariable(fuzzervars, 'NativeScrollBehavior'); } else { SetVariable(var00187, 'NativeScrollBehavior'); } } catch(e) { }
+try { var00109.setApplyScroll(var00024,var00187); } catch(e) { }
+try { var00019.setProperty("grid-column-start", "span 0"); } catch(e) { }
+try { var00171.setProperty("-webkit-box-shadow", "rgb(129,168,64) 45px 1px"); } catch(e) { }
+try { /* newvar{var00188:XPathResult} */ var var00188 = var00135.evaluate(var00075,18,var00136); } catch(e) { }
+try { if (!var00188) { var00188 = GetVariable(fuzzervars, 'XPathResult'); } else { SetVariable(var00188, 'XPathResult'); } } catch(e) { }
+try { /* newvar{var00189:Element} */ var var00189 = document.rootScroller; } catch(e) { }
+try { if (!var00189) { var00189 = GetVariable(fuzzervars, 'Element'); } else { SetVariable(var00189, 'Element'); SetVariable(var00189, 'GlobalEventHandlers'); SetVariable(var00189, 'EventTarget'); } } catch(e) { }
+try { var00171.setProperty("outline-style", "hidden"); } catch(e) { }
+try { htmlvar00019.contextMenu = var00154; } catch(e) { }
+try { /* newvar{var00190:SVGAnimatedTransformList} */ var var00190 = svgvar00008.transform; } catch(e) { }
+try { if (!var00190) { var00190 = GetVariable(fuzzervars, 'SVGAnimatedTransformList'); } else { SetVariable(var00190, 'SVGAnimatedTransformList'); } } catch(e) { }
+//endjs
+var fuzzervars = {};
+freememory()
+
+
+}
+
+</script>
+</head>
+<body onload=jsfuzzer()>
+<!--beginhtml-->
+<video id="htmlvar00001" style="justify-items: flex-end safe; stroke-opacity: 0.476883645911; -webkit-hyphens: auto; letter-spacing: 9; fill-opacity: 0.846527116873" onloadeddata="eventhandler2()" style="-webkit-transition-delay: 0s; opacity: -1; min-height: -1vmin; marker: inherit; -webkit-box-pack: justify" height="1" onloadstart="eventhandler4()" autoplay="autoplay" lang="mg" controls="controls" case="second" onwebkitsourceopen="eventhandler5()">
+<track id="htmlvar00002" mode="screen" onload="eventhandler2()" mode="screen" kind="metadata" label=";H}s*JM.:DP7" href="jUu:" media="screen and (min-height:1px)" href="}p.q^S" coords="0, 97, 9, 1" width="91">C'mX'7b6p::</track>
+</video>
+<form id="htmlvar00003" enctype="application/x-www-form-urlencoded" title="YOw\2"Wt%B+z'H," method="post" formaction="eventhandler1()" target="htmlvar00009" target="htmlvar00007" archive="](Lm(M`#Xq^ePPw&4{" download="p6SCV" formnovalidate="formnovalidate" loopend="1">
+<legend id="htmlvar00004" accesskey="~" tabindex="1" tabindex="8" style="fill: black; bottom: 1em; outline-color: white; -webkit-margin-bottom-collapse: discard; widows: 85" align="bad" novalidate="novalidate" select=".class0" max="0" challenge="uGO#gK~79YX1T&3qFXz" codebase="cA="7w5Lx,_K">[a</legend>
+<input id="htmlvar00005" list="htmlvar00001" onmousemove="eventhandler2()" style="mso-style-parent: ''; -webkit-text-emphasis: filled; src: url(#svgvar00002); mso-pagination: widow-orphan; -webkit-background-origin: initial" contenteditable="plaintext-only" role="link" type="emailNew" cellspacing="0" color="red" shape="rect" inner="1" minlength="63">
+</form>
+<video id="htmlvar00006" onloadeddata="eventhandler2()" ontimeupdate="eventhandler2()" onfocus="eventhandler3()" onloadstart="eventhandler2()" onseeked="eventhandler3()" rightmargin="-1" translate="yes" max="1" formmethod="get" scoped="scoped">
+6uhfq'Gc5nKsbrJNnC^u
+</video>
+<link id="htmlvar00007" disabled="disabled" rev="next" rev="glossary" media="screen and (max-width:7px)" charset="vbJK=v&cj#QX/Lo.NJ(" formaction="eventhandler3()" rows="0" kind="captions" name="7CIOi"X@Z33Gr}L2" mayscript="false">^iVw;'u1fo^L7$C</link>
+<map id="htmlvar00008" title="{RIL[mE]" title="MV$-uET5v'A./8" name="K'vo~C+.}eJc@*C^L_?O" abbr="mV:09b" kind="captions" right="1" dir="LTR" onseeking="eventhandler5()" menu="htmlvar00003">NG+]s9<^/I[=LD\ft/</map>
+<textarea id="htmlvar00009" onkeydown="eventhandler4()" cols="0,0" value="C" role="unknownrole checkbox" wrap="soft" pluginspage="!C" value="8" seed="0" autoload="autoload" frameborder="1">)nb_'v_16P</textarea>
+<svg id="svgvar00001" baseProfile="basic" marker-mid="url(#svgvar00004)" externalResourcesRequired="true" preserveAspectRatio="xMinYMin meet" contentStyleType="text/css" pointsAtZ="0" markerHeight="-1" g2="G" bbox="0 1 1 0" textLength="1">
+<foreignObject id="svgvar00002" x="0.740586272856" display="inline" width="96px" x="53%" x="62px" pointsAtZ="1" markerUnits="userSpaceOnUse" azimuth="0" viewTarget="polygon-object" keyTimes="82;0;4;0"><body id="htmlvar00010" xmlns="http://www.w3.org/1999/xhtml">
+
+<dialog id="htmlvar00011" class="class7" open="true" class="class6" open="true" name=">X>*jMm.?3,$" data="" reversed="reversed" hspace="0" hreflang="hi" headers="htmlvar00007">"#XR_tF_[/W~~E</dialog>
+</body></foreignObject>
+<g id="svgvar00003" viewbox="-1 -1 80 1" markerWidth="0" g1="ti" elevation="-1" systemLanguage="ko" baseline-shift="baseline" slope="0.367328199623" operator="in" arabic-form="isolated" viewbox="1 0 0 36" />
+<font-face-uri id="svgvar00004" xlink:href="#svgvar00006" xlink:href="#svgvar00008" xlink:href="#svgvar00007" xlink:href="#svgvar00005" xlink:href="#svgvar00008" in2="Gaussian" patternContentUnits="userSpaceOnUse" version="1" gradientTransform="translate(1,-1) skewX(1)" spreadMethod="pad" />
+<a id="htmlvar00012" href="x" font-family="Arial" name="VeryUnlikelyToExistFont" stroke="none" font-weight="28" ascent="1" restart="always" refX="-1" azimuth="0" font-stretch="normal">
+<feMerge id="svgvar00005" clipPathUnits="userSpaceOnUse" keyTimes="1;1" marker-mid="url(#svgvar00008)" elevation="1" onmouseup="eventhandler4()" onfocusout="eventhandler4()" diffuseConstant="-1" max="media" glyph-orientation-vertical="auto" keySplines="1" />
+</a>
+<animateTransform id="svgvar00006" type="linear" type="hueRotate" calcMode="paced" type="linear" values="inherit; none; inherit" pathLength="0" stdDeviation="1" diffuseConstant="77" rx="47%" keySplines="0,1,8,1;-1,0,1,-1;0,1,0,0" />
+<tspan id="svgvar00007" x="0.364314733631" y="0" display="block" filter="none" unicode-bidi="normal" dominant-baseline="mathematical" font-size="0px" stroke="" zoomAndPan="disable" result="red" overflow="auto" y="0 57 0" />
+<circle id="svgvar00008" cx="0.0246698918495" cy="0.409484298098" r="0px" mask="url(#svgvar00003)" stroke-dasharray="inherit" onmousedown="eventhandler1()" fill="url(#svgvar00008) currentColor" clip-path="url(#svgvar00007)" stroke-join="round" stroke-opacity="-1" kernelUnitLength="1 73" markerUnits="strokeWidth" underline-thickness="0">
+<animate id="svgvar00009" attributeType="XML" from="inherit" end="svgvar00001.begin + -1s" xlink:href="#svgvar00004" begin="svgvar00005.beginEvent" attributeType="XML" refY="1" maskUnits="userSpaceOnUse" stroke="currentColor" patternContentUnits="objectBoundingBox" />
+</circle>
+<animateTransform id="svgvar00010" repeatCount="indefinite" xlink:href="#svgvar00008" from="3, 0" attributeName="font-size" additive="replace" specularExponent="10" g1="ti" dur="media" onrepeat="eventhandler5()" dx="0,8,0,1,0,0" />
+<feConvolveMatrix id="svgvar00011" divisor="1" targetX="97" kernelMatrix="0.637585559719 0.0226215979111 0.78026967256 0.475517628235 0.996632106954 0.208561741091 0.849774125976 0.824771355361 0.67206781913" preserveAlpha="true" targetY="56" kernelMatrix="0.830754607941 0.845986808589 0.665949632952" onmouseout="eventhandler2()" stroke-dasharray="1" enable-background="new 0.746430598758 0.643573055986 60 0" exponent="4" />
+<font-face-uri id="svgvar00012" xlink:href="#svgvar00006" xlink:href="#svgvar00008" xlink:href="#svgvar00002" xlink:href="#svgvar00003" xlink:href="#svgvar00006" repeatCount="indefinite" pointsAtX="1" dominant-baseline="reset-size" from="visible" refX="1" />
+</svg>
+<marquee id="htmlvar00013" scrolldelay="1" style="column-span: all; -webkit-background-origin: content-box; -webkit-font-smoothing: antialiased; -webkit-font-feature-settings: 'c2sc'; mso-ignore: padding" height="0" direction="rtl" loop="-1" sandbox="allowscripts allow-same-origin" step="0" desc="A{5zJ! k}hJyQ{M" data="O[{KOXJzP" sandbox="allow-scriptss allow-same-origin">f#1s=6$E"d;f</marquee>
+<ul id="htmlvar00014" role="progressbar" type="3D'text/css'" style="-webkit-perspective-origin-x: 0px; box-sizing: border-box; -webkit-column-rule-style: double; text-transform: small-caps; transition: all 0ms" class="class3" role="group" tabindex="3" slope="0" media="print" frameborder="1" code="ORJi+}Hg<$f%$t7_CYPi">
+<li id="htmlvar00015" lang="el-US" role="listbox" contenteditable="inherit" class="class1" value="rvT:h" right="3" onwebkitkeymessage="eventhandler3()" href="'RxS*h^D1e|" display="none" for="htmlvar00008">
+<base id="htmlvar00016" href="@Cg X]Y066g;}2" dir="LTR" target="htmlvar00001" href="&_R@JP" title="6T@[wSv(v~m" draggable="true" icon="pLF`J" loopstart="0" itemprop="5/A12" type="application/x-invalid-type"></base>
+<ol id="htmlvar00017" tabindex="0" oninput="eventhandler3()" name="'/:67v@Ox THp_JP^<Q" compact="compact" role="region" alink="black" expanded="false" frame="vsides" itemprop="NUF-ueA\{`O***9>t " open="true">
+<li id="htmlvar00018" type="popup" onclick="eventhandler2()" title="LW$- Lvyi_" style="transition-duration: initial; stroke-dasharray: 0; mso-displayed-decimal-separator: '\.'; rotation: 67deg; min-width: 13vmax" title="):rc+B87" rowspan="81" challenge="}c*7yGb" color="white" shape="poly" seamless="seamless">
+<dir id="htmlvar00019" compact="compact" tabindex="0" inner="1" loopstart="1" width="1" frameborder="1" autofocus="autofocus">P9X\~%wy10)yC)AbW"H</dir>
+<shadow id="htmlvar00020" as="media" scheme="NIST" ondrag="eventhandler3()" lowsrc="L!z"1G[!L[,'+g/]+4HU" prompt="@_)1c(BO7Y6_>.w[9z">
+<nobr id="htmlvar00021" style="marker: initial; animation-play-state: paused; -webkit-tap-highlight-color: black; border-right: hidden; -webkit-column-rule-width: 1px" required="required" srcset="ljL<9q?tXo" nohref="nohref" menu="htmlvar00001" nonce="nonce">GVS0d@AbMN</nobr>
+</shadow>
+</li>
+</ol>
+</li>
+<li id="htmlvar00022" style="overflow-y: auto; whitespace: nowrap; align-items: self-start safe; -ms-text-combine-horizontal: all; mso-protection: locked visible" style="-webkit-transition-timing-function: ease-in; -webkit-flow-into: picture; text-decoration-style: dotted; -webkit-column-break-before: always; -webkit-border-after-color: " class="class9" class="class1" dir="RTL" content="@n"GE" charoff="-1" marginwidth="1" contextmenu="htmlvar00003" wrap="hard">
+<details id="htmlvar00023" open="true" style="background-repeat-x: repeat-x; flex-flow: wrap-reverse row-reverse; -webkit-line-clamp: 0; -webkit-column-break-after: always; column-rule: 0px solid" ontoggle="eventhandler3()" ontoggle="eventhandler5()" open="true" enctype="application/x-www-form-urlencoded" onstalled="eventhandler4()" autoload="autoload" onwebkitsourceclose="eventhandler3()" marginwidth="0">DHk!;hgQqQ|j6zcXVL_G</details>
+<video id="htmlvar00024" draggable="true" title="" oncanplaythrough="eventhandler5()" onloadeddata="eventhandler4()" tabindex="0" referrerpolicy="origin-when-crossorigin" minlength="-1" rows="1" align="center" vspace="1">I-36`4P</video>
+</li>
+</ul>
+<time id="htmlvar00025" datetime="2000-02-01T03:04:05Z" name="K7Ji_kl .[$//~:jx" as="media" srcdoc="&`xM3<K*)" hidden="hidden" colspan="1" longdesc=":<oj`IgnKhH-">
+<basefont id="htmlvar00026" name="|0wow^GzIxfVzaK" size="0" style="flex: 1px; bottom: 1cm; -webkit-margin-after: 0px; font-vendor: any; flex-shrink: 0.539878476286" span="7" description="" colspan="-1" case="first"></basefont>
+<time id="htmlvar00027" datetime="January 1, 2002" name="Z" label="|xInV1zV-16P#A9" alink="white" language="vbscript" loop="-1" select="#htmlvar00002"><U</tt>
+</tt>
+<!--endhtml-->
+</body>
+</html>
diff --git a/dom/svg/crashtests/1513603.html b/dom/svg/crashtests/1513603.html new file mode 100644 index 0000000000..21271a6837 --- /dev/null +++ b/dom/svg/crashtests/1513603.html @@ -0,0 +1,20 @@ +<script> +function go() { + b.addEventListener("DOMSubtreeModified", eh) + b.max = 0.44 + b.value = 0.94 +} +function eh() { + SpecialPowers.gc() + SpecialPowers.forceCC() + polygon.animatedPoints.z = 1; + path.animatedPathSegList.z = 1; + svg.requiredExtensions.z = 1; +} +</script> +<body onload=go()> +<svg id="svg"> +<polygon id="polygon"/> +<path id="path"/> +<dl> +<meter id="b"> diff --git a/dom/svg/crashtests/1531578-1.html b/dom/svg/crashtests/1531578-1.html new file mode 100644 index 0000000000..6fb1077c8f --- /dev/null +++ b/dom/svg/crashtests/1531578-1.html @@ -0,0 +1,17 @@ +<style> +* { + filter: brightness(0.1638); + outline: 64px dotted; +} +</style> +<script> +function go() { + a.setAttribute("text-decoration", "line-through") +} +</script> +<body onload=go()> +<svg> +<marker> +<foreignObject id="a"> +<ins> + diff --git a/dom/svg/crashtests/1555795.html b/dom/svg/crashtests/1555795.html new file mode 100644 index 0000000000..fe0d3ed4a4 --- /dev/null +++ b/dom/svg/crashtests/1555795.html @@ -0,0 +1,22 @@ +<script>
+function go() {
+ var b = document.getElementById("b")
+ c.setAttribute("refX", "0")
+ b.getCTM()
+}
+function eh1() {
+ c.addEventListener("DOMAttrModified", eh2)
+}
+function eh2() {
+ var f = document.getElementById("f")
+ d.replaceWith("")
+ f.before(a)
+}
+</script>
+<body onload=go()>
+<table background="X">A</table>
+<svg id="a">
+<line id="b">
+<tspan id="d">
+<textPath id="f">
+<animateTransform id="c" onbegin="eh1()">
diff --git a/dom/svg/crashtests/1560179.html b/dom/svg/crashtests/1560179.html new file mode 100644 index 0000000000..80f77908e1 --- /dev/null +++ b/dom/svg/crashtests/1560179.html @@ -0,0 +1,5 @@ +<svg class=""> + <image class="" id="id_1" systemLanguage=""> + <text class="" xml:space="preserve"> + <textPath class="" xlink:href="#id_1"> + diff --git a/dom/svg/crashtests/1572904.html b/dom/svg/crashtests/1572904.html new file mode 100644 index 0000000000..8aaeeff6c9 --- /dev/null +++ b/dom/svg/crashtests/1572904.html @@ -0,0 +1,15 @@ +<script> +function go() { + a.addEventListener("DOMSubtreeModified", go) + b.setAttribute("y", "") + SpecialPowers.gc() + SpecialPowers.forceCC() + try { c.viewBox.animVal[undefined] = ''} catch(e) { } + try { c.viewBox.animVal.x; } catch(e) { } +} +</script> +<filter id="a"> +<svg id="" onload="go()"> +<path id="b"> +<view id="c" viewBox="1 9 0 1" /> + diff --git a/dom/svg/crashtests/1683907.html b/dom/svg/crashtests/1683907.html new file mode 100644 index 0000000000..d8286f1c27 --- /dev/null +++ b/dom/svg/crashtests/1683907.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> + <style> + * { + block-size: max-content; + display: table-column-group; + } + </style> +</head> +<svg> + <rect id='rect' height='18pc' width='64ex'> + <animateMotion> + <mpath xlink:href='#rect' xlink:type='simple'> + </mpath> + </animateMotion> + </rect> +</svg> +</html> + diff --git a/dom/svg/crashtests/1715387.html b/dom/svg/crashtests/1715387.html new file mode 100644 index 0000000000..7de6499f82 --- /dev/null +++ b/dom/svg/crashtests/1715387.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<style> +svg { + width: 10%; + height: 10%; + background: #eee; +} +svg path { + fill: none; + stroke: #000; +} + +</style> +<script> + function run() { + const target = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const root = document.getElementById('svgroot'); + root.appendChild(target); + target.style.d = 'path("M0,0 L2,2")'; + + var m = new MutationObserver(function () { + // This will destroy the oringal document. + document.write("<html><body></body></html>"); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + document.documentElement.classList.remove("reftest-wait"); + }); + + m.observe(target, { attributes: true }); + target.setAttribute("d", "none"); + + // Calling these APIs flushes the style, which may run the script in the + // callback function above that destroys the composed document. + target.getTotalLength(); + target.getPointAtLength(1); + target.isPointInFill({x: 1, y: 1}); + target.isPointInStroke({x: 1, y: 1}); + target.getPathSegAtLength(0); + } +</script> +<body onload="run()"> +<svg viewBox="0 0 20 20" id="svgroot"> +</svg> diff --git a/dom/svg/crashtests/307322-1.svg b/dom/svg/crashtests/307322-1.svg new file mode 100644 index 0000000000..c9ad682665 --- /dev/null +++ b/dom/svg/crashtests/307322-1.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <symbol id="foo"> + <use xlink:href="#foo"/> + </symbol> + + <use xlink:href="#foo"/> +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/327705-1.svg b/dom/svg/crashtests/327705-1.svg new file mode 100644 index 0000000000..f033d345d4 --- /dev/null +++ b/dom/svg/crashtests/327705-1.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +
<svg xmlns="http://www.w3.org/2000/svg">
+ +<script>
+ +function init() +{
+ var a = document.getElementById("a"); + var b = document.getElementById("b"); + a.appendChild(b); + b.getBBox(); +};
+ +window.addEventListener("load", init, false); +
</script> + +<rect id="a" width="20" height="10"/>
<rect id="b" width="10" height="20"/>
+ +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/336994-1.html b/dom/svg/crashtests/336994-1.html new file mode 100644 index 0000000000..4e251cdde8 --- /dev/null +++ b/dom/svg/crashtests/336994-1.html @@ -0,0 +1,12 @@ +<html class="reftest-wait"> +<head> +<title>Testcase bug 336994 - Crash when window gets destroyed on SVGZoom event</title> +<script> +setTimeout('document.documentElement.className = ""', 500); +</script> +</head> +<body> +This page should not crash Mozilla, you should see no iframe<br> +<iframe src="data:image/svg+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%20standalone%3D%22no%22%3F%3E%0A%3C%21DOCTYPE%20svg%20PUBLIC%20%22-//W3C//DTD%20SVG%201.1//EN%22%20%22http%3A//www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd%22%3E%0A%3Csvg%20width%3D%22100%25%22%20height%3D%22100%25%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%0A%3Cscript%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0Awindow.addEventListener%28%27SVGZoom%27%2C%20doe%2C%20true%29%3B%0Afunction%20doe%28e%29%20%7B%0Avar%20x%3D%20parent.document.getElementsByTagName%28%27iframe%27%29%5B0%5D%3B%0Ax.parentNode.removeChild%28x%29%3B%0A%7D%0AsetTimeout%28doe2%2C%201000%29%3B%0A%0Afunction%20doe2%28%29%20%7B%0Adocument.documentElement.currentScale%20%3D%202%3B%0A%7D%0A%3C/script%3E%0A%3C/svg%3E"></iframe> +</body> +</html>
\ No newline at end of file diff --git a/dom/svg/crashtests/344888-1.svg b/dom/svg/crashtests/344888-1.svg new file mode 100644 index 0000000000..afbdb4a4f9 --- /dev/null +++ b/dom/svg/crashtests/344888-1.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<rect x="0 0 30 40" /> +
</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/345445-1.svg b/dom/svg/crashtests/345445-1.svg new file mode 100644 index 0000000000..b322b5ac05 --- /dev/null +++ b/dom/svg/crashtests/345445-1.svg @@ -0,0 +1,23 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30);" class="reftest-wait"> + +<script> + +function boom() +{ + var doomed = document.getElementById("doomed"); + doomed.parentNode.removeChild(doomed); + document.documentElement.removeAttribute("class"); +} + +</script> + +<g stroke="url(#grad)"> + + <g id="doomed"> + + <linearGradient id="grad" /> + <rect fill="url(#grad)" /> + </g> +</g> + +</svg>
\ No newline at end of file diff --git a/dom/svg/crashtests/360836-1.svg b/dom/svg/crashtests/360836-1.svg new file mode 100644 index 0000000000..27dae964d0 --- /dev/null +++ b/dom/svg/crashtests/360836-1.svg @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg xmlns="http://www.w3.org/2000/svg"> + <style type="text/css"> + circle:hover {fill-opacity:0.9;} + </style> + <g style="fill-opacity:0.7;"> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" /> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" /> + <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/> + </g> + +</svg> diff --git a/dom/svg/crashtests/369051-1.svg b/dom/svg/crashtests/369051-1.svg new file mode 100644 index 0000000000..82cd3ef2a4 --- /dev/null +++ b/dom/svg/crashtests/369051-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<text x="240" y="240"> + <svg xmlns="http://www.w3.org/2000/svg"> + <circle xmlns="http://www.w3.org/2000/svg" cx="5cm" cy="5cm" r="50" style="fill: green;"/> + </svg> +</text> + +</svg> diff --git a/dom/svg/crashtests/369291-2.svg b/dom/svg/crashtests/369291-2.svg new file mode 100644 index 0000000000..097e3dde2d --- /dev/null +++ b/dom/svg/crashtests/369291-2.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<html:script>
+
+function boom()
+{
+ document.getElementById("rect").getBBox();
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<rect id="rect" />
+
+</svg>
diff --git a/dom/svg/crashtests/369568-1.svg b/dom/svg/crashtests/369568-1.svg new file mode 100644 index 0000000000..1d8fd1511d --- /dev/null +++ b/dom/svg/crashtests/369568-1.svg @@ -0,0 +1,20 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="boom()">
+
+<script>
+function boom()
+{
+ document.getElementById("Gaussian_Blur").href;
+}
+</script>
+
+<defs>
+<filter id="Gaussian_Blur">
+<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
+</filter>
+</defs>
+
+<ellipse cx="200" cy="150" rx="70" ry="40"
+style="fill:#ff0000;stroke:#000000;
+stroke-width:2;filter:url(#Gaussian_Blur)"/>
+
+</svg>
diff --git a/dom/svg/crashtests/374882-1.svg b/dom/svg/crashtests/374882-1.svg new file mode 100644 index 0000000000..7a5a2cca9a --- /dev/null +++ b/dom/svg/crashtests/374882-1.svg @@ -0,0 +1,14 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<defs> + + <filter id="over"> + <feOffset in="SourceGraphic" dx="100" result="red" /> + <feComposite in2="SourceGraphic" in="red" operator="over" /> + </filter> + +</defs> + +<path d="M 0 0 L 0 50 L 50 0 z" x="300" filter="url(#over)"/> + +</svg> diff --git a/dom/svg/crashtests/380101-1.svg b/dom/svg/crashtests/380101-1.svg new file mode 100644 index 0000000000..cb3aa3e263 --- /dev/null +++ b/dom/svg/crashtests/380101-1.svg @@ -0,0 +1,30 @@ +<svg xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.w3.org/2000/svg"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<html:style>
+ .zind { z-index: 0; }
+ .gencon:after { content: counter(chicken); }
+</html:style>
+
+<script>
+function boom()
+{
+ document.getElementById("define").setAttribute("class", "gencon");
+ document.getElementById("use").setAttribute("class", "zind");
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+<defs>
+ <g id="define">
+ <rect x="0" y="0" width="75" height="75" fill="green"/>
+ </g>
+</defs>
+
+<use xlink:href="#define" x="0" y="20" id="use" />
+
+</svg>
diff --git a/dom/svg/crashtests/381777-1.svg b/dom/svg/crashtests/381777-1.svg new file mode 100644 index 0000000000..bc2df73c79 --- /dev/null +++ b/dom/svg/crashtests/381777-1.svg @@ -0,0 +1,96 @@ +<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+
+ <filter id="BlendNormal" >
+ <feOffset in="SourceGraphic" dx="75" dy="0" result="temp"/>
+ <feBlend in2="SourceGraphic" in="temp" mode="normal"/>
+ </filter>
+
+ <filter id="BlendMultiply" >
+ <feOffset in="SourceGraphic" dx="75" dy="0" result="temp"/>
+ <feBlend in2="SourceGraphic" in="temp" mode="multiply"/>
+ </filter>
+
+ <filter id="BlendScreen" >
+ <feOffset in="SourceGraphic" dx="75" dy="0" result="temp"/>
+ <feBlend in2="SourceGraphic" in="temp" mode="screen"/>
+ </filter>
+
+ <filter id="BlendDarken" >
+ <feOffset in="SourceGraphic" dx="75" dy="0" result="temp"/>
+ <feBlend in2="SourceGraphic" in="temp" mode="darken"/>
+ </filter>
+
+ <filter id="BlendLighten" >
+ <feOffset in="SourceGraphic" dx="75" dy="0" result="temp"/>
+ <feBlend in2="SourceGraphic" in="temp" mode="lighten"/>
+ </filter>
+
+ <filter id="noblend" >
+ <feOffset in="SourceGraphic" dx="75" dy="0" result="temp"/>
+ <feMerge>
+ <feMergeNode in="SourceGraphic"/>
+ <feMergeNode in="temp"/>
+ </feMerge>
+ </filter>
+
+ <radialGradient id="grad">
+ <stop offset="0" stop-color="yellow"/>
+ <stop offset="1" stop-color="lightgreen"/>
+ </radialGradient>
+
+ <g id="swatch">
+ <rect x="0" y="0" width="75" height="75" fill="lightblue"/>
+ <rect x="75" y="0" width="75" height="75" fill="url(#grad)"/>
+ </g>
+
+ <g id="swatchHalf">
+ <rect x="0" y="0" width="75" height="75" fill="lightblue" opacity="0.5"/>
+ <rect x="75" y="0" width="75" height="75" fill="url(#grad)" opacity="0.5"/>
+ </g>
+
+ <g id="mask">
+ <rect x="0" y="0" width="75" height="75" fill="white"/>
+ <rect x="150" y="0" width="75" height="75" fill="white"/>
+ </g>
+
+ </defs>
+
+
+ <use xlink:href="#swatch" x="0" y="25" filter="url(#BlendNormal)"/>
+ <use xlink:href="#mask" x="0" y ="25"/>
+ <use xlink:href="#swatchHalf" x="150" y="25" filter="url(#BlendNormal)"/>
+ <use xlink:href="#mask" x="150" y ="25"/>
+ <text x="10" y="50">normal</text>
+
+ <use xlink:href="#swatch" x="0" y="125" filter="url(#BlendMultiply)"/>
+ <use xlink:href="#mask" x="0" y ="125"/>
+ <use xlink:href="#swatchHalf" x="150" y="125" filter="url(#BlendMultiply)"/>
+ <use xlink:href="#mask" x="150" y ="125"/>
+ <text x="10" y="150">multiply</text>
+
+ <use xlink:href="#swatch" x="0" y="225" filter="url(#BlendScreen)"/>
+ <use xlink:href="#mask" x="0" y ="225"/>
+ <use xlink:href="#swatchHalf" x="150" y="225" filter="url(#BlendScreen)"/>
+ <use xlink:href="#mask" x="150" y ="225"/>
+ <text x="10" y="250">screen</text>
+
+ <use xlink:href="#swatch" x="0" y="325" filter="url(#BlendDarken)"/>
+ <use xlink:href="#mask" x="0" y ="325"/>
+ <use xlink:href="#swatchHalf" x="150" y="325" filter="url(#BlendDarken)"/>
+ <use xlink:href="#mask" x="150" y ="325"/>
+ <text x="10" y="350">darken</text>
+
+ <use xlink:href="#swatch" x="0" y="425" filter="url(#BlendLighten)"/>
+ <use xlink:href="#mask" x="0" y ="425"/>
+ <use xlink:href="#swatchHalf" x="150" y="425" filter="url(#BlendLighten)"/>
+ <use xlink:href="#mask" x="150" y ="425"/>
+ <text x="10" y="450">lighten</text>
+
+ <use xlink:href="#swatch" x="0" y="525" filter="url(#noblend)"/>
+ <use xlink:href="#mask" x="0" y ="525"/>
+ <use xlink:href="#swatchHalf" x="150" y="525" filter="url(#noblend)"/>
+ <use xlink:href="#mask" x="150" y ="525"/>
+ <text x="10" y="550">no blend</text>
+
+</svg>
diff --git a/dom/svg/crashtests/383685-1.svg b/dom/svg/crashtests/383685-1.svg new file mode 100644 index 0000000000..3888ef29e6 --- /dev/null +++ b/dom/svg/crashtests/383685-1.svg @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 100);"> + +<script> + +function boom() +{ + var gradient = document.getElementById("gradient"); + gradient.spreadMethod.baseVal = undefined; + uneval({p: gradient.attributes}); +} + +</script> + +<linearGradient id="gradient" /> + +</svg> diff --git a/dom/svg/crashtests/385096.html b/dom/svg/crashtests/385096.html new file mode 100644 index 0000000000..88b4e3e630 --- /dev/null +++ b/dom/svg/crashtests/385096.html @@ -0,0 +1,14 @@ +<html>
+<head>
+<script>
+function boom()
+{
+ var SVG_NS = "http://www.w3.org/2000/svg";
+ var feFuncG = document.createElementNS(SVG_NS, "feFuncG");
+ feFuncG.offset.baseVal = 1;
+}
+</script>
+</head>
+<body onload="boom();">
+</body>
+</html>
diff --git a/dom/svg/crashtests/385554-1.html b/dom/svg/crashtests/385554-1.html new file mode 100644 index 0000000000..1b2dce65b0 --- /dev/null +++ b/dom/svg/crashtests/385554-1.html @@ -0,0 +1,6 @@ +<script> +try { + document.createElementNS("http://www.w3.org/2000/svg", "svg").getScreenCTM(); +} catch(e) { +} +</script> diff --git a/dom/svg/crashtests/385554-2.xhtml b/dom/svg/crashtests/385554-2.xhtml new file mode 100644 index 0000000000..af40729ebe --- /dev/null +++ b/dom/svg/crashtests/385554-2.xhtml @@ -0,0 +1,5 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<svg xmlns="http://www.w3.org/2000/svg"> +<foreignObject/> +</svg> +</window> diff --git a/dom/svg/crashtests/388712-1.svg b/dom/svg/crashtests/388712-1.svg new file mode 100644 index 0000000000..29d5be1d4d --- /dev/null +++ b/dom/svg/crashtests/388712-1.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <defs>
+ <g id="s">
+ <rect x="0" y="0" width="75" height="75" fill="lightblue" />
+ <html:form><html:label/></html:form>
+ </g>
+ </defs>
+
+ <use xlink:href="#s" x="150" y="25" />
+
+</svg>
diff --git a/dom/svg/crashtests/395616-1.html b/dom/svg/crashtests/395616-1.html new file mode 100644 index 0000000000..43ca7961b8 --- /dev/null +++ b/dom/svg/crashtests/395616-1.html @@ -0,0 +1,13 @@ +<html> +<head> +<script> +function boom() +{ + var marker = document.createElementNS("http://www.w3.org/2000/svg", "marker"); + marker.orientType.baseVal = ""; +} +</script> +</head> +<body onload="boom();"> +</body> +</html> diff --git a/dom/svg/crashtests/396618-1.html b/dom/svg/crashtests/396618-1.html new file mode 100644 index 0000000000..72c63a0f0b --- /dev/null +++ b/dom/svg/crashtests/396618-1.html @@ -0,0 +1,14 @@ +<html> +<head> +<script> +function boom() +{ + var svgText = document.createElementNS("http://www.w3.org/2000/svg", "text"); + svgText.setAttribute("dx", "foo"); + svgText.removeAttribute("dx"); +} +</script> +</head> +<body onload="boom();"> +</body> +</html> diff --git a/dom/svg/crashtests/397017-1.html b/dom/svg/crashtests/397017-1.html new file mode 100644 index 0000000000..2a794e19e0 --- /dev/null +++ b/dom/svg/crashtests/397017-1.html @@ -0,0 +1,11 @@ +<html> +<head> +<script> +var f = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA"); +f.setAttribute("tableValues", "3 7 5"); +f.removeAttribute("tableValues"); +</script> +</head> +<body> +</body> +</html> diff --git a/dom/svg/crashtests/397551-1.svg b/dom/svg/crashtests/397551-1.svg new file mode 100644 index 0000000000..7d2fc3dfb3 --- /dev/null +++ b/dom/svg/crashtests/397551-1.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <g id="rec"><use xlink:href="#rec" /></g> + + <use xlink:href="#rec" /> + +</svg> diff --git a/dom/svg/crashtests/397704-1.svg b/dom/svg/crashtests/397704-1.svg new file mode 100644 index 0000000000..75f44fd121 --- /dev/null +++ b/dom/svg/crashtests/397704-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"><marker orient="0" /></svg> diff --git a/dom/svg/crashtests/398926-both-different.svg b/dom/svg/crashtests/398926-both-different.svg new file mode 100644 index 0000000000..e4cfc7e5e6 --- /dev/null +++ b/dom/svg/crashtests/398926-both-different.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<pattern id="pat1" /> +<pattern id="pat2" /> + +<rect x="10" y="10" width="100" height="100" stroke="url(#pat1)" fill="url(#pat2)" /> + +</svg> diff --git a/dom/svg/crashtests/398926-both-same.svg b/dom/svg/crashtests/398926-both-same.svg new file mode 100644 index 0000000000..97454241ec --- /dev/null +++ b/dom/svg/crashtests/398926-both-same.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<pattern id="pat" /> + +<rect x="10" y="10" width="100" height="100" stroke="url(#pat)" fill="url(#pat)" /> + +</svg> diff --git a/dom/svg/crashtests/398926-fill.svg b/dom/svg/crashtests/398926-fill.svg new file mode 100644 index 0000000000..d259f1cdbe --- /dev/null +++ b/dom/svg/crashtests/398926-fill.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<pattern id="pat" /> + +<rect x="10" y="10" width="100" height="100" fill="url(#pat)" /> + +</svg> diff --git a/dom/svg/crashtests/398926-stroke.svg b/dom/svg/crashtests/398926-stroke.svg new file mode 100644 index 0000000000..60e7725c45 --- /dev/null +++ b/dom/svg/crashtests/398926-stroke.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<pattern id="pat" /> + +<rect x="10" y="10" width="100" height="100" stroke="url(#pat)" /> + +</svg> diff --git a/dom/svg/crashtests/405639-1.svg b/dom/svg/crashtests/405639-1.svg new file mode 100644 index 0000000000..242eff9aca --- /dev/null +++ b/dom/svg/crashtests/405639-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ onload="boom();">
+
+<script type="text/javascript">
+
+function boom() {
+ var greensquare = document.getElementById("greensquare");
+ var use = document.getElementById("use");
+ greensquare.appendChild(use);
+}
+
+</script>
+
+<rect id="greensquare" x="10" y="10" width="10" height="10" fill="green" />
+
+<use xlink:href="#greensquare" id="use" />
+
+</svg>
diff --git a/dom/svg/crashtests/406361-1.html b/dom/svg/crashtests/406361-1.html new file mode 100644 index 0000000000..813ed7d825 --- /dev/null +++ b/dom/svg/crashtests/406361-1.html @@ -0,0 +1,13 @@ +<html> +<head> +<script> +function boom() +{ + var use = document.createElementNS("http://www.w3.org/2000/svg", "use"); + use.patternTransform = ""; +} +</script> +</head> +<body onload="boom();"> +</body> +</html>
\ No newline at end of file diff --git a/dom/svg/crashtests/409811-1.html b/dom/svg/crashtests/409811-1.html new file mode 100644 index 0000000000..28a064e4d1 --- /dev/null +++ b/dom/svg/crashtests/409811-1.html @@ -0,0 +1,15 @@ +<html> +<head> +<script type="text/javascript"> + +function boom() +{ + var marker = document.createElementNS("http://www.w3.org/2000/svg", "marker"); + marker.orientType.baseVal = ""; +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/dom/svg/crashtests/410659-1.svg b/dom/svg/crashtests/410659-1.svg new file mode 100644 index 0000000000..55bc90c58d --- /dev/null +++ b/dom/svg/crashtests/410659-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <filter id="filter1"> + <feImage x="0" + y="0" + width="5%" + height="20%" + result="raster1" + xlink:href="../../../../../testing/crashtest/images/tree.gif" /> + + </filter> + + <rect x="0" + y="0" + width="100%" + height="100%" + filter="url(#filter1)" /> + +</svg> diff --git a/dom/svg/crashtests/410659-2.svg b/dom/svg/crashtests/410659-2.svg new file mode 100644 index 0000000000..d54b8900f4 --- /dev/null +++ b/dom/svg/crashtests/410659-2.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <filter id="filter1"> + <feImage x="0" + y="0" + width="5%" + height="20%" + result="raster1" /> + + + </filter> + + <rect x="0" + y="0" + width="100%" + height="100%" + filter="url(#filter1)" /> + +</svg> diff --git a/dom/svg/crashtests/410659-3.svg b/dom/svg/crashtests/410659-3.svg new file mode 100644 index 0000000000..df971a0ec7 --- /dev/null +++ b/dom/svg/crashtests/410659-3.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <filter id="filter1"> + <feImage x="0" + y="0" + width="5%" + height="20%" + result="raster1" + xlink:href="../../../../../testing/crashtest/images/tree.gif" /> + <feTile /> + </filter> + + <rect x="0" + y="0" + width="100%" + height="100%" + filter="url(#filter1)" /> + +</svg> diff --git a/dom/svg/crashtests/413174-1.svg b/dom/svg/crashtests/413174-1.svg new file mode 100644 index 0000000000..1b1bdf0b93 --- /dev/null +++ b/dom/svg/crashtests/413174-1.svg @@ -0,0 +1,25 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + onload="boom();"> + + <script> + +function boom() +{ + var g = document.getElementById("g"); + var use = document.getElementById("use"); + + g.appendChild(use); + document.documentElement.appendChild(use); + document.documentElement.appendChild(g); +} + + </script> + + <g id="g" style="counter-reset: c;"> + <rect width="1" height="1"/> + </g> + + <use id="use" xlink:href="#g"/> + +</svg> diff --git a/dom/svg/crashtests/414188-1.svg b/dom/svg/crashtests/414188-1.svg new file mode 100644 index 0000000000..9894975a6e --- /dev/null +++ b/dom/svg/crashtests/414188-1.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id="filter1"> + <feImage width="0"/> + <feTile/> +</filter> + +<rect width="100%" height="100%" filter="url(#filter1)"/> + +</svg> diff --git a/dom/svg/crashtests/427325-1.svg b/dom/svg/crashtests/427325-1.svg new file mode 100644 index 0000000000..1711fe8cf2 --- /dev/null +++ b/dom/svg/crashtests/427325-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + + <filter id="f"> + <feGaussianBlur stdDeviation="0.1"/> + </filter> + + <g filter="url(#f)"> + <rect width="100" height="100" fill="lime"/> + </g> + +</svg> diff --git a/dom/svg/crashtests/428228-1.svg b/dom/svg/crashtests/428228-1.svg new file mode 100644 index 0000000000..fb4fd8bb45 --- /dev/null +++ b/dom/svg/crashtests/428228-1.svg @@ -0,0 +1,17 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
+<head>
+<script type="text/javascript">
+
+// Test that removing a direct child element of an SVG <svg> doesnt' crash:
+
+function boom()
+{
+ document.getElementById("s").removeChild(document.getElementById("r"));
+}
+
+</script>
+</head>
+
+<body onload="boom();">ۀ<svg:svg id="s"><svg:rect id="r"/></svg:svg></body>
+
+</html>
diff --git a/dom/svg/crashtests/428841-1.svg b/dom/svg/crashtests/428841-1.svg new file mode 100644 index 0000000000..77234aba9d --- /dev/null +++ b/dom/svg/crashtests/428841-1.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ path.getPathSegAtLength(1);
+}
+
+</script>
+
+</svg>
diff --git a/dom/svg/crashtests/436418-mpathRoot-1.svg b/dom/svg/crashtests/436418-mpathRoot-1.svg new file mode 100644 index 0000000000..c1fcef780f --- /dev/null +++ b/dom/svg/crashtests/436418-mpathRoot-1.svg @@ -0,0 +1,4 @@ +<svg:mpath xmlns:svg="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xlink:href="#foo"> +</svg:mpath> diff --git a/dom/svg/crashtests/448244-1.svg b/dom/svg/crashtests/448244-1.svg new file mode 100644 index 0000000000..f36c73e8ad --- /dev/null +++ b/dom/svg/crashtests/448244-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + +<filter id="filter1"> + <feTile/> +</filter> + +<rect width="100%" height="100%" filter="url(#filter1)"/> + +</svg> diff --git a/dom/svg/crashtests/466576-1.xhtml b/dom/svg/crashtests/466576-1.xhtml new file mode 100644 index 0000000000..a27d42f574 --- /dev/null +++ b/dom/svg/crashtests/466576-1.xhtml @@ -0,0 +1,22 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<script type="text/javascript"> + +function boom() +{ + try { + document.getElementById("y").gradientTransform.baseVal.appendItem(function(){}); + } catch(e) { } + + document.documentElement.innerHTML; +} + +</script> +</head> + +<body onload="boom();"> + +<linearGradient id="y" xmlns="http://www.w3.org/2000/svg" /> + +</body> +</html> diff --git a/dom/svg/crashtests/499879-1.svg b/dom/svg/crashtests/499879-1.svg new file mode 100644 index 0000000000..b063d29dd6 --- /dev/null +++ b/dom/svg/crashtests/499879-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" onload="document.getElementById('b').tableValues.baseVal.initialize({});"><feFuncB id="b"/></svg> diff --git a/dom/svg/crashtests/535691-1.svg b/dom/svg/crashtests/535691-1.svg new file mode 100644 index 0000000000..8e5e4647ab --- /dev/null +++ b/dom/svg/crashtests/535691-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"><g transform="10 20"><animateTransform attributeName="transform"/></g></svg> diff --git a/dom/svg/crashtests/539167-1.svg b/dom/svg/crashtests/539167-1.svg new file mode 100644 index 0000000000..960ee57778 --- /dev/null +++ b/dom/svg/crashtests/539167-1.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <circle id="circle" transform=""> + <animateTransform attributeName="transform" from="0" to="0.6"/> + </circle> + <use xlink:href="#circle"/> +</svg> diff --git a/dom/svg/crashtests/573316-1.svg b/dom/svg/crashtests/573316-1.svg new file mode 100644 index 0000000000..5410a983fd --- /dev/null +++ b/dom/svg/crashtests/573316-1.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<text dx="㾫"/> +</svg> diff --git a/dom/svg/crashtests/579356-1.svg b/dom/svg/crashtests/579356-1.svg new file mode 100644 index 0000000000..eb9a8b049d --- /dev/null +++ b/dom/svg/crashtests/579356-1.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg">
+ <animateMotion dur="1" begin="0" path="
+ M 0 0
+ C 0,0 0,0
+ 501.208526, 390.4
+ 501.208543, 390.4
+ 0,0 0,0
+ "/>
+</svg>
diff --git a/dom/svg/crashtests/579356-2.svg b/dom/svg/crashtests/579356-2.svg new file mode 100644 index 0000000000..870601c221 --- /dev/null +++ b/dom/svg/crashtests/579356-2.svg @@ -0,0 +1,13 @@ +<?xml version="1.0" standalone="no"?> +<svg xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg"> + <path d="M 0 0 + C 0,0 0,0 + 501.208526, 390.4 + 501.208543, 390.4 + 0,0 0,0 0,0 0,0 0,0" stroke="black" id="p"/> + <script> + var path = document.getElementById("p"); + var segNum = path.getPathSegAtLength(1200); + </script> +</svg> diff --git a/dom/svg/crashtests/595608-1.svg b/dom/svg/crashtests/595608-1.svg new file mode 100644 index 0000000000..f36e4381fe --- /dev/null +++ b/dom/svg/crashtests/595608-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg"><rect><set to="k" type="s" attributeName="transform"/></rect></svg> diff --git a/dom/svg/crashtests/601251-1.html b/dom/svg/crashtests/601251-1.html new file mode 100644 index 0000000000..eb22dff408 --- /dev/null +++ b/dom/svg/crashtests/601251-1.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<script> + +var cm = document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix"); +cm.kernelMatrix.baseVal.appendItem({}); +cm.setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0"); + +</script> diff --git a/dom/svg/crashtests/601406-1.svg b/dom/svg/crashtests/601406-1.svg new file mode 100644 index 0000000000..3f5914a7cb --- /dev/null +++ b/dom/svg/crashtests/601406-1.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> +<![CDATA[ +function boom() { document.getElementById("a").getBBox(); } +window.addEventListener("load", boom, false); +]]> +</script> + +<a id="a"/> + +</svg> + diff --git a/dom/svg/crashtests/603145-1.svg b/dom/svg/crashtests/603145-1.svg new file mode 100644 index 0000000000..1e95e3dc77 --- /dev/null +++ b/dom/svg/crashtests/603145-1.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> +<![CDATA[ + +var q = document.createElementNS("http://www.w3.org/2000/svg", "text").rotate.baseVal.appendItem({}); +q.__proto__ = document.createTextNode("x"); +q.constructor; + +]]> +</script> +</svg> diff --git a/dom/svg/crashtests/613899-1.svg b/dom/svg/crashtests/613899-1.svg new file mode 100644 index 0000000000..581537703e --- /dev/null +++ b/dom/svg/crashtests/613899-1.svg @@ -0,0 +1,9 @@ +<svg xmlns='http://www.w3.org/2000/svg' + xmlns:xlink='http://www.w3.org/1999/xlink' + width='16' height='16'> + <image id='i' xlink:href='a.png' width='16' height='16'/> + <filter id="f" x="0%" y="0%" width="100%" height="100%"> + <feImage xlink:href="#image1" /> + </filter> + <rect filter="url(#f)" x="0" y="0" width="16" height="16"/> +</svg> diff --git a/dom/svg/crashtests/613899-2.svg b/dom/svg/crashtests/613899-2.svg new file mode 100644 index 0000000000..f88be69394 --- /dev/null +++ b/dom/svg/crashtests/613899-2.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <set/> + <filter id="disp"> + <feImage xlink:href="#THIS_SVG_FILE_AS_AN_IMAGE"/> + </filter> +</svg> diff --git a/dom/svg/crashtests/719779-1.svg b/dom/svg/crashtests/719779-1.svg new file mode 100644 index 0000000000..3e9ac742f3 --- /dev/null +++ b/dom/svg/crashtests/719779-1.svg @@ -0,0 +1,19 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="80" y="80" width="300" height="180" viewBox="0 0 200 120"> + + <defs> + <filter id="MyFilter" filterUnits="userSpaceOnUse" > + <feGaussianBlur /> + <feOffset /> + <feSpecularLighting > + <fePointLight /> + </feSpecularLighting> + <feComposite /> + <feMerge> + <feMergeNode /> + <feMergeNode /> + </feMerge> + </filter> + </defs> + <g transform="matrix(100,-2562303,3000,100,300,300) " filter="url(#MyFilter)"/> + +</svg> diff --git a/dom/svg/crashtests/723441-1.html b/dom/svg/crashtests/723441-1.html new file mode 100644 index 0000000000..959e9fa953 --- /dev/null +++ b/dom/svg/crashtests/723441-1.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> + <body> + <img id="x"> + <script> + var xhr = new XMLHttpRequest(); + xhr.open("GET", "723441-resource.svg"); + xhr.overrideMimeType( 'text/plain; charset=x-user-defined' ); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + var svg = xhr.responseText.replace(/#/g, '%23'); + var img = document.getElementById("x"); + img.onload = function() { + document.documentElement.className = ""; + } + img.src = "data:image/svg+xml;," + svg; + } + } + xhr.send(); + </script> + </body> +</html> diff --git a/dom/svg/crashtests/723441-resource.svg b/dom/svg/crashtests/723441-resource.svg new file mode 100644 index 0000000000..69830d856a --- /dev/null +++ b/dom/svg/crashtests/723441-resource.svg @@ -0,0 +1,4922 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="612pt" height="792pt" viewBox="0 0 612 792" version="1.1"> +<defs> +<g> +<symbol overflow="visible" id="glyph0-0"> +<path style="stroke:none;" d="M 4.375 -10.671875 L 4.375 -2.0625 C 4.375 -0.609375 4.1875 -0.40625 2.75 -0.328125 L 2.75 0 L 7.78125 0 L 7.78125 -0.328125 C 6.375 -0.390625 6.125 -0.625 6.125 -1.875 L 6.125 -10.671875 L 7.0625 -10.671875 C 9.015625 -10.671875 9.40625 -10.359375 9.796875 -8.46875 L 10.203125 -8.46875 L 10.109375 -11.40625 L 0.390625 -11.40625 L 0.296875 -8.46875 L 0.703125 -8.46875 C 1.125 -10.34375 1.515625 -10.671875 3.4375 -10.671875 Z M 4.375 -10.671875 "/> +</symbol> +<symbol overflow="visible" id="glyph0-1"> +<path style="stroke:none;" d="M 2.703125 -5.90625 C 3.421875 -6.703125 3.9375 -6.984375 4.609375 -6.984375 C 5.46875 -6.984375 5.90625 -6.375 5.90625 -5.171875 L 5.90625 -1.75 C 5.90625 -0.578125 5.734375 -0.359375 4.734375 -0.265625 L 4.734375 0 L 8.390625 0 L 8.390625 -0.265625 C 7.453125 -0.4375 7.359375 -0.5625 7.359375 -1.75 L 7.359375 -5.1875 C 7.359375 -6.984375 6.625 -7.921875 5.234375 -7.921875 C 4.21875 -7.921875 3.5 -7.5 2.703125 -6.46875 L 2.703125 -11.703125 L 2.625 -11.765625 C 2.03125 -11.546875 1.59375 -11.421875 0.640625 -11.140625 L 0.171875 -11 L 0.171875 -10.734375 C 0.234375 -10.75 0.296875 -10.75 0.375 -10.75 C 1.125 -10.75 1.25 -10.609375 1.25 -9.859375 L 1.25 -1.75 C 1.25 -0.546875 1.15625 -0.390625 0.15625 -0.265625 L 0.15625 0 L 3.875 0 L 3.875 -0.265625 C 2.875 -0.359375 2.703125 -0.5625 2.703125 -1.75 Z M 2.703125 -5.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph0-2"> +<path style="stroke:none;" d="M 7.03125 -2.828125 C 6.203125 -1.515625 5.453125 -1.015625 4.359375 -1.015625 C 3.375 -1.015625 2.640625 -1.515625 2.140625 -2.5 C 1.828125 -3.15625 1.703125 -3.71875 1.671875 -4.765625 L 6.96875 -4.765625 C 6.828125 -5.890625 6.65625 -6.390625 6.234375 -6.9375 C 5.71875 -7.5625 4.921875 -7.921875 4.03125 -7.921875 C 3.171875 -7.921875 2.359375 -7.609375 1.703125 -7.03125 C 0.890625 -6.3125 0.4375 -5.09375 0.4375 -3.6875 C 0.4375 -1.3125 1.671875 0.171875 3.65625 0.171875 C 5.28125 0.171875 6.578125 -0.84375 7.296875 -2.703125 Z M 1.703125 -5.328125 C 1.890625 -6.65625 2.484375 -7.296875 3.53125 -7.296875 C 4.578125 -7.296875 5 -6.8125 5.21875 -5.328125 Z M 1.703125 -5.328125 "/> +</symbol> +<symbol overflow="visible" id="glyph0-3"> +<path style="stroke:none;" d="M 11.609375 -9.859375 L 11.609375 -2.0625 C 11.609375 -0.640625 11.40625 -0.40625 10.03125 -0.328125 L 10.03125 0 L 14.859375 0 L 14.859375 -0.328125 C 13.609375 -0.40625 13.359375 -0.65625 13.359375 -1.875 L 13.359375 -9.515625 C 13.359375 -10.75 13.578125 -10.96875 14.859375 -11.078125 L 14.859375 -11.40625 L 11.4375 -11.40625 L 7.625 -2.703125 L 3.65625 -11.40625 L 0.234375 -11.40625 L 0.234375 -11.078125 C 1.65625 -10.984375 1.875 -10.78125 1.875 -9.515625 L 1.875 -2.53125 C 1.875 -0.75 1.640625 -0.4375 0.203125 -0.328125 L 0.203125 0 L 4.25 0 L 4.25 -0.328125 C 2.921875 -0.390625 2.640625 -0.796875 2.640625 -2.53125 L 2.640625 -9.46875 L 6.953125 0 L 7.203125 0 Z M 11.609375 -9.859375 "/> +</symbol> +<symbol overflow="visible" id="glyph0-4"> +<path style="stroke:none;" d="M 8.25 -0.859375 L 8.15625 -0.859375 C 7.375 -0.859375 7.1875 -1.046875 7.1875 -1.84375 L 7.1875 -7.75 L 4.453125 -7.75 L 4.453125 -7.453125 C 5.53125 -7.40625 5.734375 -7.234375 5.734375 -6.375 L 5.734375 -2.328125 C 5.734375 -1.84375 5.640625 -1.59375 5.40625 -1.40625 C 4.9375 -1.03125 4.40625 -0.828125 3.890625 -0.828125 C 3.21875 -0.828125 2.671875 -1.40625 2.671875 -2.140625 L 2.671875 -7.75 L 0.15625 -7.75 L 0.15625 -7.5 C 0.984375 -7.453125 1.21875 -7.203125 1.21875 -6.40625 L 1.21875 -2.0625 C 1.21875 -0.703125 2.046875 0.171875 3.3125 0.171875 C 3.9375 0.171875 4.609375 -0.109375 5.078125 -0.5625 L 5.8125 -1.3125 L 5.8125 0.125 L 5.890625 0.15625 C 6.75 -0.1875 7.375 -0.375 8.25 -0.625 Z M 8.25 -0.859375 "/> +</symbol> +<symbol overflow="visible" id="glyph0-5"> +<path style="stroke:none;" d="M 0.328125 -10.734375 L 0.4375 -10.734375 C 0.625 -10.75 0.828125 -10.765625 0.96875 -10.765625 C 1.515625 -10.765625 1.6875 -10.515625 1.6875 -9.71875 L 1.6875 -1.5 C 1.6875 -0.5625 1.453125 -0.34375 0.359375 -0.265625 L 0.359375 0 L 4.421875 0 L 4.421875 -0.265625 C 3.34375 -0.328125 3.140625 -0.5 3.140625 -1.453125 L 3.140625 -11.71875 L 3.0625 -11.765625 C 2.171875 -11.46875 1.515625 -11.296875 0.328125 -11 Z M 0.328125 -10.734375 "/> +</symbol> +<symbol overflow="visible" id="glyph0-6"> +<path style="stroke:none;" d="M 4.390625 -7.75 L 2.65625 -7.75 L 2.65625 -9.75 C 2.65625 -9.921875 2.640625 -9.96875 2.53125 -9.96875 C 2.40625 -9.8125 2.3125 -9.65625 2.1875 -9.484375 C 1.53125 -8.546875 0.796875 -7.71875 0.515625 -7.640625 C 0.328125 -7.53125 0.21875 -7.40625 0.21875 -7.3125 C 0.21875 -7.265625 0.234375 -7.234375 0.296875 -7.203125 L 1.203125 -7.203125 L 1.203125 -2.015625 C 1.203125 -0.5625 1.71875 0.171875 2.734375 0.171875 C 3.578125 0.171875 4.234375 -0.234375 4.796875 -1.140625 L 4.578125 -1.328125 C 4.21875 -0.890625 3.921875 -0.71875 3.546875 -0.71875 C 2.90625 -0.71875 2.65625 -1.1875 2.65625 -2.265625 L 2.65625 -7.203125 L 4.390625 -7.203125 Z M 4.390625 -7.75 "/> +</symbol> +<symbol overflow="visible" id="glyph0-7"> +<path style="stroke:none;" d="M 3.015625 -7.921875 L 0.34375 -6.96875 L 0.34375 -6.71875 L 0.484375 -6.734375 C 0.6875 -6.765625 0.90625 -6.78125 1.0625 -6.78125 C 1.484375 -6.78125 1.640625 -6.515625 1.640625 -5.75 L 1.640625 -1.75 C 1.640625 -0.515625 1.46875 -0.328125 0.28125 -0.265625 L 0.28125 0 L 4.359375 0 L 4.359375 -0.265625 C 3.21875 -0.34375 3.078125 -0.515625 3.078125 -1.75 L 3.078125 -7.875 Z M 2.203125 -11.765625 C 1.734375 -11.765625 1.34375 -11.359375 1.34375 -10.875 C 1.34375 -10.40625 1.71875 -10 2.203125 -10 C 2.703125 -10 3.09375 -10.390625 3.09375 -10.875 C 3.09375 -11.359375 2.703125 -11.765625 2.203125 -11.765625 Z M 2.203125 -11.765625 "/> +</symbol> +<symbol overflow="visible" id="glyph0-8"> +<path style="stroke:none;" d="M 0.671875 -4.421875 L 0.671875 -3.34375 L 4.90625 -3.34375 L 4.90625 -4.421875 Z M 0.671875 -4.421875 "/> +</symbol> +<symbol overflow="visible" id="glyph0-9"> +<path style="stroke:none;" d="M 3.484375 -5.015625 C 3.921875 -4.96875 4.21875 -4.953125 4.671875 -4.953125 C 6.015625 -4.953125 6.9375 -5.125 7.671875 -5.546875 C 8.71875 -6.09375 9.328125 -7.140625 9.328125 -8.28125 C 9.328125 -9 9.09375 -9.65625 8.625 -10.15625 C 7.9375 -10.921875 6.4375 -11.40625 4.828125 -11.40625 L 0.28125 -11.40625 L 0.28125 -11.078125 C 1.546875 -10.9375 1.71875 -10.765625 1.71875 -9.515625 L 1.71875 -2.0625 C 1.71875 -0.625 1.578125 -0.453125 0.28125 -0.328125 L 0.28125 0 L 5.09375 0 L 5.09375 -0.328125 C 3.734375 -0.375 3.484375 -0.625 3.484375 -1.875 Z M 3.484375 -10.171875 C 3.484375 -10.640625 3.59375 -10.765625 4.0625 -10.765625 C 6.390625 -10.765625 7.453125 -9.953125 7.453125 -8.171875 C 7.453125 -6.515625 6.4375 -5.640625 4.453125 -5.640625 C 4.109375 -5.640625 3.875 -5.671875 3.484375 -5.703125 Z M 3.484375 -10.171875 "/> +</symbol> +<symbol overflow="visible" id="glyph0-10"> +<path style="stroke:none;" d="M 0.125 -6.71875 C 0.359375 -6.765625 0.515625 -6.78125 0.71875 -6.78125 C 1.15625 -6.78125 1.3125 -6.515625 1.3125 -5.75 L 1.3125 -1.453125 C 1.3125 -0.578125 1.1875 -0.46875 0.09375 -0.265625 L 0.09375 0 L 4.21875 0 L 4.21875 -0.265625 C 3.046875 -0.3125 2.75 -0.5625 2.75 -1.546875 L 2.75 -5.421875 C 2.75 -5.96875 3.5 -6.828125 3.953125 -6.828125 C 4.0625 -6.828125 4.21875 -6.75 4.40625 -6.578125 C 4.6875 -6.34375 4.875 -6.234375 5.09375 -6.234375 C 5.515625 -6.234375 5.765625 -6.53125 5.765625 -7.015625 C 5.765625 -7.578125 5.40625 -7.921875 4.828125 -7.921875 C 4.09375 -7.921875 3.59375 -7.53125 2.75 -6.296875 L 2.75 -7.890625 L 2.671875 -7.921875 C 1.75 -7.546875 1.140625 -7.3125 0.125 -6.984375 Z M 0.125 -6.71875 "/> +</symbol> +<symbol overflow="visible" id="glyph0-11"> +<path style="stroke:none;" d="M 0.28125 -6.859375 C 0.375 -6.90625 0.546875 -6.921875 0.734375 -6.921875 C 1.21875 -6.921875 1.375 -6.65625 1.375 -5.8125 L 1.375 -1.546875 C 1.375 -0.5625 1.1875 -0.328125 0.3125 -0.265625 L 0.3125 0 L 3.953125 0 L 3.953125 -0.265625 C 3.078125 -0.328125 2.828125 -0.53125 2.828125 -1.15625 L 2.828125 -5.984375 C 3.65625 -6.765625 4.03125 -6.96875 4.59375 -6.96875 C 5.4375 -6.96875 5.859375 -6.4375 5.859375 -5.296875 L 5.859375 -1.703125 C 5.859375 -0.625 5.625 -0.328125 4.765625 -0.265625 L 4.765625 0 L 8.34375 0 L 8.34375 -0.265625 C 7.5 -0.34375 7.296875 -0.546875 7.296875 -1.390625 L 7.296875 -5.34375 C 7.296875 -6.953125 6.546875 -7.921875 5.265625 -7.921875 C 4.484375 -7.921875 3.9375 -7.625 2.765625 -6.53125 L 2.765625 -7.890625 L 2.65625 -7.921875 C 1.8125 -7.609375 1.21875 -7.421875 0.28125 -7.140625 Z M 0.28125 -6.859375 "/> +</symbol> +<symbol overflow="visible" id="glyph0-12"> +<path style="stroke:none;" d="M 6.859375 -2.6875 C 6.03125 -1.484375 5.40625 -1.0625 4.421875 -1.0625 C 2.859375 -1.0625 1.75 -2.4375 1.75 -4.421875 C 1.75 -6.203125 2.703125 -7.421875 4.09375 -7.421875 C 4.71875 -7.421875 4.9375 -7.234375 5.109375 -6.59375 L 5.21875 -6.21875 C 5.359375 -5.734375 5.671875 -5.421875 6.03125 -5.421875 C 6.46875 -5.421875 6.859375 -5.75 6.859375 -6.140625 C 6.859375 -7.109375 5.640625 -7.921875 4.203125 -7.921875 C 3.359375 -7.921875 2.484375 -7.578125 1.78125 -6.953125 C 0.90625 -6.203125 0.4375 -5.03125 0.4375 -3.671875 C 0.4375 -1.421875 1.796875 0.171875 3.703125 0.171875 C 4.484375 0.171875 5.171875 -0.109375 5.78125 -0.640625 C 6.25 -1.046875 6.578125 -1.515625 7.09375 -2.53125 Z M 6.859375 -2.6875 "/> +</symbol> +<symbol overflow="visible" id="glyph0-13"> +<path style="stroke:none;" d="M 0.15625 -6.765625 C 0.3125 -6.78125 0.4375 -6.78125 0.578125 -6.78125 C 1.171875 -6.78125 1.296875 -6.609375 1.296875 -5.796875 L 1.296875 2.25 C 1.296875 3.15625 1.109375 3.34375 0.09375 3.4375 L 0.09375 3.734375 L 4.25 3.734375 L 4.25 3.421875 C 2.96875 3.40625 2.734375 3.21875 2.734375 2.140625 L 2.734375 -0.5625 C 3.34375 0 3.75 0.171875 4.484375 0.171875 C 6.515625 0.171875 8.09375 -1.75 8.09375 -4.25 C 8.09375 -6.390625 6.890625 -7.921875 5.21875 -7.921875 C 4.25 -7.921875 3.5 -7.484375 2.734375 -6.5625 L 2.734375 -7.890625 L 2.640625 -7.921875 C 1.703125 -7.5625 1.109375 -7.328125 0.15625 -7.046875 Z M 2.734375 -5.75 C 2.734375 -6.265625 3.703125 -6.890625 4.5 -6.890625 C 5.765625 -6.890625 6.609375 -5.578125 6.609375 -3.578125 C 6.609375 -1.671875 5.765625 -0.375 4.53125 -0.375 C 3.71875 -0.375 2.734375 -1 2.734375 -1.515625 Z M 2.734375 -5.75 "/> +</symbol> +<symbol overflow="visible" id="glyph0-14"> +<path style="stroke:none;" d="M 7.609375 -1.140625 C 7.3125 -0.890625 7.109375 -0.8125 6.859375 -0.8125 C 6.453125 -0.8125 6.34375 -1.046875 6.34375 -1.8125 L 6.34375 -5.171875 C 6.34375 -6.0625 6.25 -6.5625 5.984375 -6.96875 C 5.609375 -7.59375 4.875 -7.921875 3.859375 -7.921875 C 2.234375 -7.921875 0.96875 -7.078125 0.96875 -5.984375 C 0.96875 -5.59375 1.3125 -5.25 1.703125 -5.25 C 2.125 -5.25 2.484375 -5.59375 2.484375 -5.96875 C 2.484375 -6.046875 2.46875 -6.125 2.4375 -6.25 C 2.40625 -6.40625 2.390625 -6.546875 2.390625 -6.65625 C 2.390625 -7.125 2.9375 -7.5 3.640625 -7.5 C 4.484375 -7.5 4.9375 -7.015625 4.9375 -6.078125 L 4.9375 -5.03125 C 2.296875 -3.953125 2 -3.828125 1.25 -3.171875 C 0.875 -2.828125 0.640625 -2.234375 0.640625 -1.671875 C 0.640625 -0.578125 1.390625 0.171875 2.4375 0.171875 C 3.203125 0.171875 3.90625 -0.1875 4.953125 -1.078125 C 5.046875 -0.1875 5.359375 0.171875 6.0625 0.171875 C 6.640625 0.171875 7.015625 -0.03125 7.609375 -0.6875 Z M 4.9375 -2.125 C 4.9375 -1.578125 4.859375 -1.421875 4.5 -1.21875 C 4.078125 -0.984375 3.59375 -0.828125 3.234375 -0.828125 C 2.640625 -0.828125 2.15625 -1.40625 2.15625 -2.15625 L 2.15625 -2.21875 C 2.15625 -3.234375 2.859375 -3.859375 4.9375 -4.609375 Z M 4.9375 -2.125 "/> +</symbol> +<symbol overflow="visible" id="glyph0-15"> +<path style="stroke:none;" d="M 6.21875 -11.640625 C 2.921875 -11.640625 0.578125 -9.15625 0.578125 -5.703125 C 0.578125 -4.078125 1.140625 -2.515625 2.0625 -1.515625 C 3.0625 -0.4375 4.59375 0.234375 6.109375 0.234375 C 9.5 0.234375 11.84375 -2.171875 11.84375 -5.625 C 11.84375 -7.328125 11.34375 -8.796875 10.40625 -9.828125 C 9.3125 -11.015625 7.875 -11.640625 6.21875 -11.640625 Z M 6.21875 -11.015625 C 7.015625 -11.015625 7.796875 -10.703125 8.421875 -10.15625 C 9.34375 -9.3125 9.890625 -7.703125 9.890625 -5.640625 C 9.890625 -4.625 9.65625 -3.4375 9.3125 -2.546875 C 9.15625 -2.125 8.875 -1.6875 8.46875 -1.296875 C 7.875 -0.6875 7.09375 -0.375 6.1875 -0.375 C 5.390625 -0.375 4.609375 -0.6875 4.015625 -1.21875 C 3.109375 -2.015625 2.546875 -3.75 2.546875 -5.671875 C 2.546875 -7.421875 3.03125 -9.09375 3.75 -9.90625 C 4.421875 -10.640625 5.265625 -11.015625 6.21875 -11.015625 Z M 6.21875 -11.015625 "/> +</symbol> +<symbol overflow="visible" id="glyph0-16"> +<path style="stroke:none;" d="M 7.703125 -11.640625 L 7.328125 -11.640625 C 7.265625 -11.25 7.078125 -11.046875 6.78125 -11.046875 C 6.609375 -11.046875 6.3125 -11.125 6.03125 -11.265625 C 5.390625 -11.5 4.75 -11.640625 4.203125 -11.640625 C 3.46875 -11.640625 2.703125 -11.34375 2.140625 -10.84375 C 1.53125 -10.3125 1.21875 -9.59375 1.21875 -8.703125 C 1.21875 -7.3125 1.984375 -6.359375 3.90625 -5.34375 C 5.140625 -4.6875 6.046875 -3.984375 6.46875 -3.328125 C 6.625 -3.09375 6.71875 -2.734375 6.71875 -2.328125 C 6.71875 -1.171875 5.859375 -0.375 4.59375 -0.375 C 3.0625 -0.375 1.984375 -1.328125 1.125 -3.421875 L 0.71875 -3.421875 L 1.234375 0.21875 L 1.625 0.21875 C 1.640625 -0.109375 1.84375 -0.34375 2.09375 -0.34375 C 2.296875 -0.34375 2.578125 -0.28125 2.90625 -0.15625 C 3.5625 0.109375 4.25 0.234375 4.9375 0.234375 C 6.921875 0.234375 8.453125 -1.125 8.453125 -2.890625 C 8.453125 -4.3125 7.5 -5.421875 5.234375 -6.640625 C 3.421875 -7.640625 2.703125 -8.40625 2.703125 -9.328125 C 2.703125 -10.28125 3.421875 -10.9375 4.5 -10.9375 C 5.265625 -10.9375 5.984375 -10.609375 6.59375 -9.984375 C 7.125 -9.4375 7.375 -8.984375 7.640625 -7.96875 L 8.078125 -7.96875 Z M 7.703125 -11.640625 "/> +</symbol> +<symbol overflow="visible" id="glyph0-17"> +<path style="stroke:none;" d="M 10.671875 -7.75 L 10.515625 -11.640625 L 10.15625 -11.640625 C 10.0625 -11.28125 9.78125 -11.078125 9.4375 -11.078125 C 9.28125 -11.078125 9.015625 -11.125 8.765625 -11.234375 C 7.921875 -11.5 7.0625 -11.640625 6.25 -11.640625 C 4.84375 -11.640625 3.40625 -11.109375 2.34375 -10.15625 C 1.140625 -9.09375 0.484375 -7.484375 0.484375 -5.59375 C 0.484375 -4 1 -2.515625 1.875 -1.53125 C 2.90625 -0.40625 4.5 0.234375 6.203125 0.234375 C 8.140625 0.234375 9.84375 -0.546875 10.90625 -1.953125 L 10.59375 -2.25 C 9.3125 -1.03125 8.171875 -0.515625 6.75 -0.515625 C 5.671875 -0.515625 4.703125 -0.859375 3.953125 -1.515625 C 3.015625 -2.359375 2.484375 -3.90625 2.484375 -5.8125 C 2.484375 -8.9375 4.078125 -10.953125 6.578125 -10.953125 C 7.5625 -10.953125 8.453125 -10.59375 9.140625 -9.90625 C 9.6875 -9.34375 9.953125 -8.875 10.28125 -7.75 Z M 10.671875 -7.75 "/> +</symbol> +<symbol overflow="visible" id="glyph0-18"> +<path style="stroke:none;" d="M 4.3125 -7.921875 C 2.0625 -7.921875 0.5 -6.265625 0.5 -3.890625 C 0.5 -1.5625 2.09375 0.171875 4.265625 0.171875 C 6.4375 0.171875 8.09375 -1.65625 8.09375 -4.03125 C 8.09375 -6.28125 6.515625 -7.921875 4.3125 -7.921875 Z M 4.078125 -7.4375 C 5.53125 -7.4375 6.546875 -5.78125 6.546875 -3.421875 C 6.546875 -1.484375 5.765625 -0.3125 4.484375 -0.3125 C 3.8125 -0.3125 3.171875 -0.71875 2.8125 -1.40625 C 2.328125 -2.3125 2.046875 -3.515625 2.046875 -4.734375 C 2.046875 -6.375 2.859375 -7.4375 4.078125 -7.4375 Z M 4.078125 -7.4375 "/> +</symbol> +<symbol overflow="visible" id="glyph0-19"> +<path style="stroke:none;" d="M 5.421875 -5.40625 L 5.359375 -7.75 L 5.171875 -7.75 L 5.125 -7.71875 C 4.96875 -7.59375 4.953125 -7.578125 4.890625 -7.578125 C 4.78125 -7.578125 4.609375 -7.609375 4.421875 -7.703125 C 4.046875 -7.828125 3.671875 -7.90625 3.21875 -7.90625 C 1.859375 -7.90625 0.875 -7.03125 0.875 -5.78125 C 0.875 -4.828125 1.421875 -4.125 2.890625 -3.3125 L 3.890625 -2.734375 C 4.5 -2.390625 4.78125 -1.984375 4.78125 -1.453125 C 4.78125 -0.6875 4.234375 -0.203125 3.359375 -0.203125 C 2.765625 -0.203125 2.234375 -0.4375 1.90625 -0.8125 C 1.546875 -1.234375 1.390625 -1.640625 1.171875 -2.625 L 0.890625 -2.625 L 0.890625 0.0625 L 1.125 0.0625 C 1.234375 -0.109375 1.3125 -0.140625 1.515625 -0.140625 C 1.671875 -0.140625 1.90625 -0.109375 2.3125 0 C 2.796875 0.109375 3.25 0.171875 3.5625 0.171875 C 4.890625 0.171875 5.984375 -0.828125 5.984375 -2.03125 C 5.984375 -2.890625 5.578125 -3.46875 4.546875 -4.078125 L 2.6875 -5.1875 C 2.203125 -5.453125 1.953125 -5.890625 1.953125 -6.359375 C 1.953125 -7.046875 2.484375 -7.53125 3.265625 -7.53125 C 4.25 -7.53125 4.765625 -6.9375 5.171875 -5.40625 Z M 5.421875 -5.40625 "/> +</symbol> +<symbol overflow="visible" id="glyph0-20"> +<path style="stroke:none;" d="M 5.328125 -7.75 L 3.203125 -7.75 L 3.203125 -9.75 C 3.203125 -10.75 3.53125 -11.28125 4.1875 -11.28125 C 4.546875 -11.28125 4.78125 -11.109375 5.09375 -10.609375 C 5.375 -10.15625 5.578125 -9.984375 5.875 -9.984375 C 6.265625 -9.984375 6.59375 -10.296875 6.59375 -10.6875 C 6.59375 -11.3125 5.84375 -11.765625 4.796875 -11.765625 C 3.734375 -11.765625 2.828125 -11.296875 2.375 -10.5 C 1.921875 -9.75 1.796875 -9.125 1.78125 -7.75 L 0.359375 -7.75 L 0.359375 -7.203125 L 1.78125 -7.203125 L 1.78125 -1.796875 C 1.78125 -0.53125 1.578125 -0.328125 0.34375 -0.265625 L 0.34375 0 L 4.828125 0 L 4.828125 -0.265625 C 3.40625 -0.3125 3.21875 -0.5 3.21875 -1.796875 L 3.21875 -7.203125 L 5.328125 -7.203125 Z M 5.328125 -7.75 "/> +</symbol> +<symbol overflow="visible" id="glyph0-21"> +<path style="stroke:none;" d="M 12.203125 -6.09375 L 7.8125 -6.09375 L 7.8125 -5.78125 C 8.5625 -5.734375 8.765625 -5.6875 8.96875 -5.546875 C 9.234375 -5.390625 9.328125 -5 9.328125 -4.25 L 9.328125 -1.46875 C 9.328125 -0.90625 8.28125 -0.453125 7.046875 -0.453125 C 4.234375 -0.453125 2.515625 -2.40625 2.515625 -5.609375 C 2.515625 -7.234375 3 -8.796875 3.75 -9.640625 C 4.515625 -10.484375 5.578125 -10.953125 6.75 -10.953125 C 7.71875 -10.953125 8.5625 -10.625 9.234375 -10 C 9.75 -9.515625 10.015625 -9.0625 10.453125 -8 L 10.84375 -8 L 10.703125 -11.640625 L 10.328125 -11.640625 C 10.234375 -11.3125 9.921875 -11.078125 9.5625 -11.078125 C 9.390625 -11.078125 9.109375 -11.125 8.796875 -11.25 C 8.03125 -11.5 7.234375 -11.640625 6.453125 -11.640625 C 3.015625 -11.640625 0.546875 -9.109375 0.546875 -5.59375 C 0.546875 -3.90625 1.015625 -2.625 1.984375 -1.59375 C 3.140625 -0.40625 4.78125 0.234375 6.6875 0.234375 C 8.15625 0.234375 10.3125 -0.359375 11 -0.96875 L 11 -4.453125 C 11 -5.46875 11.203125 -5.703125 12.203125 -5.78125 Z M 12.203125 -6.09375 "/> +</symbol> +<symbol overflow="visible" id="glyph0-22"> +<path style="stroke:none;" d="M 7.203125 -2.328125 L 6.890625 -2.390625 C 6.71875 -1.53125 6.609375 -1.25 6.390625 -0.96875 C 6.140625 -0.671875 5.5625 -0.515625 4.6875 -0.515625 L 2.3125 -0.515625 L 6.9375 -7.484375 L 6.9375 -7.75 L 0.96875 -7.75 L 0.90625 -5.71875 L 1.21875 -5.71875 C 1.375 -6.96875 1.640625 -7.234375 2.671875 -7.234375 L 5.046875 -7.234375 L 0.46875 -0.265625 L 0.46875 0 L 6.953125 0 Z M 7.203125 -2.328125 "/> +</symbol> +<symbol overflow="visible" id="glyph0-23"> +<path style="stroke:none;" d="M 16.046875 -11.40625 L 12.640625 -11.40625 L 12.640625 -11.078125 C 13.515625 -10.984375 13.828125 -10.796875 13.828125 -10.328125 C 13.828125 -9.96875 13.71875 -9.515625 13.546875 -9.046875 L 11.40625 -3.203125 L 9.125 -9.078125 C 9.09375 -9.15625 8.9375 -9.546875 8.921875 -9.59375 C 8.765625 -9.953125 8.65625 -10.28125 8.65625 -10.46875 C 8.65625 -10.875 9.0625 -11.046875 9.984375 -11.078125 L 9.984375 -11.40625 L 5.390625 -11.40625 L 5.390625 -11.078125 C 6.375 -11.046875 6.546875 -10.90625 7.125 -9.515625 L 7.703125 -8.109375 L 5.859375 -3.25 L 3.375 -9.734375 C 3.25 -10.03125 3.1875 -10.328125 3.1875 -10.515625 C 3.1875 -10.90625 3.421875 -11.015625 4.3125 -11.078125 L 4.3125 -11.40625 L 0.09375 -11.40625 L 0.09375 -11.078125 C 0.96875 -10.984375 1.21875 -10.671875 1.859375 -9.0625 L 5.1875 0.1875 L 5.4375 0.1875 L 8.09375 -7.09375 L 10.84375 0.1875 L 11.109375 0.1875 C 12.5625 -4.3125 12.734375 -4.78125 14.625 -9.84375 C 14.953125 -10.75 15.140625 -10.90625 16.046875 -11.078125 Z M 16.046875 -11.40625 "/> +</symbol> +<symbol overflow="visible" id="glyph0-24"> +<path style="stroke:none;" d="M 2.640625 -11.71875 L 2.546875 -11.765625 C 1.828125 -11.5 1.359375 -11.359375 0.546875 -11.140625 L 0.046875 -11 L 0.046875 -10.734375 C 0.15625 -10.75 0.21875 -10.75 0.34375 -10.75 C 1.03125 -10.75 1.1875 -10.59375 1.1875 -9.859375 L 1.1875 -0.9375 C 1.1875 -0.390625 2.65625 0.171875 4.03125 0.171875 C 6.296875 0.171875 8.0625 -1.71875 8.0625 -4.1875 C 8.0625 -6.296875 6.75 -7.921875 5.03125 -7.921875 C 3.984375 -7.921875 2.984375 -7.296875 2.640625 -6.453125 Z M 2.640625 -5.546875 C 2.640625 -6.21875 3.4375 -6.828125 4.34375 -6.828125 C 5.6875 -6.828125 6.546875 -5.5 6.546875 -3.390625 C 6.546875 -1.484375 5.71875 -0.375 4.3125 -0.375 C 3.40625 -0.375 2.640625 -0.78125 2.640625 -1.203125 Z M 2.640625 -5.546875 "/> +</symbol> +<symbol overflow="visible" id="glyph0-25"> +<path style="stroke:none;" d="M 0.296875 -11.40625 L 0.296875 -11.078125 C 1.734375 -10.984375 1.953125 -10.796875 1.953125 -9.515625 L 1.953125 -1.875 C 1.953125 -0.609375 1.71875 -0.375 0.296875 -0.328125 L 0.296875 0 L 6.046875 0 C 7.390625 0 8.609375 -0.359375 9.25 -0.953125 C 9.859375 -1.5 10.203125 -2.265625 10.203125 -3.09375 C 10.203125 -3.859375 9.90625 -4.546875 9.359375 -5.046875 C 8.84375 -5.515625 8.390625 -5.71875 7.265625 -5.984375 C 8.15625 -6.21875 8.515625 -6.390625 8.9375 -6.75 C 9.359375 -7.125 9.625 -7.765625 9.625 -8.46875 C 9.625 -10.40625 8.09375 -11.40625 5.109375 -11.40625 Z M 3.703125 -5.609375 C 5.375 -5.609375 6.171875 -5.53125 6.78125 -5.265625 C 7.765625 -4.859375 8.234375 -4.171875 8.234375 -3.078125 C 8.234375 -2.15625 7.875 -1.484375 7.1875 -1.078125 C 6.625 -0.78125 5.921875 -0.640625 4.78125 -0.640625 C 3.9375 -0.640625 3.703125 -0.796875 3.703125 -1.34375 Z M 3.703125 -6.296875 L 3.703125 -10.25 C 3.703125 -10.609375 3.828125 -10.765625 4.078125 -10.765625 L 4.84375 -10.765625 C 6.8125 -10.765625 7.875 -9.9375 7.875 -8.40625 C 7.875 -7.0625 6.953125 -6.296875 5.34375 -6.296875 Z M 3.703125 -6.296875 "/> +</symbol> +<symbol overflow="visible" id="glyph0-26"> +<path style="stroke:none;" d="M 9.828125 -7.75 L 9.828125 -7.484375 C 10.421875 -7.375 10.59375 -7.25 10.59375 -6.953125 C 10.59375 -6.703125 10.484375 -6.28125 10.296875 -5.8125 L 8.75 -2 L 7.296875 -5.859375 C 7.015625 -6.640625 7.015625 -6.640625 7.015625 -6.875 C 7.015625 -7.25 7.203125 -7.375 8 -7.484375 L 8 -7.75 L 4.515625 -7.75 L 4.515625 -7.484375 C 5.140625 -7.421875 5.359375 -7.234375 5.703125 -6.296875 C 5.8125 -5.96875 5.9375 -5.640625 6.046875 -5.34375 L 4.484375 -1.90625 L 2.765625 -6.40625 C 2.703125 -6.578125 2.671875 -6.75 2.671875 -6.921875 C 2.671875 -7.28125 2.875 -7.421875 3.46875 -7.484375 L 3.46875 -7.75 L 0.359375 -7.75 L 0.359375 -7.484375 C 0.75 -7.453125 0.890625 -7.28125 1.28125 -6.40625 L 3.59375 -0.515625 C 3.8125 0 3.9375 0.234375 4.046875 0.234375 C 4.125 0.234375 4.265625 0.015625 4.484375 -0.4375 L 6.40625 -4.5625 L 7.96875 -0.5 C 8.21875 0.140625 8.28125 0.234375 8.390625 0.234375 C 8.5 0.234375 8.59375 0.09375 8.875 -0.609375 L 11.25 -6.5625 C 11.546875 -7.28125 11.609375 -7.375 11.953125 -7.484375 L 11.953125 -7.75 Z M 9.828125 -7.75 "/> +</symbol> +<symbol overflow="visible" id="glyph1-0"> +<path style="stroke:none;" d="M 2.5 -4.296875 L 2.5 -6.609375 C 2.5 -7.46875 2.625 -7.609375 3.546875 -7.6875 L 3.546875 -7.90625 L 0.234375 -7.90625 L 0.234375 -7.6875 C 1.140625 -7.609375 1.28125 -7.46875 1.28125 -6.609375 L 1.28125 -1.4375 C 1.28125 -0.4375 1.15625 -0.28125 0.234375 -0.234375 L 0.234375 0 L 3.546875 0 L 3.546875 -0.234375 C 2.65625 -0.296875 2.5 -0.453125 2.5 -1.296875 L 2.5 -3.765625 L 6.125 -3.765625 L 6.125 -1.4375 C 6.125 -0.4375 6 -0.28125 5.0625 -0.234375 L 5.0625 0 L 8.390625 0 L 8.390625 -0.234375 C 7.5 -0.296875 7.34375 -0.453125 7.34375 -1.296875 L 7.34375 -6.609375 C 7.34375 -7.46875 7.46875 -7.609375 8.390625 -7.6875 L 8.390625 -7.90625 L 5.0625 -7.90625 L 5.0625 -7.6875 C 5.984375 -7.609375 6.125 -7.46875 6.125 -6.609375 L 6.125 -4.296875 Z M 2.5 -4.296875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-1"> +<path style="stroke:none;" d="M 4.875 -1.953125 C 4.296875 -1.046875 3.796875 -0.703125 3.03125 -0.703125 C 2.34375 -0.703125 1.828125 -1.046875 1.484375 -1.734375 C 1.265625 -2.1875 1.1875 -2.578125 1.15625 -3.3125 L 4.84375 -3.3125 C 4.75 -4.09375 4.625 -4.4375 4.328125 -4.8125 C 3.96875 -5.25 3.421875 -5.5 2.796875 -5.5 C 2.203125 -5.5 1.640625 -5.28125 1.1875 -4.875 C 0.625 -4.390625 0.296875 -3.53125 0.296875 -2.5625 C 0.296875 -0.90625 1.15625 0.125 2.53125 0.125 C 3.671875 0.125 4.5625 -0.578125 5.0625 -1.875 Z M 1.1875 -3.6875 C 1.3125 -4.625 1.71875 -5.0625 2.453125 -5.0625 C 3.171875 -5.0625 3.46875 -4.734375 3.625 -3.6875 Z M 1.1875 -3.6875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-2"> +<path style="stroke:none;" d="M 0.234375 -7.453125 L 0.296875 -7.453125 C 0.4375 -7.453125 0.578125 -7.46875 0.671875 -7.46875 C 1.046875 -7.46875 1.171875 -7.296875 1.171875 -6.734375 L 1.171875 -1.046875 C 1.171875 -0.390625 1 -0.234375 0.25 -0.171875 L 0.25 0 L 3.078125 0 L 3.078125 -0.171875 C 2.3125 -0.234375 2.171875 -0.34375 2.171875 -1 L 2.171875 -8.140625 L 2.125 -8.15625 C 1.5 -7.953125 1.046875 -7.84375 0.234375 -7.640625 Z M 0.234375 -7.453125 "/> +</symbol> +<symbol overflow="visible" id="glyph1-3"> +<path style="stroke:none;" d="M 0.1875 -4.75 C 0.265625 -4.796875 0.375 -4.8125 0.515625 -4.8125 C 0.84375 -4.8125 0.953125 -4.625 0.953125 -4.046875 L 0.953125 -1.078125 C 0.953125 -0.390625 0.828125 -0.234375 0.21875 -0.171875 L 0.21875 0 L 2.75 0 L 2.75 -0.171875 C 2.140625 -0.234375 1.953125 -0.375 1.953125 -0.796875 L 1.953125 -4.15625 C 2.53125 -4.703125 2.796875 -4.84375 3.1875 -4.84375 C 3.78125 -4.84375 4.0625 -4.46875 4.0625 -3.6875 L 4.0625 -1.1875 C 4.0625 -0.4375 3.90625 -0.234375 3.3125 -0.171875 L 3.3125 0 L 5.796875 0 L 5.796875 -0.171875 C 5.21875 -0.234375 5.0625 -0.375 5.0625 -0.96875 L 5.0625 -3.703125 C 5.0625 -4.828125 4.546875 -5.5 3.65625 -5.5 C 3.109375 -5.5 2.734375 -5.296875 1.921875 -4.53125 L 1.921875 -5.46875 L 1.84375 -5.5 C 1.25 -5.28125 0.84375 -5.15625 0.1875 -4.953125 Z M 0.1875 -4.75 "/> +</symbol> +<symbol overflow="visible" id="glyph1-4"> +<path style="stroke:none;" d="M 3.328125 -6.609375 C 3.328125 -7.484375 3.453125 -7.609375 4.421875 -7.6875 L 4.421875 -7.90625 L 0.984375 -7.90625 L 0.984375 -7.6875 C 1.96875 -7.609375 2.109375 -7.484375 2.109375 -6.609375 L 2.109375 -1.078125 C 2.109375 -0.53125 1.953125 -0.28125 1.65625 -0.28125 C 1.484375 -0.28125 1.40625 -0.375 1.328125 -0.6875 C 1.203125 -1.09375 1.015625 -1.296875 0.703125 -1.296875 C 0.375 -1.296875 0.125 -1.015625 0.125 -0.6875 C 0.125 -0.1875 0.609375 0.171875 1.296875 0.171875 C 2.59375 0.171875 3.328125 -0.6875 3.328125 -2.1875 Z M 3.328125 -6.609375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-5"> +<path style="stroke:none;" d="M 1.5 -1.1875 C 1.140625 -1.1875 0.84375 -0.890625 0.84375 -0.515625 C 0.84375 -0.171875 1.140625 0.125 1.484375 0.125 C 1.859375 0.125 2.15625 -0.171875 2.15625 -0.515625 C 2.15625 -0.890625 1.859375 -1.1875 1.5 -1.1875 Z M 1.5 -1.1875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-6"> +<path style="stroke:none;" d="M 11.140625 -7.90625 L 8.78125 -7.90625 L 8.78125 -7.6875 C 9.390625 -7.625 9.59375 -7.5 9.59375 -7.171875 C 9.59375 -6.921875 9.53125 -6.609375 9.40625 -6.28125 L 7.90625 -2.21875 L 6.328125 -6.296875 C 6.3125 -6.359375 6.203125 -6.625 6.1875 -6.65625 C 6.078125 -6.90625 6.015625 -7.140625 6.015625 -7.265625 C 6.015625 -7.546875 6.28125 -7.671875 6.9375 -7.6875 L 6.9375 -7.90625 L 3.734375 -7.90625 L 3.734375 -7.6875 C 4.421875 -7.671875 4.546875 -7.5625 4.953125 -6.609375 L 5.34375 -5.625 L 4.0625 -2.265625 L 2.34375 -6.75 C 2.265625 -6.96875 2.21875 -7.171875 2.21875 -7.296875 C 2.21875 -7.5625 2.375 -7.65625 2.984375 -7.6875 L 2.984375 -7.90625 L 0.0625 -7.90625 L 0.0625 -7.6875 C 0.671875 -7.625 0.84375 -7.40625 1.296875 -6.28125 L 3.59375 0.125 L 3.78125 0.125 L 5.625 -4.921875 L 7.53125 0.125 L 7.703125 0.125 C 8.71875 -2.984375 8.84375 -3.328125 10.140625 -6.84375 C 10.375 -7.453125 10.5 -7.5625 11.140625 -7.6875 Z M 11.140625 -7.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-7"> +<path style="stroke:none;" d="M 5.28125 -0.78125 C 5.078125 -0.625 4.9375 -0.5625 4.75 -0.5625 C 4.484375 -0.5625 4.40625 -0.734375 4.40625 -1.25 L 4.40625 -3.578125 C 4.40625 -4.203125 4.34375 -4.546875 4.15625 -4.84375 C 3.890625 -5.265625 3.375 -5.5 2.671875 -5.5 C 1.546875 -5.5 0.671875 -4.90625 0.671875 -4.15625 C 0.671875 -3.890625 0.90625 -3.640625 1.1875 -3.640625 C 1.46875 -3.640625 1.71875 -3.890625 1.71875 -4.140625 C 1.71875 -4.203125 1.703125 -4.25 1.703125 -4.34375 C 1.671875 -4.453125 1.65625 -4.546875 1.65625 -4.625 C 1.65625 -4.953125 2.046875 -5.21875 2.515625 -5.21875 C 3.109375 -5.21875 3.4375 -4.859375 3.4375 -4.21875 L 3.4375 -3.484375 C 1.59375 -2.75 1.390625 -2.65625 0.875 -2.203125 C 0.609375 -1.953125 0.4375 -1.546875 0.4375 -1.15625 C 0.4375 -0.40625 0.96875 0.125 1.703125 0.125 C 2.21875 0.125 2.71875 -0.125 3.4375 -0.75 C 3.5 -0.125 3.71875 0.125 4.203125 0.125 C 4.609375 0.125 4.859375 -0.03125 5.28125 -0.484375 Z M 3.4375 -1.46875 C 3.4375 -1.09375 3.375 -0.984375 3.125 -0.84375 C 2.828125 -0.6875 2.5 -0.578125 2.25 -0.578125 C 1.828125 -0.578125 1.5 -0.984375 1.5 -1.5 L 1.5 -1.546875 C 1.5 -2.25 1.984375 -2.671875 3.4375 -3.203125 Z M 3.4375 -1.46875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-8"> +<path style="stroke:none;" d="M 5.625 -4.640625 L 5.625 -5.109375 L 4.703125 -5.109375 C 4.453125 -5.109375 4.28125 -5.140625 4.046875 -5.21875 L 3.78125 -5.3125 C 3.453125 -5.4375 3.125 -5.5 2.828125 -5.5 C 1.703125 -5.5 0.828125 -4.640625 0.828125 -3.546875 C 0.828125 -2.796875 1.140625 -2.34375 1.9375 -1.953125 C 1.703125 -1.734375 1.5 -1.53125 1.421875 -1.46875 C 1.03125 -1.125 0.875 -0.890625 0.875 -0.640625 C 0.875 -0.390625 1.015625 -0.25 1.5 -0.015625 C 0.65625 0.609375 0.328125 1 0.328125 1.453125 C 0.328125 2.078125 1.265625 2.609375 2.40625 2.609375 C 3.296875 2.609375 4.234375 2.296875 4.859375 1.796875 C 5.3125 1.421875 5.515625 1.046875 5.515625 0.578125 C 5.515625 -0.15625 4.953125 -0.65625 4.0625 -0.6875 L 2.515625 -0.765625 C 1.890625 -0.78125 1.59375 -0.890625 1.59375 -1.09375 C 1.59375 -1.328125 1.984375 -1.75 2.3125 -1.84375 C 2.421875 -1.828125 2.5 -1.8125 2.53125 -1.8125 C 2.765625 -1.796875 2.921875 -1.78125 2.984375 -1.78125 C 3.4375 -1.78125 3.90625 -1.953125 4.28125 -2.28125 C 4.671875 -2.625 4.859375 -3.03125 4.859375 -3.640625 C 4.859375 -3.984375 4.796875 -4.25 4.625 -4.640625 Z M 1.75 0.03125 C 2.15625 0.109375 3.109375 0.171875 3.6875 0.171875 C 4.78125 0.171875 5.171875 0.328125 5.171875 0.765625 C 5.171875 1.453125 4.265625 1.921875 2.921875 1.921875 C 1.859375 1.921875 1.171875 1.578125 1.171875 1.046875 C 1.171875 0.78125 1.25 0.625 1.75 0.03125 Z M 1.8125 -4.046875 C 1.8125 -4.75 2.15625 -5.15625 2.703125 -5.15625 C 3.078125 -5.15625 3.375 -4.953125 3.578125 -4.609375 C 3.796875 -4.1875 3.9375 -3.640625 3.9375 -3.171875 C 3.9375 -2.5 3.578125 -2.078125 3.03125 -2.078125 C 2.3125 -2.078125 1.8125 -2.859375 1.8125 -4 Z M 1.8125 -4.046875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-9"> +<path style="stroke:none;" d="M 0.984375 1.6875 C 1.796875 1.296875 2.328125 0.5625 2.328125 -0.15625 C 2.328125 -0.75 1.90625 -1.21875 1.375 -1.21875 C 0.953125 -1.21875 0.671875 -0.9375 0.671875 -0.53125 C 0.671875 -0.15625 0.9375 0.078125 1.375 0.078125 C 1.453125 0.078125 1.53125 0.0625 1.609375 0.046875 C 1.6875 0.03125 1.6875 0.03125 1.703125 0.03125 C 1.796875 0.03125 1.859375 0.09375 1.859375 0.1875 C 1.859375 0.578125 1.53125 1.015625 0.890625 1.453125 Z M 0.984375 1.6875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-10"> +<path style="stroke:none;" d="M 7.40625 -5.375 L 7.296875 -8.078125 L 7.046875 -8.078125 C 6.984375 -7.828125 6.796875 -7.6875 6.546875 -7.6875 C 6.4375 -7.6875 6.265625 -7.71875 6.078125 -7.796875 C 5.5 -7.984375 4.90625 -8.078125 4.34375 -8.078125 C 3.359375 -8.078125 2.359375 -7.703125 1.625 -7.046875 C 0.78125 -6.3125 0.328125 -5.203125 0.328125 -3.890625 C 0.328125 -2.765625 0.6875 -1.75 1.296875 -1.0625 C 2.015625 -0.28125 3.125 0.171875 4.296875 0.171875 C 5.65625 0.171875 6.84375 -0.375 7.5625 -1.34375 L 7.34375 -1.5625 C 6.46875 -0.71875 5.671875 -0.359375 4.6875 -0.359375 C 3.9375 -0.359375 3.265625 -0.59375 2.75 -1.046875 C 2.09375 -1.640625 1.71875 -2.71875 1.71875 -4.046875 C 1.71875 -6.203125 2.828125 -7.609375 4.5625 -7.609375 C 5.25 -7.609375 5.875 -7.34375 6.34375 -6.875 C 6.734375 -6.484375 6.90625 -6.15625 7.140625 -5.375 Z M 7.40625 -5.375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-11"> +<path style="stroke:none;" d="M 1.875 -4.09375 C 2.375 -4.65625 2.734375 -4.859375 3.203125 -4.859375 C 3.796875 -4.859375 4.09375 -4.421875 4.09375 -3.578125 L 4.09375 -1.21875 C 4.09375 -0.40625 3.984375 -0.25 3.28125 -0.171875 L 3.28125 0 L 5.828125 0 L 5.828125 -0.171875 C 5.171875 -0.296875 5.109375 -0.390625 5.109375 -1.21875 L 5.109375 -3.59375 C 5.109375 -4.859375 4.609375 -5.5 3.640625 -5.5 C 2.921875 -5.5 2.421875 -5.21875 1.875 -4.5 L 1.875 -8.125 L 1.8125 -8.15625 C 1.40625 -8.015625 1.109375 -7.921875 0.4375 -7.734375 L 0.125 -7.640625 L 0.125 -7.453125 C 0.171875 -7.453125 0.203125 -7.453125 0.265625 -7.453125 C 0.78125 -7.453125 0.875 -7.359375 0.875 -6.84375 L 0.875 -1.21875 C 0.875 -0.375 0.796875 -0.28125 0.109375 -0.171875 L 0.109375 0 L 2.6875 0 L 2.6875 -0.171875 C 2 -0.25 1.875 -0.390625 1.875 -1.21875 Z M 1.875 -4.09375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-12"> +<path style="stroke:none;" d="M 0.078125 -4.65625 C 0.25 -4.703125 0.359375 -4.703125 0.5 -4.703125 C 0.796875 -4.703125 0.90625 -4.515625 0.90625 -4 L 0.90625 -1 C 0.90625 -0.40625 0.828125 -0.328125 0.0625 -0.171875 L 0.0625 0 L 2.921875 0 L 2.921875 -0.171875 C 2.109375 -0.21875 1.90625 -0.390625 1.90625 -1.078125 L 1.90625 -3.765625 C 1.90625 -4.140625 2.421875 -4.75 2.75 -4.75 C 2.828125 -4.75 2.921875 -4.6875 3.0625 -4.5625 C 3.25 -4.40625 3.375 -4.328125 3.53125 -4.328125 C 3.828125 -4.328125 4 -4.53125 4 -4.859375 C 4 -5.265625 3.75 -5.5 3.34375 -5.5 C 2.84375 -5.5 2.5 -5.21875 1.90625 -4.375 L 1.90625 -5.46875 L 1.859375 -5.5 C 1.21875 -5.234375 0.78125 -5.078125 0.078125 -4.859375 Z M 0.078125 -4.65625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-13"> +<path style="stroke:none;" d="M 2.09375 -5.5 L 0.234375 -4.84375 L 0.234375 -4.65625 L 0.328125 -4.671875 C 0.484375 -4.703125 0.640625 -4.703125 0.734375 -4.703125 C 1.03125 -4.703125 1.140625 -4.515625 1.140625 -4 L 1.140625 -1.21875 C 1.140625 -0.359375 1.015625 -0.234375 0.1875 -0.171875 L 0.1875 0 L 3.03125 0 L 3.03125 -0.171875 C 2.234375 -0.234375 2.140625 -0.359375 2.140625 -1.21875 L 2.140625 -5.46875 Z M 1.53125 -8.15625 C 1.203125 -8.15625 0.9375 -7.890625 0.9375 -7.546875 C 0.9375 -7.21875 1.1875 -6.9375 1.53125 -6.9375 C 1.875 -6.9375 2.15625 -7.203125 2.15625 -7.546875 C 2.15625 -7.890625 1.875 -8.15625 1.53125 -8.15625 Z M 1.53125 -8.15625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-14"> +<path style="stroke:none;" d="M 3.765625 -3.75 L 3.71875 -5.375 L 3.578125 -5.375 L 3.5625 -5.359375 C 3.453125 -5.265625 3.4375 -5.265625 3.390625 -5.265625 C 3.328125 -5.265625 3.203125 -5.28125 3.078125 -5.34375 C 2.8125 -5.4375 2.546875 -5.484375 2.234375 -5.484375 C 1.296875 -5.484375 0.609375 -4.875 0.609375 -4.015625 C 0.609375 -3.34375 0.984375 -2.875 2.015625 -2.296875 L 2.703125 -1.90625 C 3.125 -1.65625 3.328125 -1.375 3.328125 -1 C 3.328125 -0.484375 2.9375 -0.140625 2.328125 -0.140625 C 1.921875 -0.140625 1.546875 -0.296875 1.328125 -0.5625 C 1.078125 -0.859375 0.96875 -1.140625 0.8125 -1.8125 L 0.625 -1.8125 L 0.625 0.046875 L 0.78125 0.046875 C 0.859375 -0.078125 0.90625 -0.09375 1.046875 -0.09375 C 1.15625 -0.09375 1.328125 -0.078125 1.609375 0 C 1.9375 0.078125 2.265625 0.125 2.46875 0.125 C 3.390625 0.125 4.15625 -0.578125 4.15625 -1.40625 C 4.15625 -2.015625 3.875 -2.40625 3.15625 -2.828125 L 1.859375 -3.59375 C 1.53125 -3.796875 1.34375 -4.09375 1.34375 -4.40625 C 1.34375 -4.890625 1.71875 -5.21875 2.265625 -5.21875 C 2.953125 -5.21875 3.3125 -4.8125 3.578125 -3.75 Z M 3.765625 -3.75 "/> +</symbol> +<symbol overflow="visible" id="glyph1-15"> +<path style="stroke:none;" d="M 8.46875 -4.234375 L 5.421875 -4.234375 L 5.421875 -4.015625 C 5.9375 -3.984375 6.078125 -3.9375 6.234375 -3.84375 C 6.40625 -3.734375 6.484375 -3.46875 6.484375 -2.953125 L 6.484375 -1.015625 C 6.484375 -0.640625 5.75 -0.3125 4.890625 -0.3125 C 2.9375 -0.3125 1.75 -1.671875 1.75 -3.890625 C 1.75 -5.015625 2.078125 -6.109375 2.609375 -6.6875 C 3.125 -7.28125 3.875 -7.609375 4.6875 -7.609375 C 5.359375 -7.609375 5.9375 -7.375 6.40625 -6.9375 C 6.765625 -6.609375 6.953125 -6.28125 7.25 -5.5625 L 7.53125 -5.5625 L 7.4375 -8.078125 L 7.171875 -8.078125 C 7.09375 -7.859375 6.890625 -7.6875 6.640625 -7.6875 C 6.515625 -7.6875 6.328125 -7.71875 6.109375 -7.8125 C 5.5625 -7.984375 5.015625 -8.078125 4.484375 -8.078125 C 2.09375 -8.078125 0.375 -6.328125 0.375 -3.890625 C 0.375 -2.71875 0.703125 -1.8125 1.375 -1.109375 C 2.171875 -0.28125 3.328125 0.171875 4.640625 0.171875 C 5.671875 0.171875 7.15625 -0.25 7.640625 -0.671875 L 7.640625 -3.09375 C 7.640625 -3.796875 7.78125 -3.953125 8.46875 -4.015625 Z M 8.46875 -4.234375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-16"> +<path style="stroke:none;" d="M 8.4375 -0.234375 C 7.90625 -0.265625 7.78125 -0.375 7.359375 -1.265625 L 4.390625 -8.0625 L 4.140625 -8.0625 L 1.65625 -2.1875 C 0.890625 -0.4375 0.75 -0.25 0.171875 -0.234375 L 0.171875 0 L 2.546875 0 L 2.546875 -0.234375 C 1.96875 -0.234375 1.734375 -0.375 1.734375 -0.71875 C 1.734375 -0.859375 1.765625 -1.03125 1.828125 -1.1875 L 2.375 -2.578125 L 5.515625 -2.578125 L 6 -1.4375 C 6.140625 -1.109375 6.234375 -0.796875 6.234375 -0.640625 C 6.234375 -0.328125 6.03125 -0.234375 5.390625 -0.234375 L 5.390625 0 L 8.4375 0 Z M 2.578125 -3.078125 L 3.953125 -6.359375 L 5.34375 -3.078125 Z M 2.578125 -3.078125 "/> +</symbol> +<symbol overflow="visible" id="glyph1-17"> +<path style="stroke:none;" d="M 3.328125 0 L 5.71875 0 L 5.71875 -0.171875 C 5.359375 -0.171875 5.109375 -0.375 4.75 -0.890625 L 3.21875 -3.234375 L 4.203125 -4.671875 C 4.4375 -5 4.796875 -5.1875 5.171875 -5.203125 L 5.171875 -5.375 L 3.28125 -5.375 L 3.28125 -5.203125 C 3.640625 -5.171875 3.765625 -5.109375 3.765625 -4.9375 C 3.765625 -4.796875 3.625 -4.53125 3.328125 -4.15625 C 3.265625 -4.09375 3.125 -3.875 2.96875 -3.640625 L 2.796875 -3.875 C 2.46875 -4.375 2.25 -4.78125 2.25 -4.9375 C 2.25 -5.109375 2.40625 -5.1875 2.765625 -5.203125 L 2.765625 -5.375 L 0.28125 -5.375 L 0.28125 -5.203125 L 0.390625 -5.203125 C 0.75 -5.203125 0.9375 -5.046875 1.3125 -4.484375 L 2.4375 -2.765625 L 1.078125 -0.78125 C 0.71875 -0.296875 0.59375 -0.21875 0.203125 -0.171875 L 0.203125 0 L 1.9375 0 L 1.9375 -0.171875 C 1.609375 -0.171875 1.453125 -0.234375 1.453125 -0.390625 C 1.453125 -0.46875 1.546875 -0.640625 1.703125 -0.890625 L 2.640625 -2.359375 L 3.734375 -0.6875 C 3.78125 -0.609375 3.796875 -0.53125 3.796875 -0.46875 C 3.796875 -0.25 3.71875 -0.203125 3.328125 -0.171875 Z M 3.328125 0 "/> +</symbol> +<symbol overflow="visible" id="glyph1-18"> +<path style="stroke:none;" d="M 4.109375 0.125 L 5.875 -0.5 L 5.875 -0.6875 C 5.65625 -0.6875 5.625 -0.6875 5.59375 -0.6875 C 5.15625 -0.6875 5.0625 -0.8125 5.0625 -1.359375 L 5.0625 -8.140625 L 5.015625 -8.15625 C 4.4375 -7.953125 4.015625 -7.84375 3.25 -7.640625 L 3.25 -7.453125 C 3.34375 -7.453125 3.421875 -7.453125 3.515625 -7.453125 C 3.953125 -7.453125 4.0625 -7.34375 4.0625 -6.84375 L 4.0625 -4.984375 C 3.609375 -5.359375 3.28125 -5.5 2.8125 -5.5 C 1.4375 -5.5 0.328125 -4.140625 0.328125 -2.453125 C 0.328125 -0.921875 1.21875 0.125 2.53125 0.125 C 3.203125 0.125 3.65625 -0.125 4.0625 -0.6875 L 4.0625 0.078125 Z M 4.0625 -1.21875 C 4.0625 -1.140625 3.984375 -0.984375 3.859375 -0.859375 C 3.640625 -0.625 3.34375 -0.5 3 -0.5 C 2.015625 -0.5 1.34375 -1.453125 1.34375 -2.921875 C 1.34375 -4.28125 1.9375 -5.15625 2.84375 -5.15625 C 3.484375 -5.15625 4.0625 -4.609375 4.0625 -3.96875 Z M 4.0625 -1.21875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-19"> +<path style="stroke:none;" d="M 8.0625 -6.84375 L 8.0625 -1.4375 C 8.0625 -0.4375 7.90625 -0.28125 6.96875 -0.234375 L 6.96875 0 L 10.3125 0 L 10.3125 -0.234375 C 9.4375 -0.28125 9.28125 -0.453125 9.28125 -1.296875 L 9.28125 -6.609375 C 9.28125 -7.453125 9.4375 -7.609375 10.3125 -7.6875 L 10.3125 -7.90625 L 7.9375 -7.90625 L 5.296875 -1.875 L 2.53125 -7.90625 L 0.171875 -7.90625 L 0.171875 -7.6875 C 1.140625 -7.625 1.296875 -7.484375 1.296875 -6.609375 L 1.296875 -1.75 C 1.296875 -0.53125 1.140625 -0.296875 0.140625 -0.234375 L 0.140625 0 L 2.953125 0 L 2.953125 -0.234375 C 2.03125 -0.28125 1.828125 -0.546875 1.828125 -1.75 L 1.828125 -6.578125 L 4.828125 0 L 5 0 Z M 8.0625 -6.84375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-20"> +<path style="stroke:none;" d="M 2.984375 -5.5 C 1.4375 -5.5 0.34375 -4.34375 0.34375 -2.703125 C 0.34375 -1.09375 1.453125 0.125 2.96875 0.125 C 4.46875 0.125 5.625 -1.140625 5.625 -2.796875 C 5.625 -4.359375 4.515625 -5.5 2.984375 -5.5 Z M 2.828125 -5.15625 C 3.84375 -5.15625 4.546875 -4.015625 4.546875 -2.375 C 4.546875 -1.03125 4 -0.21875 3.109375 -0.21875 C 2.640625 -0.21875 2.203125 -0.5 1.953125 -0.984375 C 1.609375 -1.609375 1.421875 -2.4375 1.421875 -3.28125 C 1.421875 -4.421875 1.984375 -5.15625 2.828125 -5.15625 Z M 2.828125 -5.15625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-21"> +<path style="stroke:none;" d="M 4.75 -1.859375 C 4.1875 -1.03125 3.75 -0.734375 3.078125 -0.734375 C 1.984375 -0.734375 1.21875 -1.703125 1.21875 -3.078125 C 1.21875 -4.296875 1.875 -5.15625 2.84375 -5.15625 C 3.28125 -5.15625 3.4375 -5.015625 3.546875 -4.578125 L 3.625 -4.3125 C 3.71875 -3.984375 3.9375 -3.765625 4.1875 -3.765625 C 4.5 -3.765625 4.75 -4 4.75 -4.265625 C 4.75 -4.9375 3.921875 -5.5 2.921875 -5.5 C 2.328125 -5.5 1.71875 -5.265625 1.234375 -4.828125 C 0.640625 -4.296875 0.296875 -3.484375 0.296875 -2.546875 C 0.296875 -0.984375 1.25 0.125 2.5625 0.125 C 3.109375 0.125 3.578125 -0.078125 4.015625 -0.4375 C 4.34375 -0.734375 4.5625 -1.046875 4.921875 -1.75 Z M 4.75 -1.859375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-22"> +<path style="stroke:none;" d="M 5.71875 -0.59375 L 5.671875 -0.59375 C 5.109375 -0.59375 4.984375 -0.734375 4.984375 -1.28125 L 4.984375 -5.375 L 3.09375 -5.375 L 3.09375 -5.171875 C 3.84375 -5.140625 3.984375 -5.015625 3.984375 -4.421875 L 3.984375 -1.609375 C 3.984375 -1.28125 3.921875 -1.109375 3.75 -0.984375 C 3.4375 -0.71875 3.0625 -0.578125 2.703125 -0.578125 C 2.234375 -0.578125 1.859375 -0.984375 1.859375 -1.484375 L 1.859375 -5.375 L 0.109375 -5.375 L 0.109375 -5.21875 C 0.6875 -5.171875 0.84375 -5 0.84375 -4.453125 L 0.84375 -1.4375 C 0.84375 -0.484375 1.421875 0.125 2.296875 0.125 C 2.734375 0.125 3.203125 -0.078125 3.53125 -0.390625 L 4.046875 -0.90625 L 4.046875 0.078125 L 4.09375 0.109375 C 4.6875 -0.125 5.109375 -0.265625 5.71875 -0.4375 Z M 5.71875 -0.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-23"> +<path style="stroke:none;" d="M 0.078125 -7.453125 C 0.234375 -7.453125 0.34375 -7.46875 0.46875 -7.46875 C 0.859375 -7.46875 0.984375 -7.3125 0.984375 -6.734375 L 0.984375 -0.984375 C 0.984375 -0.359375 0.9375 -0.328125 0.078125 -0.171875 L 0.078125 0 L 2.875 0 L 2.875 -0.171875 L 2.640625 -0.1875 C 2.15625 -0.21875 1.984375 -0.375 1.984375 -0.796875 L 1.984375 -3 L 3.65625 -0.765625 L 3.6875 -0.71875 C 3.71875 -0.6875 3.734375 -0.640625 3.78125 -0.609375 C 3.875 -0.484375 3.90625 -0.421875 3.90625 -0.359375 C 3.90625 -0.25 3.796875 -0.171875 3.65625 -0.171875 L 3.4375 -0.171875 L 3.4375 0 L 6.03125 0 L 6.03125 -0.171875 C 5.515625 -0.21875 5.140625 -0.4375 4.640625 -1.046875 L 2.8125 -3.375 L 3.15625 -3.6875 C 4 -4.484375 4.75 -5.03125 5.09375 -5.125 C 5.265625 -5.171875 5.4375 -5.203125 5.640625 -5.203125 L 5.734375 -5.203125 L 5.734375 -5.375 L 3.296875 -5.375 L 3.296875 -5.21875 C 3.765625 -5.203125 3.890625 -5.15625 3.890625 -4.984375 C 3.890625 -4.890625 3.796875 -4.71875 3.625 -4.578125 L 1.984375 -3.125 L 1.984375 -8.140625 L 1.9375 -8.15625 C 1.484375 -8.015625 1.140625 -7.921875 0.4375 -7.734375 L 0.078125 -7.640625 Z M 0.078125 -7.453125 "/> +</symbol> +<symbol overflow="visible" id="glyph1-24"> +<path style="stroke:none;" d="M 5.34375 -8.078125 L 5.09375 -8.078125 C 5.046875 -7.8125 4.90625 -7.671875 4.703125 -7.671875 C 4.59375 -7.671875 4.390625 -7.71875 4.1875 -7.8125 C 3.734375 -7.984375 3.296875 -8.078125 2.921875 -8.078125 C 2.40625 -8.078125 1.875 -7.875 1.484375 -7.53125 C 1.0625 -7.15625 0.84375 -6.65625 0.84375 -6.03125 C 0.84375 -5.078125 1.375 -4.40625 2.71875 -3.703125 C 3.578125 -3.25 4.203125 -2.765625 4.5 -2.3125 C 4.609375 -2.15625 4.65625 -1.90625 4.65625 -1.609375 C 4.65625 -0.8125 4.0625 -0.265625 3.1875 -0.265625 C 2.125 -0.265625 1.375 -0.921875 0.78125 -2.375 L 0.5 -2.375 L 0.859375 0.15625 L 1.125 0.15625 C 1.140625 -0.078125 1.28125 -0.234375 1.453125 -0.234375 C 1.59375 -0.234375 1.796875 -0.1875 2.015625 -0.109375 C 2.46875 0.078125 2.953125 0.171875 3.4375 0.171875 C 4.8125 0.171875 5.875 -0.78125 5.875 -2.015625 C 5.875 -2.984375 5.21875 -3.765625 3.640625 -4.609375 C 2.375 -5.3125 1.875 -5.828125 1.875 -6.484375 C 1.875 -7.140625 2.375 -7.59375 3.125 -7.59375 C 3.65625 -7.59375 4.15625 -7.359375 4.578125 -6.9375 C 4.953125 -6.546875 5.109375 -6.234375 5.3125 -5.53125 L 5.609375 -5.53125 Z M 5.34375 -8.078125 "/> +</symbol> +<symbol overflow="visible" id="glyph1-25"> +<path style="stroke:none;" d="M 0.234375 -4.75 C 0.375 -4.796875 0.484375 -4.8125 0.609375 -4.8125 C 0.921875 -4.8125 1.03125 -4.609375 1.03125 -4.046875 L 1.03125 -1.015625 C 1.03125 -0.375 0.859375 -0.1875 0.1875 -0.171875 L 0.1875 0 L 2.84375 0 L 2.84375 -0.171875 C 2.21875 -0.203125 2.03125 -0.328125 2.03125 -0.796875 L 2.03125 -4.171875 C 2.03125 -4.203125 2.125 -4.3125 2.21875 -4.40625 C 2.515625 -4.671875 3.03125 -4.875 3.4375 -4.875 C 3.96875 -4.875 4.234375 -4.453125 4.234375 -3.625 L 4.234375 -1.03125 C 4.234375 -0.359375 4.09375 -0.234375 3.421875 -0.171875 L 3.421875 0 L 6.09375 0 L 6.09375 -0.171875 C 5.421875 -0.1875 5.234375 -0.390625 5.234375 -1.140625 L 5.234375 -4.140625 C 5.59375 -4.65625 5.984375 -4.875 6.53125 -4.875 C 7.21875 -4.875 7.4375 -4.546875 7.4375 -3.5625 L 7.4375 -1.046875 C 7.4375 -0.359375 7.34375 -0.265625 6.640625 -0.171875 L 6.640625 0 L 9.265625 0 L 9.265625 -0.171875 L 8.953125 -0.203125 C 8.59375 -0.234375 8.4375 -0.4375 8.4375 -0.90625 L 8.4375 -3.375 C 8.4375 -4.78125 7.96875 -5.5 7.046875 -5.5 C 6.359375 -5.5 5.75 -5.1875 5.109375 -4.5 C 4.890625 -5.171875 4.484375 -5.5 3.84375 -5.5 C 3.3125 -5.5 2.96875 -5.328125 1.984375 -4.578125 L 1.984375 -5.46875 L 1.90625 -5.5 C 1.296875 -5.265625 0.890625 -5.140625 0.234375 -4.953125 Z M 0.234375 -4.75 "/> +</symbol> +<symbol overflow="visible" id="glyph1-26"> +<path style="stroke:none;" d="M 3.03125 -7.40625 L 3.03125 -1.4375 C 3.03125 -0.421875 2.90625 -0.28125 1.90625 -0.234375 L 1.90625 0 L 5.40625 0 L 5.40625 -0.234375 C 4.421875 -0.28125 4.25 -0.4375 4.25 -1.296875 L 4.25 -7.40625 L 4.90625 -7.40625 C 6.265625 -7.40625 6.53125 -7.203125 6.796875 -5.875 L 7.09375 -5.875 L 7.015625 -7.90625 L 0.28125 -7.90625 L 0.203125 -5.875 L 0.484375 -5.875 C 0.78125 -7.1875 1.046875 -7.40625 2.390625 -7.40625 Z M 3.03125 -7.40625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-27"> +<path style="stroke:none;" d="M 4.9375 -7.6875 C 5.078125 -7.671875 5.203125 -7.671875 5.25 -7.671875 C 5.609375 -7.65625 5.75 -7.546875 5.75 -7.3125 C 5.75 -7.046875 5.46875 -6.6875 4.8125 -6.078125 L 2.703125 -4.15625 L 2.703125 -6.609375 C 2.703125 -7.484375 2.828125 -7.609375 3.796875 -7.6875 L 3.796875 -7.90625 L 0.40625 -7.90625 L 0.40625 -7.6875 C 1.34375 -7.609375 1.484375 -7.453125 1.484375 -6.609375 L 1.484375 -1.4375 C 1.484375 -0.4375 1.34375 -0.28125 0.40625 -0.234375 L 0.40625 0 L 3.78125 0 L 3.78125 -0.234375 C 2.84375 -0.28125 2.703125 -0.4375 2.703125 -1.296875 L 2.703125 -3.53125 L 3.015625 -3.796875 L 4.28125 -2.53125 C 5.1875 -1.640625 5.828125 -0.78125 5.828125 -0.5 C 5.828125 -0.328125 5.671875 -0.25 5.328125 -0.234375 C 5.265625 -0.234375 5.140625 -0.234375 5 -0.234375 L 5 0 L 8.640625 0 L 8.640625 -0.234375 C 8.015625 -0.234375 7.859375 -0.359375 6.765625 -1.515625 L 3.984375 -4.5 L 6.25 -6.75 C 7.0625 -7.53125 7.25 -7.625 8.0625 -7.6875 L 8.0625 -7.90625 L 4.9375 -7.90625 Z M 4.9375 -7.6875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-28"> +<path style="stroke:none;" d="M 2.421875 -3.484375 C 2.71875 -3.453125 2.921875 -3.4375 3.234375 -3.4375 C 4.171875 -3.4375 4.8125 -3.5625 5.328125 -3.84375 C 6.046875 -4.234375 6.484375 -4.953125 6.484375 -5.75 C 6.484375 -6.25 6.3125 -6.703125 5.984375 -7.046875 C 5.515625 -7.578125 4.46875 -7.90625 3.34375 -7.90625 L 0.1875 -7.90625 L 0.1875 -7.6875 C 1.078125 -7.59375 1.1875 -7.46875 1.1875 -6.609375 L 1.1875 -1.4375 C 1.1875 -0.4375 1.09375 -0.3125 0.1875 -0.234375 L 0.1875 0 L 3.53125 0 L 3.53125 -0.234375 C 2.59375 -0.265625 2.421875 -0.4375 2.421875 -1.296875 Z M 2.421875 -7.0625 C 2.421875 -7.390625 2.5 -7.46875 2.828125 -7.46875 C 4.4375 -7.46875 5.171875 -6.90625 5.171875 -5.671875 C 5.171875 -4.515625 4.46875 -3.921875 3.09375 -3.921875 C 2.859375 -3.921875 2.6875 -3.9375 2.421875 -3.953125 Z M 2.421875 -7.0625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-29"> +<path style="stroke:none;" d="M 5.671875 -5.375 L 4.0625 -5.375 L 4.0625 -5.203125 C 4.453125 -5.203125 4.640625 -5.09375 4.640625 -4.90625 C 4.640625 -4.859375 4.625 -4.78125 4.59375 -4.703125 L 3.4375 -1.40625 L 2.0625 -4.421875 C 1.984375 -4.59375 1.9375 -4.75 1.9375 -4.875 C 1.9375 -5.09375 2.109375 -5.171875 2.625 -5.203125 L 2.625 -5.375 L 0.171875 -5.375 L 0.171875 -5.21875 C 0.484375 -5.15625 0.6875 -5.03125 0.78125 -4.828125 L 2.140625 -1.890625 L 2.171875 -1.796875 L 2.359375 -1.4375 C 2.6875 -0.84375 2.875 -0.40625 2.875 -0.234375 C 2.875 -0.046875 2.609375 0.703125 2.40625 1.0625 C 2.234375 1.375 1.96875 1.609375 1.8125 1.609375 C 1.734375 1.609375 1.625 1.578125 1.5 1.515625 C 1.28125 1.4375 1.078125 1.390625 0.875 1.390625 C 0.59375 1.390625 0.359375 1.625 0.359375 1.90625 C 0.359375 2.3125 0.734375 2.609375 1.25 2.609375 C 2.046875 2.609375 2.625 1.9375 3.265625 0.21875 L 5.109375 -4.65625 C 5.265625 -5.046875 5.390625 -5.15625 5.671875 -5.203125 Z M 5.671875 -5.375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-30"> +<path style="stroke:none;" d="M 8.328125 -7.90625 L 5.875 -7.90625 L 5.875 -7.6875 C 6.53125 -7.65625 6.75 -7.515625 6.75 -7.1875 C 6.75 -7 6.671875 -6.671875 6.53125 -6.3125 L 4.765625 -1.921875 L 2.96875 -5.96875 C 2.5625 -6.84375 2.46875 -7.109375 2.46875 -7.296875 C 2.46875 -7.515625 2.640625 -7.640625 3.03125 -7.65625 C 3.078125 -7.65625 3.203125 -7.671875 3.375 -7.6875 L 3.375 -7.90625 L 0.1875 -7.90625 L 0.1875 -7.6875 C 0.78125 -7.65625 0.9375 -7.5 1.453125 -6.4375 L 4.40625 0.125 L 4.578125 0.125 L 7.234375 -6.578125 C 7.625 -7.515625 7.75 -7.65625 8.328125 -7.6875 Z M 8.328125 -7.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-31"> +<path style="stroke:none;" d="M 3.046875 -5.375 L 1.84375 -5.375 L 1.84375 -6.765625 C 1.84375 -6.890625 1.828125 -6.921875 1.75 -6.921875 C 1.671875 -6.8125 1.609375 -6.703125 1.515625 -6.59375 C 1.0625 -5.921875 0.546875 -5.359375 0.359375 -5.3125 C 0.234375 -5.21875 0.15625 -5.140625 0.15625 -5.078125 C 0.15625 -5.046875 0.171875 -5.015625 0.203125 -5 L 0.84375 -5 L 0.84375 -1.40625 C 0.84375 -0.390625 1.1875 0.125 1.90625 0.125 C 2.484375 0.125 2.9375 -0.171875 3.328125 -0.78125 L 3.171875 -0.921875 C 2.921875 -0.625 2.71875 -0.5 2.46875 -0.5 C 2.015625 -0.5 1.84375 -0.828125 1.84375 -1.578125 L 1.84375 -5 L 3.046875 -5 Z M 3.046875 -5.375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-32"> +<path style="stroke:none;" d="M 3.6875 -5.375 L 2.21875 -5.375 L 2.21875 -6.765625 C 2.21875 -7.453125 2.453125 -7.828125 2.90625 -7.828125 C 3.15625 -7.828125 3.328125 -7.703125 3.53125 -7.359375 C 3.734375 -7.046875 3.875 -6.9375 4.078125 -6.9375 C 4.34375 -6.9375 4.578125 -7.140625 4.578125 -7.421875 C 4.578125 -7.859375 4.046875 -8.15625 3.328125 -8.15625 C 2.59375 -8.15625 1.953125 -7.84375 1.65625 -7.296875 C 1.34375 -6.765625 1.25 -6.328125 1.234375 -5.375 L 0.25 -5.375 L 0.25 -5 L 1.234375 -5 L 1.234375 -1.25 C 1.234375 -0.375 1.09375 -0.234375 0.234375 -0.171875 L 0.234375 0 L 3.34375 0 L 3.34375 -0.171875 C 2.359375 -0.21875 2.234375 -0.34375 2.234375 -1.25 L 2.234375 -5 L 3.6875 -5 Z M 3.6875 -5.375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-33"> +<path style="stroke:none;" d="M 7.875 -0.234375 C 7.421875 -0.265625 7.1875 -0.375 6.84375 -0.78125 L 4.375 -3.8125 C 5.171875 -3.96875 5.53125 -4.109375 5.921875 -4.4375 C 6.3125 -4.75 6.53125 -5.25 6.53125 -5.8125 C 6.53125 -6.328125 6.390625 -6.765625 6.078125 -7.109375 C 5.625 -7.609375 4.625 -7.90625 3.5 -7.90625 L 0.203125 -7.90625 L 0.203125 -7.6875 C 1.09375 -7.59375 1.21875 -7.453125 1.21875 -6.609375 L 1.21875 -1.4375 C 1.21875 -0.4375 1.09375 -0.296875 0.203125 -0.234375 L 0.203125 0 L 3.515625 0 L 3.515625 -0.234375 C 2.59375 -0.28125 2.4375 -0.4375 2.4375 -1.296875 L 2.4375 -3.65625 L 3.109375 -3.6875 L 5.953125 0 L 7.875 0 Z M 2.4375 -7.046875 C 2.4375 -7.375 2.5625 -7.46875 3.046875 -7.46875 C 4.546875 -7.46875 5.234375 -6.953125 5.234375 -5.828125 C 5.234375 -5.234375 4.984375 -4.734375 4.546875 -4.5 C 4 -4.203125 3.578125 -4.125 2.4375 -4.09375 Z M 2.4375 -7.046875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-34"> +<path style="stroke:none;" d="M 7.296875 -6.15625 C 7.296875 -7.375 7.453125 -7.5625 8.421875 -7.6875 L 8.421875 -7.90625 L 5.65625 -7.90625 L 5.65625 -7.6875 C 6.59375 -7.609375 6.78125 -7.34375 6.78125 -6.15625 L 6.78125 -2.921875 C 6.78125 -2.109375 6.6875 -1.65625 6.5 -1.296875 C 6.1875 -0.734375 5.421875 -0.359375 4.515625 -0.359375 C 3.671875 -0.359375 3.078125 -0.640625 2.765625 -1.203125 C 2.546875 -1.59375 2.46875 -2.03125 2.46875 -2.78125 L 2.46875 -6.609375 C 2.46875 -7.46875 2.59375 -7.609375 3.546875 -7.6875 L 3.546875 -7.90625 L 0.171875 -7.90625 L 0.171875 -7.6875 C 1.109375 -7.609375 1.25 -7.484375 1.25 -6.609375 L 1.25 -2.875 C 1.25 -0.8125 2.21875 0.171875 4.25 0.171875 C 5.546875 0.171875 6.4375 -0.25 6.90625 -1.0625 C 7.203125 -1.5625 7.296875 -2.109375 7.296875 -3.03125 Z M 7.296875 -6.15625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-35"> +<path style="stroke:none;" d="M 5.703125 -5.375 L 4.046875 -5.375 L 4.046875 -5.203125 C 4.421875 -5.15625 4.609375 -5.046875 4.609375 -4.8125 C 4.609375 -4.703125 4.578125 -4.578125 4.53125 -4.453125 L 3.34375 -1.359375 L 2.125 -4.421875 C 2.0625 -4.59375 2.015625 -4.75 2.015625 -4.859375 C 2.015625 -5.078125 2.15625 -5.15625 2.5625 -5.203125 L 2.5625 -5.375 L 0.234375 -5.375 L 0.234375 -5.203125 C 0.6875 -5.171875 0.765625 -5.0625 1.3125 -3.828125 L 2.75 -0.390625 C 2.765625 -0.328125 2.8125 -0.234375 2.84375 -0.140625 C 2.921875 0.078125 2.984375 0.171875 3.0625 0.171875 C 3.125 0.171875 3.21875 0.015625 3.390625 -0.4375 L 4.921875 -4.265625 C 5.265625 -5.078125 5.34375 -5.15625 5.703125 -5.203125 Z M 5.703125 -5.375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-36"> +<path style="stroke:none;" d="M 1.375 -1.296875 C 1.375 -0.40625 1.21875 -0.265625 0.21875 -0.234375 L 0.21875 0 L 3.765625 0 L 3.765625 -0.234375 C 2.78125 -0.265625 2.59375 -0.4375 2.59375 -1.296875 L 2.59375 -6.609375 C 2.59375 -7.484375 2.765625 -7.640625 3.765625 -7.6875 L 3.765625 -7.90625 L 0.21875 -7.90625 L 0.21875 -7.6875 C 1.234375 -7.625 1.375 -7.5 1.375 -6.609375 Z M 1.375 -1.296875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-37"> +<path style="stroke:none;" d="M 1.828125 -8.140625 L 1.765625 -8.15625 C 1.265625 -7.984375 0.9375 -7.890625 0.375 -7.734375 L 0.03125 -7.640625 L 0.03125 -7.453125 C 0.109375 -7.453125 0.15625 -7.453125 0.234375 -7.453125 C 0.71875 -7.453125 0.828125 -7.34375 0.828125 -6.84375 L 0.828125 -0.640625 C 0.828125 -0.28125 1.84375 0.125 2.796875 0.125 C 4.375 0.125 5.59375 -1.1875 5.59375 -2.90625 C 5.59375 -4.375 4.6875 -5.5 3.484375 -5.5 C 2.765625 -5.5 2.0625 -5.0625 1.828125 -4.484375 Z M 1.828125 -3.84375 C 1.828125 -4.3125 2.390625 -4.75 3.015625 -4.75 C 3.9375 -4.75 4.546875 -3.8125 4.546875 -2.359375 C 4.546875 -1.03125 3.96875 -0.265625 2.984375 -0.265625 C 2.359375 -0.265625 1.828125 -0.53125 1.828125 -0.84375 Z M 1.828125 -3.84375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-38"> +<path style="stroke:none;" d="M 0.46875 -3.078125 L 0.46875 -2.3125 L 3.40625 -2.3125 L 3.40625 -3.078125 Z M 0.46875 -3.078125 "/> +</symbol> +<symbol overflow="visible" id="glyph1-39"> +<path style="stroke:none;" d="M 0.109375 -4.703125 C 0.21875 -4.703125 0.296875 -4.703125 0.40625 -4.703125 C 0.8125 -4.703125 0.890625 -4.59375 0.890625 -4.03125 L 0.890625 1.5625 C 0.890625 2.1875 0.765625 2.3125 0.0625 2.390625 L 0.0625 2.59375 L 2.953125 2.59375 L 2.953125 2.375 C 2.0625 2.359375 1.90625 2.234375 1.90625 1.484375 L 1.90625 -0.390625 C 2.3125 0 2.609375 0.125 3.109375 0.125 C 4.515625 0.125 5.625 -1.21875 5.625 -2.953125 C 5.625 -4.4375 4.78125 -5.5 3.625 -5.5 C 2.953125 -5.5 2.421875 -5.203125 1.90625 -4.546875 L 1.90625 -5.46875 L 1.828125 -5.5 C 1.1875 -5.25 0.765625 -5.09375 0.109375 -4.890625 Z M 1.90625 -4 C 1.90625 -4.34375 2.5625 -4.78125 3.125 -4.78125 C 4 -4.78125 4.59375 -3.875 4.59375 -2.484375 C 4.59375 -1.15625 4 -0.265625 3.140625 -0.265625 C 2.578125 -0.265625 1.90625 -0.6875 1.90625 -1.046875 Z M 1.90625 -4 "/> +</symbol> +<symbol overflow="visible" id="glyph1-40"> +<path style="stroke:none;" d="M 6.828125 -5.375 L 6.828125 -5.203125 C 7.234375 -5.109375 7.34375 -5.03125 7.34375 -4.828125 C 7.34375 -4.65625 7.28125 -4.359375 7.140625 -4.046875 L 6.078125 -1.390625 L 5.0625 -4.0625 C 4.859375 -4.609375 4.859375 -4.609375 4.859375 -4.765625 C 4.859375 -5.03125 5 -5.109375 5.5625 -5.203125 L 5.5625 -5.375 L 3.125 -5.375 L 3.125 -5.203125 C 3.578125 -5.15625 3.71875 -5.015625 3.953125 -4.375 C 4.046875 -4.140625 4.125 -3.921875 4.203125 -3.703125 L 3.109375 -1.328125 L 1.921875 -4.453125 C 1.875 -4.5625 1.859375 -4.6875 1.859375 -4.8125 C 1.859375 -5.0625 2 -5.15625 2.40625 -5.203125 L 2.40625 -5.375 L 0.25 -5.375 L 0.25 -5.203125 C 0.53125 -5.171875 0.625 -5.0625 0.890625 -4.453125 L 2.5 -0.359375 C 2.640625 0 2.734375 0.171875 2.8125 0.171875 C 2.875 0.171875 2.96875 0.015625 3.109375 -0.296875 L 4.453125 -3.171875 L 5.53125 -0.34375 C 5.703125 0.09375 5.75 0.171875 5.828125 0.171875 C 5.90625 0.171875 5.96875 0.0625 6.15625 -0.421875 L 7.8125 -4.546875 C 8.015625 -5.0625 8.0625 -5.109375 8.296875 -5.203125 L 8.296875 -5.375 Z M 6.828125 -5.375 "/> +</symbol> +<symbol overflow="visible" id="glyph1-41"> +<path style="stroke:none;" d="M 8.21875 -0.875 C 7.296875 -0.453125 6.6875 -0.296875 5.875 -0.296875 C 3.84375 -0.296875 2.390625 -1.78125 2.390625 -3.859375 C 2.390625 -4.96875 2.828125 -6.09375 3.5 -6.796875 C 4.09375 -7.390625 4.890625 -7.703125 5.765625 -7.703125 C 7.640625 -7.703125 9.1875 -6.234375 9.1875 -4.453125 C 9.1875 -3.265625 8.4375 -2.109375 7.65625 -2.109375 C 7.34375 -2.109375 7.1875 -2.28125 7.1875 -2.65625 C 7.1875 -2.734375 7.203125 -2.796875 7.203125 -2.875 L 7.984375 -5.90625 L 7.15625 -5.90625 L 7.046875 -5.453125 C 6.78125 -5.9375 6.59375 -6.078125 6.1875 -6.078125 C 5.671875 -6.078125 5.25 -5.875 4.84375 -5.46875 C 4.203125 -4.84375 3.84375 -3.9375 3.84375 -3.0625 C 3.84375 -2.3125 4.296875 -1.71875 4.890625 -1.71875 C 5.421875 -1.71875 5.96875 -2.046875 6.390625 -2.578125 C 6.453125 -2.0625 6.890625 -1.703125 7.46875 -1.703125 C 8.65625 -1.703125 9.671875 -3.03125 9.671875 -4.578125 C 9.671875 -6.546875 7.953125 -8.078125 5.75 -8.078125 C 3.328125 -8.078125 1.390625 -6.234375 1.390625 -3.890625 C 1.390625 -1.609375 3.296875 0.171875 5.765625 0.171875 C 6.609375 0.171875 7.203125 0.015625 8.359375 -0.515625 Z M 6.34375 -5.546875 C 6.6875 -5.515625 6.859375 -5.234375 6.84375 -4.765625 C 6.8125 -4.203125 6.609375 -3.484375 6.296875 -2.921875 C 6.078125 -2.5 5.71875 -2.234375 5.40625 -2.234375 C 4.953125 -2.234375 4.6875 -2.625 4.6875 -3.25 C 4.6875 -3.890625 4.90625 -4.5 5.3125 -4.953125 C 5.640625 -5.328125 6.0625 -5.5625 6.34375 -5.546875 Z M 6.34375 -5.546875 "/> +</symbol> +<symbol overflow="visible" id="glyph1-42"> +<path style="stroke:none;" d="M 5.671875 -1.640625 L 5.515625 -1.703125 C 5.078125 -1.015625 4.921875 -0.90625 4.390625 -0.90625 L 1.53125 -0.90625 L 3.53125 -3.015625 C 4.609375 -4.125 5.0625 -5.03125 5.0625 -5.96875 C 5.0625 -7.15625 4.09375 -8.078125 2.859375 -8.078125 C 2.203125 -8.078125 1.578125 -7.8125 1.140625 -7.34375 C 0.75 -6.9375 0.578125 -6.546875 0.375 -5.703125 L 0.625 -5.640625 C 1.09375 -6.8125 1.53125 -7.203125 2.359375 -7.203125 C 3.359375 -7.203125 4.046875 -6.515625 4.046875 -5.515625 C 4.046875 -4.578125 3.484375 -3.46875 2.484375 -2.40625 L 0.359375 -0.140625 L 0.359375 0 L 5.015625 0 Z M 5.671875 -1.640625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-43"> +<path style="stroke:none;" d="M 3.03125 -8.078125 C 2.375 -8.078125 1.875 -7.875 1.4375 -7.453125 C 0.734375 -6.796875 0.28125 -5.421875 0.28125 -4.015625 C 0.28125 -2.71875 0.6875 -1.3125 1.25 -0.640625 C 1.6875 -0.125 2.296875 0.171875 2.984375 0.171875 C 3.59375 0.171875 4.109375 -0.03125 4.546875 -0.453125 C 5.234375 -1.109375 5.6875 -2.5 5.6875 -3.9375 C 5.6875 -6.390625 4.609375 -8.078125 3.03125 -8.078125 Z M 3 -7.765625 C 4 -7.765625 4.546875 -6.421875 4.546875 -3.921875 C 4.546875 -1.421875 4.015625 -0.140625 2.984375 -0.140625 C 1.953125 -0.140625 1.4375 -1.421875 1.4375 -3.90625 C 1.4375 -6.4375 1.96875 -7.765625 3 -7.765625 Z M 3 -7.765625 "/> +</symbol> +<symbol overflow="visible" id="glyph1-44"> +<path style="stroke:none;" d="M 0.703125 0.265625 C 2.046875 0.109375 2.703125 -0.125 3.515625 -0.703125 C 4.75 -1.609375 5.484375 -3.09375 5.484375 -4.703125 C 5.484375 -6.6875 4.390625 -8.078125 2.84375 -8.078125 C 1.421875 -8.078125 0.359375 -6.875 0.359375 -5.265625 C 0.359375 -3.796875 1.21875 -2.828125 2.515625 -2.828125 C 3.171875 -2.828125 3.671875 -3.03125 4.296875 -3.515625 C 3.8125 -1.5625 2.484375 -0.28125 0.671875 0.03125 Z M 4.328125 -4.25 C 4.328125 -4 4.28125 -3.890625 4.140625 -3.796875 C 3.8125 -3.5 3.375 -3.34375 2.9375 -3.34375 C 2.03125 -3.34375 1.453125 -4.25 1.453125 -5.671875 C 1.453125 -6.34375 1.65625 -7.0625 1.90625 -7.375 C 2.109375 -7.609375 2.40625 -7.75 2.75 -7.75 C 3.796875 -7.75 4.328125 -6.71875 4.328125 -4.703125 Z M 4.328125 -4.25 "/> +</symbol> +<symbol overflow="visible" id="glyph1-45"> +<path style="stroke:none;" d="M 3.484375 -8.078125 L 1.328125 -7 L 1.328125 -6.828125 C 1.46875 -6.890625 1.609375 -6.9375 1.65625 -6.953125 C 1.859375 -7.046875 2.0625 -7.09375 2.1875 -7.09375 C 2.4375 -7.09375 2.546875 -6.90625 2.546875 -6.53125 L 2.546875 -1.109375 C 2.546875 -0.71875 2.453125 -0.4375 2.265625 -0.328125 C 2.078125 -0.234375 1.90625 -0.1875 1.40625 -0.171875 L 1.40625 0 L 4.703125 0 L 4.703125 -0.171875 C 3.765625 -0.1875 3.578125 -0.3125 3.578125 -0.890625 L 3.578125 -8.0625 Z M 3.484375 -8.078125 "/> +</symbol> +<symbol overflow="visible" id="glyph1-46"> +<path style="stroke:none;" d="M 5.328125 -8.171875 C 3.96875 -8.0625 3.28125 -7.828125 2.40625 -7.21875 C 1.109375 -6.296875 0.40625 -4.9375 0.40625 -3.328125 C 0.40625 -2.296875 0.734375 -1.25 1.25 -0.640625 C 1.703125 -0.125 2.34375 0.171875 3.078125 0.171875 C 4.5625 0.171875 5.59375 -0.96875 5.59375 -2.625 C 5.59375 -4.140625 4.71875 -5.109375 3.34375 -5.109375 C 2.828125 -5.109375 2.5625 -5.03125 1.8125 -4.578125 C 2.140625 -6.390625 3.484375 -7.671875 5.359375 -7.984375 Z M 2.890625 -4.5625 C 3.921875 -4.5625 4.515625 -3.703125 4.515625 -2.21875 C 4.515625 -0.890625 4.046875 -0.171875 3.21875 -0.171875 C 2.15625 -0.171875 1.515625 -1.296875 1.515625 -3.140625 C 1.515625 -3.75 1.609375 -4.09375 1.859375 -4.265625 C 2.109375 -4.453125 2.46875 -4.5625 2.890625 -4.5625 Z M 2.890625 -4.5625 "/> +</symbol> +<symbol overflow="visible" id="glyph2-0"> +<path style="stroke:none;" d="M 3.375 -6.734375 C 3.375 -7.59375 3.375 -7.859375 3.671875 -8.15625 C 3.890625 -8.359375 4.15625 -8.65625 5 -8.703125 C 5.0625 -8.71875 5.109375 -8.765625 5.109375 -8.828125 C 5.109375 -8.96875 5.015625 -8.96875 4.859375 -8.96875 C 3.671875 -8.96875 2.609375 -8.359375 2.578125 -7.5 L 2.578125 -5.3125 C 2.578125 -4.1875 2.578125 -4 2.265625 -3.65625 C 2.109375 -3.484375 1.78125 -3.171875 1.03125 -3.125 C 0.9375 -3.125 0.859375 -3.109375 0.859375 -2.984375 C 0.859375 -2.875 0.9375 -2.875 1.046875 -2.859375 C 1.5625 -2.828125 2.578125 -2.5625 2.578125 -1.375 L 2.578125 0.984375 C 2.578125 1.6875 2.578125 2.09375 3.203125 2.53125 C 3.71875 2.890625 4.5 2.984375 4.859375 2.984375 C 5.015625 2.984375 5.109375 2.984375 5.109375 2.859375 C 5.109375 2.734375 5.03125 2.734375 4.90625 2.71875 C 3.96875 2.671875 3.53125 2.125 3.421875 1.703125 C 3.375 1.5625 3.375 1.546875 3.375 1.125 L 3.375 -0.671875 C 3.375 -1.03125 3.375 -1.640625 3.359375 -1.75 C 3.203125 -2.546875 2.4375 -2.859375 1.96875 -2.984375 C 3.375 -3.390625 3.375 -4.25 3.375 -4.578125 Z M 3.375 -6.734375 "/> +</symbol> +<symbol overflow="visible" id="glyph2-1"> +<path style="stroke:none;" d="M 3.375 -6.96875 C 3.375 -7.65625 3.375 -8.0625 2.765625 -8.515625 C 2.25 -8.859375 1.484375 -8.96875 1.09375 -8.96875 C 0.984375 -8.96875 0.859375 -8.96875 0.859375 -8.828125 C 0.859375 -8.71875 0.9375 -8.71875 1.046875 -8.703125 C 2 -8.640625 2.4375 -8.109375 2.546875 -7.671875 C 2.578125 -7.546875 2.578125 -7.515625 2.578125 -7.09375 L 2.578125 -5.3125 C 2.578125 -4.953125 2.578125 -4.34375 2.609375 -4.21875 C 2.765625 -3.4375 3.53125 -3.125 4 -2.984375 C 2.578125 -2.578125 2.578125 -1.734375 2.578125 -1.40625 L 2.578125 0.75 C 2.578125 1.609375 2.578125 1.875 2.296875 2.171875 C 2.078125 2.390625 1.8125 2.671875 0.96875 2.71875 C 0.90625 2.734375 0.859375 2.78125 0.859375 2.859375 C 0.859375 2.984375 0.984375 2.984375 1.09375 2.984375 C 2.296875 2.984375 3.359375 2.375 3.375 1.515625 L 3.375 -0.671875 C 3.375 -1.796875 3.375 -1.984375 3.6875 -2.3125 C 3.859375 -2.484375 4.1875 -2.8125 4.9375 -2.859375 C 5.015625 -2.859375 5.109375 -2.875 5.109375 -2.984375 C 5.109375 -3.109375 5.03125 -3.109375 4.90625 -3.125 C 4.40625 -3.15625 3.375 -3.40625 3.375 -4.609375 Z M 3.375 -6.96875 "/> +</symbol> +<symbol overflow="visible" id="glyph3-0"> +<path style="stroke:none;" d="M 6.875 -0.25 L 6.828125 -0.25 C 6.4375 -0.25 6.28125 -0.453125 5.78125 -1.640625 L 3.5625 -6.875 L 3.28125 -6.875 L 1.0625 -1.421875 C 0.6875 -0.484375 0.5625 -0.34375 0.09375 -0.25 L 0.09375 0 L 2.109375 0 L 2.109375 -0.25 C 1.53125 -0.296875 1.296875 -0.40625 1.296875 -0.65625 C 1.296875 -0.78125 1.375 -1.03125 1.578125 -1.578125 L 1.734375 -1.96875 L 3.984375 -1.96875 C 4.3125 -1.1875 4.4375 -0.828125 4.4375 -0.609375 C 4.4375 -0.40625 4.3125 -0.296875 3.953125 -0.28125 C 3.90625 -0.28125 3.78125 -0.265625 3.640625 -0.25 L 3.640625 0 L 6.875 0 Z M 1.890625 -2.359375 L 2.828125 -4.78125 L 3.828125 -2.359375 Z M 1.890625 -2.359375 "/> +</symbol> +<symbol overflow="visible" id="glyph3-1"> +<path style="stroke:none;" d="M 2.109375 -6.734375 L 0.171875 -6.734375 L 0.171875 -6.5 C 0.625 -6.40625 0.71875 -6.3125 0.71875 -5.921875 L 0.71875 0.125 L 0.84375 0.125 L 1.625 -0.421875 C 2.078125 -0.015625 2.4375 0.140625 2.9375 0.140625 C 4.265625 0.140625 5.1875 -0.890625 5.1875 -2.375 C 5.1875 -3.765625 4.421875 -4.71875 3.3125 -4.71875 C 2.84375 -4.71875 2.46875 -4.546875 2.109375 -4.15625 Z M 2.109375 -3.59375 C 2.265625 -4.015625 2.46875 -4.171875 2.796875 -4.171875 C 3.421875 -4.171875 3.734375 -3.515625 3.734375 -2.203125 C 3.734375 -0.828125 3.421875 -0.171875 2.796875 -0.171875 C 2.375 -0.171875 2.109375 -0.484375 2.109375 -0.96875 Z M 2.109375 -3.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph3-2"> +<path style="stroke:none;" d="M 3.390625 -3.25 L 3.390625 -4.6875 L 3.171875 -4.6875 C 3.109375 -4.546875 3.046875 -4.5 2.9375 -4.5 C 2.875 -4.5 2.765625 -4.515625 2.609375 -4.5625 C 2.28125 -4.671875 2.046875 -4.71875 1.828125 -4.71875 C 0.921875 -4.71875 0.265625 -4.09375 0.265625 -3.265625 C 0.265625 -2.609375 0.671875 -2.140625 1.6875 -1.71875 C 2.359375 -1.421875 2.640625 -1.171875 2.640625 -0.84375 C 2.640625 -0.453125 2.34375 -0.203125 1.890625 -0.203125 C 1.203125 -0.203125 0.734375 -0.640625 0.53125 -1.515625 L 0.25 -1.515625 L 0.25 0.125 L 0.5 0.125 C 0.609375 -0.078125 0.671875 -0.15625 0.75 -0.15625 C 0.8125 -0.15625 0.890625 -0.125 0.984375 -0.09375 C 1.25 0.03125 1.78125 0.140625 2.0625 0.140625 C 2.96875 0.140625 3.59375 -0.484375 3.59375 -1.375 C 3.59375 -2.078125 3.203125 -2.515625 2.21875 -2.9375 C 1.546875 -3.21875 1.265625 -3.46875 1.265625 -3.8125 C 1.265625 -4.140625 1.546875 -4.390625 1.921875 -4.390625 C 2.1875 -4.390625 2.453125 -4.28125 2.671875 -4.0625 C 2.875 -3.875 2.984375 -3.6875 3.140625 -3.25 Z M 3.390625 -3.25 "/> +</symbol> +<symbol overflow="visible" id="glyph3-3"> +<path style="stroke:none;" d="M 3.046875 -4.59375 L 2.109375 -4.59375 L 2.109375 -6.28125 L 1.859375 -6.28125 C 1.25 -5.421875 0.84375 -4.96875 0.203125 -4.421875 L 0.203125 -4.15625 L 0.71875 -4.15625 L 0.71875 -0.921875 C 0.71875 -0.28125 1.140625 0.125 1.828125 0.125 C 2.5 0.125 2.90625 -0.171875 3.3125 -1 L 3.0625 -1.109375 C 2.859375 -0.734375 2.703125 -0.59375 2.5 -0.59375 C 2.21875 -0.59375 2.109375 -0.75 2.109375 -1.15625 L 2.109375 -4.15625 L 3.046875 -4.15625 Z M 3.046875 -4.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph3-4"> +<path style="stroke:none;" d="M 2.171875 -4.59375 L 0.296875 -4.59375 L 0.296875 -4.359375 C 0.71875 -4.296875 0.828125 -4.171875 0.828125 -3.765625 L 0.828125 -0.84375 C 0.828125 -0.421875 0.734375 -0.3125 0.296875 -0.234375 L 0.296875 0 L 2.9375 0 L 2.9375 -0.234375 C 2.328125 -0.28125 2.21875 -0.40625 2.21875 -1.03125 L 2.21875 -2.90625 C 2.21875 -3.421875 2.5 -3.859375 2.828125 -3.859375 C 2.90625 -3.859375 2.984375 -3.78125 3.09375 -3.625 C 3.296875 -3.359375 3.4375 -3.265625 3.703125 -3.265625 C 4.0625 -3.265625 4.328125 -3.546875 4.328125 -3.9375 C 4.328125 -4.390625 3.984375 -4.71875 3.515625 -4.71875 C 3.015625 -4.71875 2.640625 -4.453125 2.171875 -3.78125 Z M 2.171875 -4.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph3-5"> +<path style="stroke:none;" d="M 4.71875 -0.640625 L 4.609375 -0.53125 C 4.578125 -0.515625 4.5625 -0.5 4.5 -0.5 C 4.359375 -0.5 4.296875 -0.59375 4.296875 -0.75 L 4.296875 -3.34375 C 4.296875 -4.1875 3.53125 -4.71875 2.328125 -4.71875 C 1.203125 -4.71875 0.4375 -4.203125 0.4375 -3.453125 C 0.4375 -3.046875 0.671875 -2.796875 1.09375 -2.796875 C 1.484375 -2.796875 1.765625 -3.046875 1.765625 -3.375 C 1.765625 -3.515625 1.703125 -3.65625 1.578125 -3.8125 C 1.5 -3.90625 1.46875 -3.96875 1.46875 -4.03125 C 1.46875 -4.234375 1.75 -4.390625 2.09375 -4.390625 C 2.671875 -4.390625 2.9375 -4.125 2.9375 -3.515625 L 2.9375 -2.796875 C 1.75 -2.4375 1.25 -2.234375 0.890625 -1.984375 C 0.453125 -1.6875 0.25 -1.34375 0.25 -0.921875 C 0.25 -0.3125 0.71875 0.140625 1.359375 0.140625 C 1.921875 0.140625 2.375 -0.0625 2.9375 -0.5625 C 3.046875 -0.046875 3.265625 0.140625 3.75 0.140625 C 4.171875 0.140625 4.484375 -0.015625 4.859375 -0.421875 Z M 2.921875 -1 C 2.65625 -0.6875 2.453125 -0.5625 2.21875 -0.5625 C 1.90625 -0.5625 1.703125 -0.84375 1.703125 -1.234375 C 1.703125 -1.8125 2.125 -2.234375 2.921875 -2.4375 Z M 2.921875 -1 "/> +</symbol> +<symbol overflow="visible" id="glyph3-6"> +<path style="stroke:none;" d="M 4.109375 -1.09375 C 3.734375 -0.671875 3.484375 -0.53125 3.078125 -0.53125 C 2.203125 -0.53125 1.65625 -1.390625 1.65625 -2.75 C 1.65625 -3.78125 1.96875 -4.40625 2.5 -4.40625 C 2.65625 -4.40625 2.796875 -4.328125 2.859375 -4.21875 C 2.90625 -4.125 2.90625 -4.125 2.90625 -3.703125 C 2.921875 -3.21875 3.09375 -2.984375 3.46875 -2.984375 C 3.890625 -2.984375 4.140625 -3.234375 4.140625 -3.625 C 4.140625 -4.234375 3.484375 -4.71875 2.609375 -4.71875 C 1.25 -4.71875 0.25 -3.65625 0.25 -2.21875 C 0.25 -0.84375 1.140625 0.140625 2.375 0.140625 C 3.15625 0.140625 3.703125 -0.171875 4.28125 -0.90625 Z M 4.109375 -1.09375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-0"> +<path style="stroke:none;" d="M 9.296875 -6.59375 L 7.3125 -6.59375 L 7.3125 -6.40625 C 7.828125 -6.359375 8 -6.25 8 -5.984375 C 8 -5.765625 7.9375 -5.515625 7.84375 -5.234375 L 6.59375 -1.859375 L 5.28125 -5.25 C 5.265625 -5.296875 5.171875 -5.515625 5.15625 -5.546875 C 5.078125 -5.765625 5.015625 -5.953125 5.015625 -6.0625 C 5.015625 -6.296875 5.25 -6.40625 5.78125 -6.40625 L 5.78125 -6.59375 L 3.125 -6.59375 L 3.125 -6.40625 C 3.6875 -6.40625 3.78125 -6.3125 4.125 -5.515625 L 4.453125 -4.6875 L 3.390625 -1.890625 L 1.953125 -5.625 C 1.890625 -5.8125 1.84375 -5.984375 1.84375 -6.09375 C 1.84375 -6.3125 1.984375 -6.375 2.5 -6.40625 L 2.5 -6.59375 L 0.046875 -6.59375 L 0.046875 -6.40625 C 0.5625 -6.359375 0.703125 -6.1875 1.078125 -5.25 L 3 0.109375 L 3.15625 0.109375 L 4.6875 -4.109375 L 6.28125 0.109375 L 6.4375 0.109375 C 7.28125 -2.5 7.375 -2.765625 8.46875 -5.703125 C 8.65625 -6.21875 8.765625 -6.3125 9.296875 -6.40625 Z M 9.296875 -6.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-1"> +<path style="stroke:none;" d="M 4.0625 -1.640625 C 3.59375 -0.875 3.15625 -0.59375 2.515625 -0.59375 C 1.953125 -0.59375 1.53125 -0.875 1.234375 -1.453125 C 1.0625 -1.828125 0.984375 -2.15625 0.96875 -2.765625 L 4.03125 -2.765625 C 3.953125 -3.40625 3.859375 -3.703125 3.609375 -4.015625 C 3.3125 -4.375 2.84375 -4.578125 2.328125 -4.578125 C 1.828125 -4.578125 1.359375 -4.40625 0.984375 -4.0625 C 0.515625 -3.65625 0.25 -2.953125 0.25 -2.140625 C 0.25 -0.75 0.96875 0.09375 2.109375 0.09375 C 3.0625 0.09375 3.8125 -0.484375 4.234375 -1.5625 Z M 0.984375 -3.078125 C 1.09375 -3.859375 1.4375 -4.234375 2.046875 -4.234375 C 2.65625 -4.234375 2.890625 -3.953125 3.015625 -3.078125 Z M 0.984375 -3.078125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-2"> +<path style="stroke:none;" d="M 1.53125 -6.78125 L 1.46875 -6.8125 C 1.0625 -6.65625 0.78125 -6.578125 0.3125 -6.453125 L 0.03125 -6.375 L 0.03125 -6.203125 C 0.09375 -6.21875 0.125 -6.21875 0.203125 -6.21875 C 0.59375 -6.21875 0.6875 -6.125 0.6875 -5.71875 L 0.6875 -0.53125 C 0.6875 -0.234375 1.53125 0.09375 2.328125 0.09375 C 3.65625 0.09375 4.671875 -1 4.671875 -2.421875 C 4.671875 -3.65625 3.90625 -4.578125 2.90625 -4.578125 C 2.296875 -4.578125 1.71875 -4.234375 1.53125 -3.734375 Z M 1.53125 -3.203125 C 1.53125 -3.59375 2 -3.953125 2.515625 -3.953125 C 3.296875 -3.953125 3.78125 -3.1875 3.78125 -1.96875 C 3.78125 -0.859375 3.3125 -0.21875 2.5 -0.21875 C 1.96875 -0.21875 1.53125 -0.453125 1.53125 -0.703125 Z M 1.53125 -3.203125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-3"> +<path style="stroke:none;" d="M 0.0625 -3.890625 C 0.203125 -3.921875 0.296875 -3.921875 0.421875 -3.921875 C 0.671875 -3.921875 0.75 -3.765625 0.75 -3.328125 L 0.75 -0.84375 C 0.75 -0.34375 0.6875 -0.265625 0.046875 -0.15625 L 0.046875 0 L 2.4375 0 L 2.4375 -0.15625 C 1.765625 -0.171875 1.59375 -0.328125 1.59375 -0.890625 L 1.59375 -3.140625 C 1.59375 -3.453125 2.03125 -3.953125 2.296875 -3.953125 C 2.359375 -3.953125 2.4375 -3.90625 2.546875 -3.8125 C 2.71875 -3.671875 2.828125 -3.609375 2.953125 -3.609375 C 3.1875 -3.609375 3.34375 -3.78125 3.34375 -4.0625 C 3.34375 -4.390625 3.125 -4.578125 2.796875 -4.578125 C 2.375 -4.578125 2.078125 -4.359375 1.59375 -3.65625 L 1.59375 -4.5625 L 1.546875 -4.578125 C 1.015625 -4.359375 0.65625 -4.234375 0.0625 -4.046875 Z M 0.0625 -3.890625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-4"> +<path style="stroke:none;" d="M 2.5 -4.578125 C 1.203125 -4.578125 0.296875 -3.625 0.296875 -2.25 C 0.296875 -0.90625 1.21875 0.09375 2.46875 0.09375 C 3.734375 0.09375 4.6875 -0.953125 4.6875 -2.328125 C 4.6875 -3.640625 3.765625 -4.578125 2.5 -4.578125 Z M 2.359375 -4.3125 C 3.203125 -4.3125 3.78125 -3.34375 3.78125 -1.984375 C 3.78125 -0.859375 3.34375 -0.171875 2.59375 -0.171875 C 2.203125 -0.171875 1.828125 -0.421875 1.625 -0.8125 C 1.34375 -1.328125 1.1875 -2.03125 1.1875 -2.734375 C 1.1875 -3.6875 1.65625 -4.3125 2.359375 -4.3125 Z M 2.359375 -4.3125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-5"> +<path style="stroke:none;" d="M 5.6875 -4.484375 L 5.6875 -4.34375 C 6.03125 -4.265625 6.125 -4.203125 6.125 -4.03125 C 6.125 -3.875 6.078125 -3.640625 5.96875 -3.375 L 5.0625 -1.15625 L 4.234375 -3.390625 C 4.0625 -3.84375 4.0625 -3.84375 4.0625 -3.984375 C 4.0625 -4.203125 4.171875 -4.265625 4.640625 -4.34375 L 4.640625 -4.484375 L 2.609375 -4.484375 L 2.609375 -4.34375 C 2.984375 -4.296875 3.09375 -4.1875 3.296875 -3.65625 C 3.375 -3.453125 3.4375 -3.265625 3.5 -3.09375 L 2.59375 -1.109375 L 1.609375 -3.703125 C 1.5625 -3.8125 1.546875 -3.90625 1.546875 -4 C 1.546875 -4.21875 1.671875 -4.296875 2 -4.34375 L 2 -4.484375 L 0.203125 -4.484375 L 0.203125 -4.34375 C 0.4375 -4.3125 0.515625 -4.21875 0.734375 -3.703125 L 2.078125 -0.296875 C 2.203125 0 2.28125 0.140625 2.34375 0.140625 C 2.390625 0.140625 2.46875 0.015625 2.59375 -0.25 L 3.703125 -2.640625 L 4.609375 -0.296875 C 4.75 0.078125 4.796875 0.140625 4.859375 0.140625 C 4.921875 0.140625 4.96875 0.046875 5.140625 -0.34375 L 6.515625 -3.796875 C 6.6875 -4.21875 6.71875 -4.265625 6.921875 -4.34375 L 6.921875 -4.484375 Z M 5.6875 -4.484375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-6"> +<path style="stroke:none;" d="M 3.140625 -3.125 L 3.09375 -4.484375 L 2.984375 -4.484375 L 2.96875 -4.46875 C 2.875 -4.390625 2.875 -4.390625 2.828125 -4.390625 C 2.765625 -4.390625 2.671875 -4.40625 2.5625 -4.453125 C 2.34375 -4.53125 2.125 -4.578125 1.859375 -4.578125 C 1.078125 -4.578125 0.515625 -4.0625 0.515625 -3.34375 C 0.515625 -2.796875 0.828125 -2.390625 1.671875 -1.90625 L 2.25 -1.578125 C 2.609375 -1.390625 2.765625 -1.140625 2.765625 -0.84375 C 2.765625 -0.40625 2.453125 -0.125 1.9375 -0.125 C 1.609375 -0.125 1.296875 -0.25 1.109375 -0.46875 C 0.890625 -0.71875 0.8125 -0.953125 0.671875 -1.515625 L 0.515625 -1.515625 L 0.515625 0.046875 L 0.640625 0.046875 C 0.71875 -0.0625 0.75 -0.078125 0.875 -0.078125 C 0.96875 -0.078125 1.109375 -0.0625 1.328125 0 C 1.609375 0.0625 1.890625 0.09375 2.0625 0.09375 C 2.828125 0.09375 3.46875 -0.484375 3.46875 -1.171875 C 3.46875 -1.671875 3.234375 -2 2.625 -2.359375 L 1.5625 -3 C 1.28125 -3.15625 1.125 -3.40625 1.125 -3.671875 C 1.125 -4.078125 1.4375 -4.359375 1.890625 -4.359375 C 2.46875 -4.359375 2.765625 -4.015625 2.984375 -3.125 Z M 3.140625 -3.125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-7"> +<path style="stroke:none;" d="M 1.75 -4.578125 L 0.203125 -4.03125 L 0.203125 -3.890625 L 0.28125 -3.890625 C 0.40625 -3.921875 0.53125 -3.921875 0.625 -3.921875 C 0.859375 -3.921875 0.953125 -3.765625 0.953125 -3.328125 L 0.953125 -1.015625 C 0.953125 -0.296875 0.84375 -0.1875 0.15625 -0.15625 L 0.15625 0 L 2.515625 0 L 2.515625 -0.15625 C 1.859375 -0.203125 1.78125 -0.296875 1.78125 -1.015625 L 1.78125 -4.5625 Z M 1.28125 -6.8125 C 1 -6.8125 0.78125 -6.578125 0.78125 -6.296875 C 0.78125 -6.015625 1 -5.796875 1.28125 -5.796875 C 1.5625 -5.796875 1.796875 -6.015625 1.796875 -6.296875 C 1.796875 -6.578125 1.5625 -6.8125 1.28125 -6.8125 Z M 1.28125 -6.8125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-8"> +<path style="stroke:none;" d="M 4.6875 -3.875 L 4.6875 -4.25 L 3.921875 -4.25 C 3.71875 -4.25 3.5625 -4.28125 3.375 -4.359375 L 3.15625 -4.4375 C 2.875 -4.53125 2.609375 -4.578125 2.359375 -4.578125 C 1.421875 -4.578125 0.6875 -3.875 0.6875 -2.953125 C 0.6875 -2.328125 0.953125 -1.953125 1.609375 -1.625 C 1.421875 -1.453125 1.25 -1.28125 1.1875 -1.21875 C 0.859375 -0.9375 0.734375 -0.734375 0.734375 -0.53125 C 0.734375 -0.328125 0.84375 -0.203125 1.25 -0.015625 C 0.546875 0.515625 0.28125 0.84375 0.28125 1.203125 C 0.28125 1.734375 1.0625 2.171875 2 2.171875 C 2.75 2.171875 3.53125 1.90625 4.046875 1.5 C 4.421875 1.1875 4.59375 0.875 4.59375 0.484375 C 4.59375 -0.125 4.125 -0.546875 3.390625 -0.578125 L 2.109375 -0.640625 C 1.578125 -0.65625 1.328125 -0.75 1.328125 -0.90625 C 1.328125 -1.109375 1.65625 -1.453125 1.921875 -1.53125 C 2.015625 -1.53125 2.078125 -1.515625 2.109375 -1.515625 C 2.296875 -1.5 2.4375 -1.484375 2.5 -1.484375 C 2.859375 -1.484375 3.265625 -1.640625 3.5625 -1.90625 C 3.890625 -2.1875 4.046875 -2.53125 4.046875 -3.03125 C 4.046875 -3.3125 4 -3.546875 3.859375 -3.875 Z M 1.46875 0.015625 C 1.796875 0.09375 2.59375 0.15625 3.078125 0.15625 C 3.984375 0.15625 4.3125 0.28125 4.3125 0.640625 C 4.3125 1.21875 3.5625 1.609375 2.4375 1.609375 C 1.5625 1.609375 0.984375 1.3125 0.984375 0.875 C 0.984375 0.640625 1.046875 0.515625 1.46875 0.015625 Z M 1.515625 -3.375 C 1.515625 -3.953125 1.796875 -4.3125 2.25 -4.3125 C 2.5625 -4.3125 2.828125 -4.140625 2.984375 -3.84375 C 3.15625 -3.484375 3.28125 -3.03125 3.28125 -2.640625 C 3.28125 -2.078125 2.984375 -1.734375 2.53125 -1.734375 C 1.9375 -1.734375 1.515625 -2.375 1.515625 -3.34375 Z M 1.515625 -3.375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-9"> +<path style="stroke:none;" d="M 0.15625 -3.96875 C 0.21875 -4 0.3125 -4 0.421875 -4 C 0.703125 -4 0.796875 -3.859375 0.796875 -3.375 L 0.796875 -0.890625 C 0.796875 -0.328125 0.6875 -0.1875 0.171875 -0.15625 L 0.171875 0 L 2.296875 0 L 2.296875 -0.15625 C 1.78125 -0.1875 1.640625 -0.3125 1.640625 -0.671875 L 1.640625 -3.46875 C 2.109375 -3.921875 2.328125 -4.03125 2.65625 -4.03125 C 3.15625 -4.03125 3.390625 -3.734375 3.390625 -3.078125 L 3.390625 -0.984375 C 3.390625 -0.359375 3.265625 -0.1875 2.765625 -0.15625 L 2.765625 0 L 4.828125 0 L 4.828125 -0.15625 C 4.34375 -0.203125 4.234375 -0.3125 4.234375 -0.8125 L 4.234375 -3.09375 C 4.234375 -4.03125 3.78125 -4.578125 3.046875 -4.578125 C 2.59375 -4.578125 2.28125 -4.421875 1.609375 -3.78125 L 1.609375 -4.5625 L 1.53125 -4.578125 C 1.046875 -4.40625 0.703125 -4.296875 0.15625 -4.140625 Z M 0.15625 -3.96875 "/> +</symbol> +<symbol overflow="visible" id="glyph4-10"> +<path style="stroke:none;" d="M 4.40625 -0.65625 C 4.234375 -0.515625 4.109375 -0.46875 3.96875 -0.46875 C 3.734375 -0.46875 3.671875 -0.609375 3.671875 -1.046875 L 3.671875 -2.984375 C 3.671875 -3.515625 3.625 -3.796875 3.46875 -4.03125 C 3.25 -4.390625 2.828125 -4.578125 2.234375 -4.578125 C 1.296875 -4.578125 0.5625 -4.09375 0.5625 -3.46875 C 0.5625 -3.234375 0.75 -3.046875 0.984375 -3.046875 C 1.21875 -3.046875 1.4375 -3.234375 1.4375 -3.453125 C 1.4375 -3.5 1.421875 -3.546875 1.421875 -3.625 C 1.390625 -3.703125 1.390625 -3.78125 1.390625 -3.859375 C 1.390625 -4.125 1.703125 -4.34375 2.109375 -4.34375 C 2.59375 -4.34375 2.859375 -4.0625 2.859375 -3.515625 L 2.859375 -2.90625 C 1.328125 -2.296875 1.15625 -2.21875 0.734375 -1.828125 C 0.515625 -1.640625 0.375 -1.296875 0.375 -0.96875 C 0.375 -0.34375 0.8125 0.09375 1.421875 0.09375 C 1.859375 0.09375 2.265625 -0.109375 2.875 -0.625 C 2.921875 -0.109375 3.09375 0.09375 3.515625 0.09375 C 3.84375 0.09375 4.0625 -0.015625 4.40625 -0.40625 Z M 2.859375 -1.21875 C 2.859375 -0.921875 2.8125 -0.828125 2.609375 -0.703125 C 2.359375 -0.5625 2.078125 -0.484375 1.875 -0.484375 C 1.53125 -0.484375 1.25 -0.8125 1.25 -1.25 L 1.25 -1.28125 C 1.25 -1.875 1.65625 -2.234375 2.859375 -2.671875 Z M 2.859375 -1.21875 "/> +</symbol> +<symbol overflow="visible" id="glyph4-11"> +<path style="stroke:none;" d="M 2.546875 -4.484375 L 1.53125 -4.484375 L 1.53125 -5.640625 C 1.53125 -5.734375 1.53125 -5.765625 1.46875 -5.765625 C 1.390625 -5.6875 1.328125 -5.59375 1.265625 -5.5 C 0.890625 -4.9375 0.453125 -4.46875 0.296875 -4.421875 C 0.1875 -4.359375 0.125 -4.28125 0.125 -4.234375 C 0.125 -4.203125 0.140625 -4.1875 0.171875 -4.171875 L 0.703125 -4.171875 L 0.703125 -1.171875 C 0.703125 -0.328125 1 0.09375 1.578125 0.09375 C 2.078125 0.09375 2.453125 -0.140625 2.78125 -0.65625 L 2.65625 -0.765625 C 2.4375 -0.515625 2.265625 -0.421875 2.046875 -0.421875 C 1.6875 -0.421875 1.53125 -0.6875 1.53125 -1.3125 L 1.53125 -4.171875 L 2.546875 -4.171875 Z M 2.546875 -4.484375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-12"> +<path style="stroke:none;" d="M 3.421875 0.09375 L 4.890625 -0.421875 L 4.890625 -0.578125 C 4.71875 -0.5625 4.6875 -0.5625 4.671875 -0.5625 C 4.3125 -0.5625 4.234375 -0.671875 4.234375 -1.140625 L 4.234375 -6.78125 L 4.171875 -6.8125 C 3.703125 -6.640625 3.34375 -6.546875 2.71875 -6.375 L 2.71875 -6.203125 C 2.796875 -6.21875 2.84375 -6.21875 2.9375 -6.21875 C 3.296875 -6.21875 3.390625 -6.125 3.390625 -5.71875 L 3.390625 -4.15625 C 3.015625 -4.46875 2.734375 -4.578125 2.34375 -4.578125 C 1.203125 -4.578125 0.265625 -3.453125 0.265625 -2.046875 C 0.265625 -0.765625 1.015625 0.09375 2.109375 0.09375 C 2.671875 0.09375 3.046875 -0.09375 3.390625 -0.5625 L 3.390625 0.0625 Z M 3.390625 -1.015625 C 3.390625 -0.953125 3.3125 -0.828125 3.21875 -0.71875 C 3.046875 -0.515625 2.796875 -0.421875 2.5 -0.421875 C 1.671875 -0.421875 1.125 -1.21875 1.125 -2.4375 C 1.125 -3.5625 1.609375 -4.3125 2.375 -4.3125 C 2.90625 -4.3125 3.390625 -3.84375 3.390625 -3.3125 Z M 3.390625 -1.015625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-13"> +<path style="stroke:none;" d="M 0.09375 -3.921875 C 0.171875 -3.921875 0.25 -3.921875 0.34375 -3.921875 C 0.671875 -3.921875 0.75 -3.828125 0.75 -3.359375 L 0.75 1.3125 C 0.75 1.828125 0.640625 1.9375 0.046875 2 L 0.046875 2.15625 L 2.46875 2.15625 L 2.46875 1.984375 C 1.71875 1.96875 1.578125 1.859375 1.578125 1.234375 L 1.578125 -0.328125 C 1.9375 0 2.171875 0.09375 2.59375 0.09375 C 3.765625 0.09375 4.6875 -1.015625 4.6875 -2.46875 C 4.6875 -3.703125 3.984375 -4.578125 3.015625 -4.578125 C 2.46875 -4.578125 2.03125 -4.34375 1.578125 -3.796875 L 1.578125 -4.5625 L 1.53125 -4.578125 C 0.984375 -4.375 0.640625 -4.25 0.09375 -4.078125 Z M 1.578125 -3.328125 C 1.578125 -3.625 2.140625 -3.984375 2.609375 -3.984375 C 3.34375 -3.984375 3.828125 -3.234375 3.828125 -2.078125 C 3.828125 -0.96875 3.34375 -0.21875 2.625 -0.21875 C 2.15625 -0.21875 1.578125 -0.578125 1.578125 -0.875 Z M 1.578125 -3.328125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-14"> +<path style="stroke:none;" d="M 0.1875 -6.203125 L 0.25 -6.203125 C 0.359375 -6.21875 0.484375 -6.234375 0.5625 -6.234375 C 0.875 -6.234375 0.984375 -6.09375 0.984375 -5.625 L 0.984375 -0.875 C 0.984375 -0.328125 0.84375 -0.203125 0.203125 -0.15625 L 0.203125 0 L 2.5625 0 L 2.5625 -0.15625 C 1.9375 -0.1875 1.8125 -0.296875 1.8125 -0.84375 L 1.8125 -6.78125 L 1.78125 -6.8125 C 1.25 -6.640625 0.875 -6.546875 0.1875 -6.375 Z M 0.1875 -6.203125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-15"> +<path style="stroke:none;" d="M 3.96875 -1.5625 C 3.484375 -0.859375 3.125 -0.625 2.5625 -0.625 C 1.65625 -0.625 1.015625 -1.421875 1.015625 -2.5625 C 1.015625 -3.59375 1.5625 -4.296875 2.375 -4.296875 C 2.734375 -4.296875 2.859375 -4.1875 2.953125 -3.8125 L 3.015625 -3.59375 C 3.09375 -3.3125 3.28125 -3.140625 3.484375 -3.140625 C 3.75 -3.140625 3.96875 -3.328125 3.96875 -3.5625 C 3.96875 -4.109375 3.265625 -4.578125 2.4375 -4.578125 C 1.9375 -4.578125 1.4375 -4.390625 1.03125 -4.03125 C 0.53125 -3.59375 0.25 -2.90625 0.25 -2.125 C 0.25 -0.828125 1.03125 0.09375 2.140625 0.09375 C 2.59375 0.09375 2.984375 -0.0625 3.34375 -0.375 C 3.625 -0.609375 3.8125 -0.875 4.109375 -1.46875 Z M 3.96875 -1.5625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-16"> +<path style="stroke:none;" d="M 1.5625 -3.421875 C 1.984375 -3.875 2.28125 -4.046875 2.671875 -4.046875 C 3.171875 -4.046875 3.421875 -3.6875 3.421875 -2.984375 L 3.421875 -1.015625 C 3.421875 -0.34375 3.3125 -0.203125 2.734375 -0.15625 L 2.734375 0 L 4.859375 0 L 4.859375 -0.15625 C 4.3125 -0.25 4.25 -0.328125 4.25 -1.015625 L 4.25 -3 C 4.25 -4.046875 3.84375 -4.578125 3.03125 -4.578125 C 2.4375 -4.578125 2.03125 -4.34375 1.5625 -3.75 L 1.5625 -6.78125 L 1.515625 -6.8125 C 1.171875 -6.6875 0.921875 -6.609375 0.375 -6.453125 L 0.09375 -6.375 L 0.09375 -6.203125 C 0.140625 -6.21875 0.171875 -6.21875 0.21875 -6.21875 C 0.640625 -6.21875 0.734375 -6.140625 0.734375 -5.71875 L 0.734375 -1.015625 C 0.734375 -0.3125 0.671875 -0.234375 0.09375 -0.15625 L 0.09375 0 L 2.25 0 L 2.25 -0.15625 C 1.671875 -0.203125 1.5625 -0.328125 1.5625 -1.015625 Z M 1.5625 -3.421875 "/> +</symbol> +<symbol overflow="visible" id="glyph4-17"> +<path style="stroke:none;" d="M 4.78125 -0.5 L 4.71875 -0.5 C 4.265625 -0.5 4.15625 -0.609375 4.15625 -1.0625 L 4.15625 -4.484375 L 2.578125 -4.484375 L 2.578125 -4.3125 C 3.203125 -4.28125 3.3125 -4.1875 3.3125 -3.6875 L 3.3125 -1.34375 C 3.3125 -1.0625 3.265625 -0.921875 3.125 -0.8125 C 2.859375 -0.59375 2.546875 -0.484375 2.25 -0.484375 C 1.859375 -0.484375 1.546875 -0.8125 1.546875 -1.234375 L 1.546875 -4.484375 L 0.09375 -4.484375 L 0.09375 -4.34375 C 0.5625 -4.3125 0.703125 -4.171875 0.703125 -3.703125 L 0.703125 -1.203125 C 0.703125 -0.40625 1.1875 0.09375 1.90625 0.09375 C 2.28125 0.09375 2.671875 -0.0625 2.9375 -0.328125 L 3.375 -0.75 L 3.375 0.0625 L 3.40625 0.09375 C 3.90625 -0.109375 4.265625 -0.21875 4.78125 -0.359375 Z M 4.78125 -0.5 "/> +</symbol> +<symbol overflow="visible" id="glyph4-18"> +<path style="stroke:none;" d="M 4.75 -4.484375 L 3.375 -4.484375 L 3.375 -4.34375 C 3.6875 -4.3125 3.84375 -4.203125 3.84375 -4.015625 C 3.84375 -3.921875 3.8125 -3.8125 3.78125 -3.71875 L 2.796875 -1.140625 L 1.78125 -3.6875 C 1.71875 -3.828125 1.6875 -3.96875 1.6875 -4.0625 C 1.6875 -4.234375 1.796875 -4.3125 2.140625 -4.34375 L 2.140625 -4.484375 L 0.1875 -4.484375 L 0.1875 -4.34375 C 0.5625 -4.3125 0.640625 -4.21875 1.09375 -3.1875 L 2.296875 -0.328125 C 2.3125 -0.265625 2.34375 -0.203125 2.375 -0.125 C 2.4375 0.0625 2.5 0.140625 2.546875 0.140625 C 2.609375 0.140625 2.6875 0.015625 2.828125 -0.359375 L 4.109375 -3.5625 C 4.390625 -4.234375 4.453125 -4.3125 4.75 -4.34375 Z M 4.75 -4.484375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-19"> +<path style="stroke:none;" d="M 3.59375 -4.234375 C 3.125 -4.5 2.828125 -4.59375 2.4375 -4.59375 C 1.203125 -4.59375 0.234375 -3.484375 0.234375 -2.046875 C 0.234375 -0.78125 0.890625 0.09375 1.828125 0.09375 C 2.40625 0.09375 2.953125 -0.15625 3.40625 -0.640625 L 3.40625 1.234375 C 3.40625 1.796875 3.21875 1.9375 2.515625 2 L 2.515625 2.15625 L 4.859375 2.15625 L 4.859375 2.03125 C 4.3125 1.90625 4.234375 1.828125 4.234375 1.40625 L 4.234375 -4.5625 L 4.125 -4.5625 Z M 3.40625 -1.265625 C 3.40625 -1.03125 3.359375 -0.890625 3.265625 -0.8125 C 3.046875 -0.625 2.71875 -0.515625 2.40625 -0.515625 C 2.09375 -0.515625 1.828125 -0.609375 1.625 -0.8125 C 1.3125 -1.09375 1.09375 -1.75 1.09375 -2.40625 C 1.09375 -3.578125 1.625 -4.3125 2.46875 -4.3125 C 3.09375 -4.3125 3.40625 -3.984375 3.40625 -3.3125 Z M 3.40625 -1.265625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-20"> +<path style="stroke:none;" d="M 4.734375 -4.484375 L 3.390625 -4.484375 L 3.390625 -4.34375 C 3.703125 -4.34375 3.875 -4.25 3.875 -4.09375 C 3.875 -4.046875 3.859375 -3.984375 3.828125 -3.921875 L 2.859375 -1.171875 L 1.71875 -3.6875 C 1.65625 -3.828125 1.609375 -3.953125 1.609375 -4.0625 C 1.609375 -4.25 1.765625 -4.3125 2.1875 -4.34375 L 2.1875 -4.484375 L 0.140625 -4.484375 L 0.140625 -4.34375 C 0.40625 -4.3125 0.5625 -4.203125 0.640625 -4.03125 L 1.78125 -1.578125 L 1.8125 -1.5 L 1.96875 -1.203125 C 2.25 -0.703125 2.40625 -0.34375 2.40625 -0.1875 C 2.40625 -0.046875 2.171875 0.59375 2 0.890625 C 1.859375 1.140625 1.640625 1.328125 1.5 1.328125 C 1.453125 1.328125 1.359375 1.3125 1.25 1.265625 C 1.0625 1.203125 0.890625 1.15625 0.734375 1.15625 C 0.5 1.15625 0.296875 1.359375 0.296875 1.59375 C 0.296875 1.921875 0.625 2.171875 1.03125 2.171875 C 1.703125 2.171875 2.1875 1.609375 2.71875 0.171875 L 4.25 -3.890625 C 4.390625 -4.203125 4.5 -4.3125 4.734375 -4.34375 Z M 4.734375 -4.484375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-21"> +<path style="stroke:none;" d="M 1.25 -1 C 0.953125 -1 0.703125 -0.734375 0.703125 -0.421875 C 0.703125 -0.140625 0.953125 0.109375 1.234375 0.109375 C 1.546875 0.109375 1.796875 -0.140625 1.796875 -0.421875 C 1.796875 -0.734375 1.546875 -1 1.25 -1 Z M 1.25 -1 "/> +</symbol> +<symbol overflow="visible" id="glyph4-22"> +<path style="stroke:none;" d="M 7.03125 -0.1875 C 6.59375 -0.21875 6.484375 -0.3125 6.140625 -1.0625 L 3.65625 -6.71875 L 3.453125 -6.71875 L 1.390625 -1.828125 C 0.75 -0.375 0.625 -0.203125 0.15625 -0.1875 L 0.15625 0 L 2.125 0 L 2.125 -0.1875 C 1.640625 -0.1875 1.453125 -0.3125 1.453125 -0.59375 C 1.453125 -0.71875 1.46875 -0.859375 1.53125 -0.984375 L 1.984375 -2.15625 L 4.59375 -2.15625 L 5 -1.203125 C 5.125 -0.921875 5.1875 -0.671875 5.1875 -0.53125 C 5.1875 -0.28125 5.03125 -0.203125 4.5 -0.1875 L 4.5 0 L 7.03125 0 Z M 2.15625 -2.5625 L 3.296875 -5.296875 L 4.453125 -2.5625 Z M 2.15625 -2.5625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-23"> +<path style="stroke:none;" d="M 0.1875 -3.96875 C 0.3125 -4 0.40625 -4 0.515625 -4 C 0.765625 -4 0.859375 -3.84375 0.859375 -3.375 L 0.859375 -0.84375 C 0.859375 -0.3125 0.71875 -0.15625 0.15625 -0.15625 L 0.15625 0 L 2.375 0 L 2.375 -0.15625 C 1.84375 -0.171875 1.6875 -0.28125 1.6875 -0.671875 L 1.6875 -3.484375 C 1.6875 -3.5 1.78125 -3.59375 1.84375 -3.671875 C 2.09375 -3.890625 2.515625 -4.0625 2.875 -4.0625 C 3.3125 -4.0625 3.53125 -3.71875 3.53125 -3.015625 L 3.53125 -0.859375 C 3.53125 -0.296875 3.421875 -0.1875 2.84375 -0.15625 L 2.84375 0 L 5.078125 0 L 5.078125 -0.15625 C 4.515625 -0.15625 4.359375 -0.328125 4.359375 -0.953125 L 4.359375 -3.453125 C 4.671875 -3.890625 5 -4.0625 5.453125 -4.0625 C 6.015625 -4.0625 6.203125 -3.796875 6.203125 -2.96875 L 6.203125 -0.875 C 6.203125 -0.296875 6.125 -0.21875 5.546875 -0.15625 L 5.546875 0 L 7.71875 0 L 7.71875 -0.15625 L 7.46875 -0.171875 C 7.171875 -0.1875 7.03125 -0.375 7.03125 -0.75 L 7.03125 -2.8125 C 7.03125 -3.984375 6.65625 -4.578125 5.875 -4.578125 C 5.296875 -4.578125 4.796875 -4.328125 4.25 -3.75 C 4.078125 -4.3125 3.734375 -4.578125 3.203125 -4.578125 C 2.765625 -4.578125 2.484375 -4.453125 1.65625 -3.8125 L 1.65625 -4.5625 L 1.578125 -4.578125 C 1.078125 -4.390625 0.734375 -4.28125 0.1875 -4.140625 Z M 0.1875 -3.96875 "/> +</symbol> +<symbol overflow="visible" id="glyph4-24"> +<path style="stroke:none;" d="M 3.078125 -4.484375 L 1.859375 -4.484375 L 1.859375 -5.640625 C 1.859375 -6.21875 2.046875 -6.53125 2.421875 -6.53125 C 2.625 -6.53125 2.765625 -6.4375 2.953125 -6.140625 C 3.109375 -5.875 3.234375 -5.78125 3.40625 -5.78125 C 3.625 -5.78125 3.8125 -5.96875 3.8125 -6.1875 C 3.8125 -6.546875 3.375 -6.8125 2.78125 -6.8125 C 2.15625 -6.8125 1.640625 -6.546875 1.375 -6.078125 C 1.109375 -5.640625 1.03125 -5.28125 1.03125 -4.484375 L 0.203125 -4.484375 L 0.203125 -4.171875 L 1.03125 -4.171875 L 1.03125 -1.03125 C 1.03125 -0.3125 0.921875 -0.1875 0.203125 -0.15625 L 0.203125 0 L 2.796875 0 L 2.796875 -0.15625 C 1.96875 -0.171875 1.859375 -0.296875 1.859375 -1.03125 L 1.859375 -4.171875 L 3.078125 -4.171875 Z M 3.078125 -4.484375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-25"> +<path style="stroke:none;" d="M 0.828125 1.40625 C 1.5 1.078125 1.9375 0.46875 1.9375 -0.125 C 1.9375 -0.625 1.59375 -1.015625 1.140625 -1.015625 C 0.796875 -1.015625 0.5625 -0.78125 0.5625 -0.453125 C 0.5625 -0.125 0.78125 0.0625 1.140625 0.0625 C 1.203125 0.0625 1.28125 0.046875 1.328125 0.046875 C 1.40625 0.015625 1.40625 0.015625 1.421875 0.015625 C 1.5 0.015625 1.5625 0.078125 1.5625 0.15625 C 1.5625 0.484375 1.28125 0.84375 0.734375 1.21875 Z M 0.828125 1.40625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-26"> +<path style="stroke:none;" d="M 0.390625 -2.5625 L 0.390625 -1.9375 L 2.84375 -1.9375 L 2.84375 -2.5625 Z M 0.390625 -2.5625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-27"> +<path style="stroke:none;" d="M 6.09375 0.109375 L 6.09375 -5.140625 C 6.09375 -5.734375 6.203125 -6.09375 6.390625 -6.234375 C 6.53125 -6.328125 6.671875 -6.375 7.046875 -6.40625 L 7.046875 -6.59375 L 4.703125 -6.59375 L 4.703125 -6.40625 C 5.078125 -6.375 5.21875 -6.34375 5.375 -6.25 C 5.578125 -6.109375 5.65625 -5.75 5.65625 -5.140625 L 5.65625 -1.78125 L 1.828125 -6.59375 L 0.125 -6.59375 L 0.125 -6.40625 C 0.546875 -6.40625 0.6875 -6.328125 1.09375 -5.859375 L 1.09375 -1.46875 C 1.09375 -0.4375 0.953125 -0.25 0.125 -0.1875 L 0.125 0 L 2.46875 0 L 2.46875 -0.1875 C 1.6875 -0.234375 1.53125 -0.453125 1.53125 -1.46875 L 1.53125 -5.375 L 5.9375 0.109375 Z M 6.09375 0.109375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-28"> +<path style="stroke:none;" d="M 2.765625 0 L 4.78125 0 L 4.78125 -0.15625 C 4.46875 -0.15625 4.265625 -0.3125 3.953125 -0.75 L 2.6875 -2.703125 L 3.515625 -3.890625 C 3.703125 -4.171875 4 -4.328125 4.3125 -4.34375 L 4.3125 -4.484375 L 2.734375 -4.484375 L 2.734375 -4.34375 C 3.046875 -4.3125 3.140625 -4.25 3.140625 -4.109375 C 3.140625 -4 3.015625 -3.78125 2.765625 -3.46875 C 2.71875 -3.40625 2.609375 -3.234375 2.46875 -3.03125 L 2.328125 -3.234375 C 2.046875 -3.65625 1.875 -3.984375 1.875 -4.109375 C 1.875 -4.25 2 -4.328125 2.296875 -4.34375 L 2.296875 -4.484375 L 0.234375 -4.484375 L 0.234375 -4.34375 L 0.328125 -4.34375 C 0.625 -4.34375 0.78125 -4.203125 1.09375 -3.734375 L 2.03125 -2.296875 L 0.890625 -0.65625 C 0.59375 -0.25 0.5 -0.171875 0.171875 -0.15625 L 0.171875 0 L 1.609375 0 L 1.609375 -0.15625 C 1.328125 -0.15625 1.21875 -0.203125 1.21875 -0.328125 C 1.21875 -0.390625 1.28125 -0.53125 1.421875 -0.734375 L 2.203125 -1.96875 L 3.109375 -0.5625 C 3.15625 -0.515625 3.171875 -0.453125 3.171875 -0.390625 C 3.171875 -0.203125 3.09375 -0.171875 2.765625 -0.15625 Z M 2.765625 0 "/> +</symbol> +<symbol overflow="visible" id="glyph4-29"> +<path style="stroke:none;" d="M 0.0625 -6.203125 C 0.203125 -6.21875 0.296875 -6.234375 0.390625 -6.234375 C 0.71875 -6.234375 0.8125 -6.09375 0.8125 -5.625 L 0.8125 -0.8125 C 0.8125 -0.296875 0.78125 -0.265625 0.0625 -0.15625 L 0.0625 0 L 2.40625 0 L 2.40625 -0.15625 L 2.203125 -0.15625 C 1.796875 -0.171875 1.65625 -0.3125 1.65625 -0.671875 L 1.65625 -2.5 L 3.046875 -0.640625 L 3.078125 -0.59375 C 3.09375 -0.5625 3.125 -0.53125 3.15625 -0.515625 C 3.234375 -0.40625 3.265625 -0.34375 3.265625 -0.296875 C 3.265625 -0.203125 3.171875 -0.15625 3.046875 -0.15625 L 2.859375 -0.15625 L 2.859375 0 L 5.03125 0 L 5.03125 -0.15625 C 4.59375 -0.171875 4.28125 -0.375 3.875 -0.875 L 2.34375 -2.8125 L 2.625 -3.078125 C 3.34375 -3.734375 3.953125 -4.203125 4.25 -4.28125 C 4.390625 -4.3125 4.53125 -4.34375 4.703125 -4.34375 L 4.78125 -4.34375 L 4.78125 -4.484375 L 2.75 -4.484375 L 2.75 -4.34375 C 3.140625 -4.34375 3.25 -4.296875 3.25 -4.15625 C 3.25 -4.078125 3.15625 -3.9375 3.015625 -3.8125 L 1.65625 -2.609375 L 1.65625 -6.78125 L 1.609375 -6.8125 C 1.234375 -6.6875 0.953125 -6.609375 0.375 -6.453125 L 0.0625 -6.375 Z M 0.0625 -6.203125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-30"> +<path style="stroke:none;" d="M 1.140625 -1.09375 C 1.140625 -0.34375 1.015625 -0.21875 0.171875 -0.1875 L 0.171875 0 L 3.140625 0 L 3.140625 -0.1875 C 2.328125 -0.21875 2.15625 -0.359375 2.15625 -1.09375 L 2.15625 -5.515625 C 2.15625 -6.234375 2.296875 -6.375 3.140625 -6.40625 L 3.140625 -6.59375 L 0.171875 -6.59375 L 0.171875 -6.40625 C 1.03125 -6.359375 1.140625 -6.25 1.140625 -5.515625 Z M 1.140625 -1.09375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-31"> +<path style="stroke:none;" d="M 5.953125 -1.6875 L 5.671875 -1.6875 C 5.171875 -0.609375 4.75 -0.375 3.3125 -0.375 L 3.046875 -0.375 C 2.5625 -0.375 2.140625 -0.421875 2.078125 -0.484375 C 2.03125 -0.515625 2 -0.625 2 -0.796875 L 2 -3.265625 L 3.53125 -3.265625 C 4.359375 -3.265625 4.5 -3.125 4.640625 -2.296875 L 4.859375 -2.296875 L 4.859375 -4.609375 L 4.640625 -4.609375 C 4.5625 -4.203125 4.53125 -4.0625 4.421875 -3.921875 C 4.28125 -3.75 4 -3.671875 3.53125 -3.671875 L 2 -3.671875 L 2 -5.875 C 2 -6.15625 2.0625 -6.21875 2.328125 -6.21875 L 3.671875 -6.21875 C 4.8125 -6.21875 5.03125 -6.078125 5.1875 -5.171875 L 5.4375 -5.171875 L 5.40625 -6.59375 L 0.125 -6.59375 L 0.125 -6.40625 C 0.859375 -6.34375 0.984375 -6.203125 0.984375 -5.515625 L 0.984375 -1.09375 C 0.984375 -0.390625 0.84375 -0.234375 0.125 -0.1875 L 0.125 0 L 5.5 0 Z M 5.953125 -1.6875 "/> +</symbol> +<symbol overflow="visible" id="glyph4-32"> +<path style="stroke:none;" d="M 2.890625 -3.703125 C 3.875 -4.234375 4.234375 -4.640625 4.234375 -5.328125 C 4.234375 -6.140625 3.515625 -6.734375 2.515625 -6.734375 C 1.421875 -6.734375 0.625 -6.078125 0.625 -5.15625 C 0.625 -4.515625 0.8125 -4.234375 1.859375 -3.3125 C 0.78125 -2.5 0.5625 -2.1875 0.5625 -1.5 C 0.5625 -0.53125 1.34375 0.140625 2.46875 0.140625 C 3.671875 0.140625 4.4375 -0.515625 4.4375 -1.546875 C 4.4375 -2.3125 4.09375 -2.796875 2.890625 -3.703125 Z M 2.71875 -2.671875 C 3.4375 -2.15625 3.671875 -1.796875 3.671875 -1.234375 C 3.671875 -0.59375 3.234375 -0.140625 2.578125 -0.140625 C 1.828125 -0.140625 1.3125 -0.71875 1.3125 -1.578125 C 1.3125 -2.21875 1.53125 -2.640625 2.109375 -3.109375 Z M 2.609375 -3.875 C 1.71875 -4.453125 1.359375 -4.921875 1.359375 -5.46875 C 1.359375 -6.046875 1.796875 -6.453125 2.4375 -6.453125 C 3.109375 -6.453125 3.53125 -6.015625 3.53125 -5.328125 C 3.53125 -4.765625 3.265625 -4.3125 2.6875 -3.9375 C 2.640625 -3.90625 2.640625 -3.90625 2.609375 -3.875 Z M 2.609375 -3.875 "/> +</symbol> +<symbol overflow="visible" id="glyph4-33"> +<path style="stroke:none;" d="M 7.0625 -3.53125 L 4.53125 -3.53125 L 4.53125 -3.34375 C 4.953125 -3.3125 5.078125 -3.296875 5.1875 -3.203125 C 5.34375 -3.125 5.40625 -2.890625 5.40625 -2.46875 L 5.40625 -0.84375 C 5.40625 -0.53125 4.796875 -0.265625 4.078125 -0.265625 C 2.453125 -0.265625 1.453125 -1.390625 1.453125 -3.25 C 1.453125 -4.1875 1.734375 -5.09375 2.171875 -5.578125 C 2.609375 -6.078125 3.234375 -6.34375 3.90625 -6.34375 C 4.46875 -6.34375 4.953125 -6.15625 5.34375 -5.796875 C 5.640625 -5.515625 5.796875 -5.25 6.046875 -4.640625 L 6.28125 -4.640625 L 6.203125 -6.734375 L 5.984375 -6.734375 C 5.921875 -6.546875 5.734375 -6.40625 5.53125 -6.40625 C 5.4375 -6.40625 5.28125 -6.4375 5.09375 -6.515625 C 4.640625 -6.65625 4.1875 -6.734375 3.734375 -6.734375 C 1.75 -6.734375 0.3125 -5.28125 0.3125 -3.234375 C 0.3125 -2.265625 0.59375 -1.515625 1.140625 -0.921875 C 1.8125 -0.234375 2.765625 0.140625 3.875 0.140625 C 4.71875 0.140625 5.96875 -0.203125 6.375 -0.5625 L 6.375 -2.578125 C 6.375 -3.171875 6.484375 -3.296875 7.0625 -3.34375 Z M 7.0625 -3.53125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-34"> +<path style="stroke:none;" d="M 6.1875 -4.484375 L 6.09375 -6.734375 L 5.875 -6.734375 C 5.828125 -6.53125 5.65625 -6.40625 5.46875 -6.40625 C 5.375 -6.40625 5.21875 -6.4375 5.078125 -6.5 C 4.578125 -6.65625 4.09375 -6.734375 3.625 -6.734375 C 2.796875 -6.734375 1.96875 -6.4375 1.359375 -5.875 C 0.65625 -5.265625 0.28125 -4.34375 0.28125 -3.234375 C 0.28125 -2.3125 0.578125 -1.453125 1.09375 -0.890625 C 1.6875 -0.234375 2.609375 0.140625 3.59375 0.140625 C 4.71875 0.140625 5.703125 -0.3125 6.3125 -1.125 L 6.125 -1.3125 C 5.390625 -0.59375 4.734375 -0.296875 3.90625 -0.296875 C 3.28125 -0.296875 2.71875 -0.5 2.296875 -0.875 C 1.75 -1.359375 1.4375 -2.265625 1.4375 -3.375 C 1.4375 -5.171875 2.359375 -6.34375 3.8125 -6.34375 C 4.375 -6.34375 4.890625 -6.125 5.296875 -5.734375 C 5.609375 -5.40625 5.765625 -5.140625 5.953125 -4.484375 Z M 6.1875 -4.484375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-35"> +<path style="stroke:none;" d="M 3.59375 -6.734375 C 1.6875 -6.734375 0.34375 -5.296875 0.34375 -3.296875 C 0.34375 -2.359375 0.65625 -1.453125 1.203125 -0.875 C 1.78125 -0.25 2.65625 0.140625 3.53125 0.140625 C 5.5 0.140625 6.859375 -1.25 6.859375 -3.265625 C 6.859375 -4.25 6.5625 -5.09375 6.015625 -5.6875 C 5.390625 -6.375 4.5625 -6.734375 3.59375 -6.734375 Z M 3.59375 -6.375 C 4.0625 -6.375 4.515625 -6.203125 4.875 -5.875 C 5.40625 -5.390625 5.71875 -4.453125 5.71875 -3.265625 C 5.71875 -2.6875 5.59375 -2 5.390625 -1.46875 C 5.296875 -1.21875 5.140625 -0.984375 4.90625 -0.75 C 4.5625 -0.40625 4.109375 -0.21875 3.578125 -0.21875 C 3.125 -0.21875 2.671875 -0.40625 2.328125 -0.703125 C 1.796875 -1.171875 1.46875 -2.171875 1.46875 -3.28125 C 1.46875 -4.296875 1.75 -5.265625 2.171875 -5.734375 C 2.5625 -6.15625 3.046875 -6.375 3.59375 -6.375 Z M 3.59375 -6.375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-36"> +<path style="stroke:none;" d="M 2.015625 -2.90625 C 2.265625 -2.875 2.4375 -2.875 2.703125 -2.875 C 3.484375 -2.875 4.015625 -2.96875 4.453125 -3.203125 C 5.046875 -3.53125 5.40625 -4.140625 5.40625 -4.796875 C 5.40625 -5.21875 5.265625 -5.59375 5 -5.875 C 4.59375 -6.3125 3.734375 -6.59375 2.796875 -6.59375 L 0.15625 -6.59375 L 0.15625 -6.40625 C 0.890625 -6.328125 1 -6.234375 1 -5.515625 L 1 -1.203125 C 1 -0.359375 0.921875 -0.265625 0.15625 -0.1875 L 0.15625 0 L 2.953125 0 L 2.953125 -0.1875 C 2.15625 -0.21875 2.015625 -0.359375 2.015625 -1.09375 Z M 2.015625 -5.890625 C 2.015625 -6.15625 2.078125 -6.234375 2.359375 -6.234375 C 3.703125 -6.234375 4.3125 -5.765625 4.3125 -4.734375 C 4.3125 -3.765625 3.734375 -3.265625 2.578125 -3.265625 C 2.375 -3.265625 2.25 -3.28125 2.015625 -3.296875 Z M 2.015625 -5.890625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-37"> +<path style="stroke:none;" d="M 4.453125 -6.734375 L 4.25 -6.734375 C 4.203125 -6.515625 4.09375 -6.40625 3.921875 -6.40625 C 3.828125 -6.40625 3.65625 -6.4375 3.484375 -6.515625 C 3.125 -6.65625 2.75 -6.734375 2.4375 -6.734375 C 2 -6.734375 1.5625 -6.5625 1.234375 -6.28125 C 0.890625 -5.96875 0.703125 -5.546875 0.703125 -5.03125 C 0.703125 -4.234375 1.140625 -3.671875 2.265625 -3.09375 C 2.984375 -2.71875 3.5 -2.296875 3.75 -1.921875 C 3.84375 -1.796875 3.890625 -1.578125 3.890625 -1.34375 C 3.890625 -0.671875 3.390625 -0.21875 2.65625 -0.21875 C 1.78125 -0.21875 1.140625 -0.765625 0.640625 -1.984375 L 0.421875 -1.984375 L 0.71875 0.125 L 0.9375 0.125 C 0.953125 -0.0625 1.0625 -0.203125 1.21875 -0.203125 C 1.328125 -0.203125 1.5 -0.15625 1.6875 -0.09375 C 2.0625 0.0625 2.46875 0.140625 2.859375 0.140625 C 4 0.140625 4.890625 -0.640625 4.890625 -1.671875 C 4.890625 -2.5 4.34375 -3.140625 3.03125 -3.84375 C 1.984375 -4.421875 1.5625 -4.859375 1.5625 -5.40625 C 1.5625 -5.953125 1.984375 -6.328125 2.609375 -6.328125 C 3.046875 -6.328125 3.46875 -6.140625 3.8125 -5.78125 C 4.125 -5.46875 4.265625 -5.203125 4.421875 -4.609375 L 4.671875 -4.609375 Z M 4.453125 -6.734375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-38"> +<path style="stroke:none;" d="M 4.171875 -1.34375 L 3.984375 -1.390625 C 3.890625 -0.890625 3.828125 -0.734375 3.703125 -0.5625 C 3.5625 -0.390625 3.21875 -0.296875 2.71875 -0.296875 L 1.328125 -0.296875 L 4.015625 -4.34375 L 4.015625 -4.484375 L 0.5625 -4.484375 L 0.53125 -3.3125 L 0.703125 -3.3125 C 0.796875 -4.03125 0.953125 -4.1875 1.546875 -4.1875 L 2.921875 -4.1875 L 0.265625 -0.15625 L 0.265625 0 L 4.03125 0 Z M 4.171875 -1.34375 "/> +</symbol> +<symbol overflow="visible" id="glyph4-39"> +<path style="stroke:none;" d="M 1.0625 -4.3125 C 1.71875 -4.640625 2.171875 -5.25 2.171875 -5.859375 C 2.171875 -6.34375 1.828125 -6.734375 1.375 -6.734375 C 1.03125 -6.734375 0.78125 -6.515625 0.78125 -6.171875 C 0.78125 -5.859375 1.015625 -5.65625 1.375 -5.65625 C 1.4375 -5.65625 1.5 -5.671875 1.5625 -5.6875 C 1.640625 -5.703125 1.640625 -5.703125 1.640625 -5.703125 C 1.71875 -5.703125 1.78125 -5.640625 1.78125 -5.5625 C 1.78125 -5.234375 1.5 -4.875 0.96875 -4.5 Z M 1.0625 -4.3125 "/> +</symbol> +<symbol overflow="visible" id="glyph4-40"> +<path style="stroke:none;" d="M 0.171875 -6.59375 L 0.171875 -6.40625 C 1 -6.359375 1.125 -6.25 1.125 -5.515625 L 1.125 -1.09375 C 1.125 -0.34375 1 -0.21875 0.171875 -0.1875 L 0.171875 0 L 3.5 0 C 4.28125 0 4.984375 -0.203125 5.359375 -0.546875 C 5.71875 -0.875 5.90625 -1.3125 5.90625 -1.796875 C 5.90625 -2.234375 5.734375 -2.625 5.421875 -2.921875 C 5.125 -3.1875 4.859375 -3.3125 4.203125 -3.46875 C 4.71875 -3.59375 4.9375 -3.703125 5.171875 -3.90625 C 5.421875 -4.125 5.578125 -4.5 5.578125 -4.90625 C 5.578125 -6.015625 4.6875 -6.59375 2.953125 -6.59375 Z M 2.140625 -3.25 C 3.109375 -3.25 3.5625 -3.203125 3.921875 -3.046875 C 4.5 -2.8125 4.765625 -2.40625 4.765625 -1.78125 C 4.765625 -1.25 4.5625 -0.859375 4.15625 -0.625 C 3.84375 -0.453125 3.421875 -0.375 2.765625 -0.375 C 2.28125 -0.375 2.140625 -0.453125 2.140625 -0.78125 Z M 2.140625 -3.65625 L 2.140625 -5.9375 C 2.140625 -6.140625 2.21875 -6.234375 2.359375 -6.234375 L 2.796875 -6.234375 C 3.953125 -6.234375 4.5625 -5.75 4.5625 -4.859375 C 4.5625 -4.09375 4.03125 -3.65625 3.09375 -3.65625 Z M 2.140625 -3.65625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-41"> +<path style="stroke:none;" d="M 4.109375 -6.40625 C 4.234375 -6.40625 4.34375 -6.40625 4.375 -6.40625 C 4.671875 -6.375 4.796875 -6.296875 4.796875 -6.09375 C 4.796875 -5.875 4.5625 -5.578125 4.015625 -5.078125 L 2.25 -3.46875 L 2.25 -5.515625 C 2.25 -6.234375 2.359375 -6.34375 3.171875 -6.40625 L 3.171875 -6.59375 L 0.34375 -6.59375 L 0.34375 -6.40625 C 1.109375 -6.34375 1.234375 -6.21875 1.234375 -5.515625 L 1.234375 -1.203125 C 1.234375 -0.375 1.125 -0.234375 0.34375 -0.1875 L 0.34375 0 L 3.15625 0 L 3.15625 -0.1875 C 2.375 -0.234375 2.25 -0.359375 2.25 -1.09375 L 2.25 -2.953125 L 2.515625 -3.15625 L 3.5625 -2.109375 C 4.328125 -1.359375 4.859375 -0.65625 4.859375 -0.421875 C 4.859375 -0.28125 4.71875 -0.203125 4.453125 -0.203125 C 4.390625 -0.203125 4.28125 -0.203125 4.171875 -0.1875 L 4.171875 0 L 7.203125 0 L 7.203125 -0.1875 C 6.6875 -0.203125 6.546875 -0.296875 5.640625 -1.265625 L 3.3125 -3.765625 L 5.21875 -5.625 C 5.890625 -6.28125 6.046875 -6.359375 6.734375 -6.40625 L 6.734375 -6.59375 L 4.109375 -6.59375 Z M 4.109375 -6.40625 "/> +</symbol> +<symbol overflow="visible" id="glyph4-42"> +<path style="stroke:none;" d="M 2.53125 -6.1875 L 2.53125 -1.203125 C 2.53125 -0.34375 2.421875 -0.234375 1.59375 -0.1875 L 1.59375 0 L 4.5 0 L 4.5 -0.1875 C 3.6875 -0.234375 3.546875 -0.359375 3.546875 -1.09375 L 3.546875 -6.1875 L 4.09375 -6.1875 C 5.21875 -6.1875 5.4375 -6 5.671875 -4.90625 L 5.90625 -4.90625 L 5.859375 -6.59375 L 0.234375 -6.59375 L 0.171875 -4.90625 L 0.40625 -4.90625 C 0.640625 -5.984375 0.875 -6.1875 2 -6.1875 Z M 2.53125 -6.1875 "/> +</symbol> +<symbol overflow="visible" id="glyph4-43"> +<path style="stroke:none;" d="M 0.3125 -4.15625 L 0.984375 -4.15625 L 0.984375 -0.9375 C 0.984375 -0.296875 0.875 -0.171875 0.3125 -0.15625 L 0.3125 0 L 2.515625 0 L 2.515625 -0.15625 C 1.953125 -0.1875 1.828125 -0.328125 1.828125 -0.875 L 1.828125 -4.15625 L 2.6875 -4.15625 C 3.640625 -4.15625 3.6875 -4.125 3.6875 -3.59375 L 3.6875 -0.96875 C 3.6875 -0.296875 3.625 -0.203125 3 -0.15625 L 3 0 L 5.1875 0 L 5.1875 -0.15625 C 4.640625 -0.203125 4.53125 -0.328125 4.53125 -0.96875 L 4.53125 -3.578125 C 4.53125 -3.796875 4.53125 -4.03125 4.546875 -4.5625 L 4.5 -4.578125 C 4.0625 -4.515625 3.78125 -4.484375 3.375 -4.484375 L 1.8125 -4.484375 L 1.8125 -4.921875 C 1.8125 -5.359375 1.84375 -5.65625 1.890625 -5.828125 C 2.03125 -6.265625 2.421875 -6.5625 2.875 -6.5625 C 3.15625 -6.5625 3.34375 -6.4375 3.578125 -6.09375 C 3.765625 -5.828125 3.890625 -5.71875 4.0625 -5.71875 C 4.28125 -5.71875 4.421875 -5.890625 4.421875 -6.109375 C 4.421875 -6.53125 3.921875 -6.8125 3.15625 -6.8125 C 2.40625 -6.8125 1.828125 -6.546875 1.46875 -6.0625 C 1.171875 -5.65625 1.0625 -5.28125 1 -4.484375 L 0.3125 -4.484375 Z M 0.3125 -4.15625 "/> +</symbol> +<symbol overflow="visible" id="glyph5-0"> +<path style="stroke:none;" d="M 0.5625 -4.03125 L 0.78125 -4.03125 C 0.78125 -4.03125 0.8125 -4.03125 0.8125 -4.03125 C 0.953125 -4.09375 1.203125 -3.90625 1.203125 -3.765625 C 1.203125 -3.671875 0.84375 -2.25 0.5 -0.9375 C 0.234375 0.0625 -0.015625 1 -0.078125 1.296875 C -0.1875 1.75 -0.3125 1.875 -0.75 1.890625 L -0.75 2.046875 L 1.296875 2.046875 L 1.296875 1.890625 C 0.828125 1.890625 0.65625 1.796875 0.65625 1.59375 C 0.65625 1.453125 0.828125 0.671875 1.03125 -0.0625 C 1.28125 0.0625 1.453125 0.109375 1.6875 0.109375 C 3.140625 0.109375 4.671875 -1.5625 4.671875 -3.15625 C 4.671875 -3.921875 4.234375 -4.390625 3.53125 -4.390625 C 2.875 -4.390625 2.40625 -4.078125 1.828125 -3.265625 L 2.109375 -4.265625 L 2.140625 -4.359375 C 2.140625 -4.359375 2.140625 -4.359375 2.125 -4.390625 L 2.109375 -4.390625 C 2.109375 -4.40625 2.09375 -4.40625 2.09375 -4.40625 L 2.078125 -4.390625 L 0.53125 -4.171875 Z M 3.15625 -3.96875 C 3.59375 -3.953125 3.78125 -3.6875 3.78125 -3.109375 C 3.78125 -2.453125 3.484375 -1.59375 3.0625 -0.984375 C 2.640625 -0.390625 2.15625 -0.078125 1.640625 -0.078125 C 1.359375 -0.078125 1.171875 -0.234375 1.171875 -0.453125 C 1.171875 -0.78125 1.53125 -2.09375 1.8125 -2.828125 C 2.078125 -3.515625 2.65625 -4 3.15625 -3.96875 Z M 3.15625 -3.96875 "/> +</symbol> +<symbol overflow="visible" id="glyph5-1"> +<path style="stroke:none;" d="M 1.203125 0 C 1.71875 -1.6875 1.890625 -2.125 2.359375 -2.875 C 2.6875 -3.4375 2.953125 -3.75 3.125 -3.75 C 3.1875 -3.75 3.234375 -3.703125 3.296875 -3.609375 C 3.375 -3.4375 3.453125 -3.390625 3.65625 -3.390625 C 3.9375 -3.390625 4.109375 -3.5625 4.109375 -3.875 C 4.109375 -4.1875 3.921875 -4.390625 3.640625 -4.390625 C 3.40625 -4.390625 3.140625 -4.25 2.875 -3.984375 C 2.46875 -3.5625 2.078125 -2.96875 1.90625 -2.578125 L 1.78125 -2.21875 L 2.296875 -4.375 L 2.265625 -4.390625 C 1.546875 -4.265625 1.453125 -4.25 0.734375 -4.125 L 0.734375 -3.953125 C 0.96875 -4 1 -4 1.0625 -4 C 1.28125 -4 1.421875 -3.90625 1.421875 -3.734375 C 1.421875 -3.59375 1.421875 -3.59375 1.25 -2.90625 L 0.453125 0 Z M 1.203125 0 "/> +</symbol> +<symbol overflow="visible" id="glyph5-2"> +<path style="stroke:none;" d="M 2.21875 -1.140625 C 1.96875 -0.8125 1.90625 -0.734375 1.796875 -0.625 C 1.640625 -0.453125 1.484375 -0.359375 1.390625 -0.359375 C 1.3125 -0.359375 1.234375 -0.4375 1.234375 -0.515625 C 1.234375 -0.609375 1.265625 -0.75 1.328125 -0.953125 C 1.328125 -0.984375 1.359375 -1.046875 1.390625 -1.125 L 1.390625 -1.171875 L 2.265625 -4.375 L 2.25 -4.390625 C 1.234375 -4.203125 1.03125 -4.171875 0.640625 -4.140625 L 0.640625 -3.984375 C 1.171875 -3.984375 1.28125 -3.953125 1.28125 -3.75 C 1.28125 -3.671875 1.25 -3.515625 1.1875 -3.3125 L 0.703125 -1.546875 C 0.546875 -0.96875 0.484375 -0.65625 0.484375 -0.453125 C 0.484375 -0.09375 0.640625 0.109375 0.953125 0.109375 C 1.40625 0.109375 1.78125 -0.1875 2.34375 -1.03125 Z M 2.140625 -6.515625 C 1.859375 -6.515625 1.671875 -6.296875 1.671875 -5.984375 C 1.671875 -5.671875 1.859375 -5.46875 2.140625 -5.46875 C 2.40625 -5.46875 2.625 -5.6875 2.625 -5.96875 C 2.625 -6.265625 2.40625 -6.515625 2.140625 -6.515625 Z M 2.140625 -6.515625 "/> +</symbol> +<symbol overflow="visible" id="glyph5-3"> +<path style="stroke:none;" d="M 4.578125 -1.171875 L 4.375 -0.90625 C 4.09375 -0.53125 3.90625 -0.375 3.765625 -0.375 C 3.6875 -0.375 3.609375 -0.453125 3.609375 -0.53125 C 3.609375 -0.609375 3.609375 -0.609375 3.75 -1.171875 L 4.3125 -3.21875 C 4.359375 -3.421875 4.40625 -3.65625 4.40625 -3.78125 C 4.40625 -4.140625 4.140625 -4.390625 3.75 -4.390625 C 3.109375 -4.390625 2.484375 -3.796875 1.453125 -2.203125 L 2.125 -4.375 L 2.09375 -4.390625 C 1.5625 -4.28125 1.34375 -4.25 0.484375 -4.09375 L 0.484375 -3.921875 C 0.984375 -3.921875 1.109375 -3.859375 1.109375 -3.65625 C 1.109375 -3.59375 1.109375 -3.53125 1.09375 -3.484375 L 0.140625 0 L 0.890625 0 C 1.359375 -1.578125 1.453125 -1.796875 1.890625 -2.46875 C 2.484375 -3.390625 2.984375 -3.890625 3.359375 -3.890625 C 3.515625 -3.890625 3.59375 -3.78125 3.59375 -3.59375 C 3.59375 -3.484375 3.53125 -3.15625 3.453125 -2.84375 L 3.015625 -1.203125 C 2.890625 -0.6875 2.859375 -0.546875 2.859375 -0.453125 C 2.859375 -0.0625 3 0.09375 3.328125 0.09375 C 3.78125 0.09375 4.03125 -0.125 4.71875 -1.03125 Z M 4.578125 -1.171875 "/> +</symbol> +<symbol overflow="visible" id="glyph5-4"> +<path style="stroke:none;" d="M 3.484375 -1.0625 C 2.9375 -0.46875 2.546875 -0.25 2.0625 -0.25 C 1.5 -0.25 1.15625 -0.671875 1.15625 -1.390625 C 1.15625 -2.234375 1.5 -3.125 2.0625 -3.703125 C 2.359375 -4 2.75 -4.1875 3.140625 -4.1875 C 3.375 -4.1875 3.515625 -4.109375 3.515625 -3.984375 C 3.515625 -3.9375 3.5 -3.890625 3.453125 -3.796875 C 3.390625 -3.671875 3.375 -3.59375 3.375 -3.515625 C 3.375 -3.265625 3.515625 -3.125 3.765625 -3.125 C 4.03125 -3.125 4.234375 -3.328125 4.234375 -3.59375 C 4.234375 -4.046875 3.78125 -4.390625 3.1875 -4.390625 C 1.6875 -4.390625 0.296875 -2.9375 0.296875 -1.390625 C 0.296875 -0.4375 0.84375 0.109375 1.765625 0.109375 C 2.5 0.109375 3.046875 -0.203125 3.65625 -0.953125 Z M 3.484375 -1.0625 "/> +</symbol> +<symbol overflow="visible" id="glyph5-5"> +<path style="stroke:none;" d="M 4.625 -1.09375 C 4.46875 -0.953125 4.421875 -0.890625 4.34375 -0.8125 C 4.046875 -0.515625 3.921875 -0.40625 3.828125 -0.40625 C 3.75 -0.40625 3.6875 -0.46875 3.6875 -0.53125 C 3.6875 -0.734375 4.109375 -2.4375 4.578125 -4.15625 C 4.609375 -4.25 4.609375 -4.28125 4.640625 -4.359375 L 4.5625 -4.390625 L 3.953125 -4.328125 L 3.921875 -4.296875 L 3.8125 -3.8125 C 3.734375 -4.1875 3.453125 -4.390625 3.015625 -4.390625 C 1.703125 -4.390625 0.171875 -2.578125 0.171875 -1 C 0.171875 -0.296875 0.546875 0.109375 1.1875 0.109375 C 1.890625 0.109375 2.3125 -0.21875 3.1875 -1.453125 C 2.984375 -0.671875 2.953125 -0.546875 2.953125 -0.3125 C 2.953125 -0.015625 3.078125 0.09375 3.359375 0.09375 C 3.765625 0.09375 4 -0.09375 4.75 -1 Z M 3.078125 -4.171875 C 3.421875 -4.15625 3.640625 -3.921875 3.640625 -3.5625 C 3.640625 -2.734375 3.140625 -1.5625 2.453125 -0.8125 C 2.21875 -0.546875 1.875 -0.375 1.578125 -0.375 C 1.234375 -0.375 1 -0.671875 1 -1.125 C 1 -1.671875 1.390625 -2.6875 1.8125 -3.3125 C 2.21875 -3.890625 2.6875 -4.203125 3.078125 -4.171875 Z M 3.078125 -4.171875 "/> +</symbol> +<symbol overflow="visible" id="glyph5-6"> +<path style="stroke:none;" d="M 2.265625 -1.21875 C 2.1875 -1.109375 2.09375 -1 2 -0.890625 C 1.6875 -0.46875 1.5 -0.3125 1.3125 -0.3125 C 1.21875 -0.3125 1.171875 -0.390625 1.171875 -0.5 C 1.171875 -0.5625 1.203125 -0.6875 1.25 -0.890625 C 1.25 -0.921875 1.265625 -0.96875 1.28125 -0.984375 L 2.78125 -6.765625 L 2.734375 -6.8125 C 2.140625 -6.671875 1.765625 -6.609375 1.171875 -6.546875 L 1.171875 -6.375 C 1.65625 -6.375 1.859375 -6.3125 1.859375 -6.15625 C 1.859375 -6.125 1.84375 -6.0625 1.8125 -5.96875 L 0.453125 -0.703125 C 0.421875 -0.59375 0.40625 -0.5 0.40625 -0.453125 C 0.40625 -0.09375 0.5625 0.109375 0.890625 0.109375 C 1.40625 0.109375 1.75 -0.171875 2.40625 -1.140625 Z M 2.265625 -1.21875 "/> +</symbol> +<symbol overflow="visible" id="glyph5-7"> +<path style="stroke:none;" d="M 0.359375 -1.453125 L 0.15625 0.125 L 0.3125 0.125 C 0.40625 -0.03125 0.453125 -0.078125 0.5625 -0.078125 C 0.671875 -0.078125 0.84375 -0.046875 1.046875 0.015625 C 1.265625 0.078125 1.4375 0.109375 1.59375 0.109375 C 2.4375 0.109375 3.03125 -0.421875 3.03125 -1.1875 C 3.03125 -1.5625 2.828125 -2 2.359375 -2.578125 C 1.96875 -3.046875 1.8125 -3.359375 1.8125 -3.625 C 1.8125 -3.953125 2.03125 -4.171875 2.375 -4.171875 C 2.890625 -4.171875 3.1875 -3.796875 3.296875 -3.015625 L 3.453125 -3.015625 L 3.65625 -4.40625 L 3.515625 -4.40625 C 3.421875 -4.265625 3.34375 -4.234375 3.203125 -4.234375 C 3.140625 -4.234375 3.046875 -4.25 2.84375 -4.296875 C 2.609375 -4.375 2.453125 -4.390625 2.296875 -4.390625 C 1.5625 -4.390625 1.09375 -3.96875 1.09375 -3.296875 C 1.09375 -2.984375 1.296875 -2.546875 1.71875 -2.015625 C 2.109375 -1.5 2.265625 -1.15625 2.265625 -0.875 C 2.265625 -0.421875 1.96875 -0.09375 1.53125 -0.09375 C 0.984375 -0.09375 0.671875 -0.515625 0.515625 -1.453125 Z M 0.359375 -1.453125 "/> +</symbol> +<symbol overflow="visible" id="glyph5-8"> +<path style="stroke:none;" d="M 3.5625 -1.09375 C 2.84375 -0.5 2.546875 -0.34375 2.109375 -0.34375 C 1.546875 -0.34375 1.171875 -0.703125 1.171875 -1.234375 C 1.171875 -1.390625 1.203125 -1.53125 1.28125 -1.859375 L 1.5625 -1.890625 C 3.046875 -2.109375 4.109375 -2.859375 4.109375 -3.71875 C 4.109375 -4.140625 3.8125 -4.390625 3.3125 -4.390625 C 1.875 -4.390625 0.3125 -2.75 0.3125 -1.25 C 0.3125 -0.453125 0.84375 0.109375 1.625 0.109375 C 2.328125 0.109375 3.09375 -0.296875 3.6875 -0.96875 Z M 1.515625 -2.515625 C 1.859375 -3.40625 2.59375 -4.171875 3.09375 -4.171875 C 3.3125 -4.171875 3.453125 -4 3.453125 -3.78125 C 3.453125 -3.46875 3.265625 -3.109375 2.953125 -2.8125 C 2.578125 -2.46875 2.203125 -2.28125 1.34375 -2.078125 Z M 1.515625 -2.515625 "/> +</symbol> +<symbol overflow="visible" id="glyph5-9"> +<path style="stroke:none;" d="M 4 -1.109375 C 3.921875 -1.015625 3.875 -0.953125 3.78125 -0.84375 C 3.5625 -0.53125 3.4375 -0.4375 3.328125 -0.4375 C 3.1875 -0.4375 3.09375 -0.5625 3.015625 -0.84375 C 3 -0.921875 2.984375 -0.984375 2.984375 -1.015625 C 2.734375 -2.03125 2.625 -2.484375 2.625 -2.625 C 3.0625 -3.40625 3.421875 -3.84375 3.59375 -3.84375 C 3.65625 -3.84375 3.734375 -3.8125 3.84375 -3.765625 C 3.953125 -3.6875 4.03125 -3.671875 4.109375 -3.671875 C 4.3125 -3.671875 4.453125 -3.8125 4.453125 -4.03125 C 4.453125 -4.234375 4.28125 -4.390625 4.046875 -4.390625 C 3.609375 -4.390625 3.234375 -4.03125 2.546875 -2.96875 L 2.4375 -3.515625 C 2.296875 -4.203125 2.1875 -4.390625 1.90625 -4.390625 C 1.6875 -4.390625 1.359375 -4.3125 0.75 -4.109375 L 0.640625 -4.0625 L 0.671875 -3.921875 C 1.0625 -4 1.140625 -4.03125 1.234375 -4.03125 C 1.484375 -4.03125 1.546875 -3.9375 1.6875 -3.34375 L 1.96875 -2.109375 L 1.15625 -0.953125 C 0.953125 -0.640625 0.75 -0.46875 0.640625 -0.46875 C 0.59375 -0.46875 0.484375 -0.5 0.390625 -0.5625 C 0.265625 -0.625 0.15625 -0.65625 0.0625 -0.65625 C -0.125 -0.65625 -0.265625 -0.515625 -0.265625 -0.3125 C -0.265625 -0.046875 -0.0625 0.109375 0.234375 0.109375 C 0.53125 0.109375 0.65625 0.015625 1.15625 -0.59375 C 1.421875 -0.90625 1.640625 -1.171875 2.046875 -1.75 L 2.359375 -0.5625 C 2.484375 -0.046875 2.609375 0.109375 2.9375 0.109375 C 3.3125 0.109375 3.5625 -0.125 4.140625 -1.03125 Z M 4 -1.109375 "/> +</symbol> +<symbol overflow="visible" id="glyph5-10"> +<path style="stroke:none;" d="M 4.59375 -1.171875 C 4.078125 -0.515625 3.921875 -0.375 3.765625 -0.375 C 3.703125 -0.375 3.65625 -0.4375 3.65625 -0.546875 C 3.65625 -0.609375 3.65625 -0.609375 3.84375 -1.328125 L 4.640625 -4.3125 L 3.890625 -4.3125 C 3.328125 -2.765625 3.265625 -2.609375 2.796875 -1.875 C 2.203125 -0.921875 1.71875 -0.421875 1.390625 -0.421875 C 1.28125 -0.421875 1.1875 -0.515625 1.1875 -0.671875 C 1.1875 -0.703125 1.1875 -0.734375 1.203125 -0.75 L 2.109375 -4.375 L 2.078125 -4.390625 C 1.5 -4.265625 1.140625 -4.203125 0.578125 -4.125 L 0.578125 -3.984375 C 0.953125 -3.984375 0.984375 -3.984375 1.0625 -3.921875 C 1.125 -3.90625 1.171875 -3.8125 1.171875 -3.75 C 1.171875 -3.671875 1.125 -3.421875 1.03125 -3.078125 L 0.671875 -1.671875 C 0.484375 -0.953125 0.421875 -0.625 0.421875 -0.4375 C 0.421875 -0.078125 0.59375 0.109375 0.953125 0.109375 C 1.65625 0.109375 2.1875 -0.421875 3.328125 -2.359375 C 3.046875 -1.28125 2.875 -0.59375 2.875 -0.390625 C 2.875 -0.09375 3.0625 0.09375 3.359375 0.09375 C 3.828125 0.09375 4.0625 -0.109375 4.734375 -1.078125 Z M 4.59375 -1.171875 "/> +</symbol> +<symbol overflow="visible" id="glyph5-11"> +<path style="stroke:none;" d="M 0.203125 -4.015625 C 0.3125 -4.03125 0.40625 -4.03125 0.515625 -4.03125 C 0.890625 -4.03125 1 -3.859375 1.140625 -2.9375 C 1.234375 -2.25 1.359375 -0.75 1.359375 -0.171875 C 1.359375 0.109375 1.375 0.171875 1.453125 0.171875 C 1.6875 0.171875 2.625 -0.875 3.640625 -2.296875 C 3.984375 -2.796875 4.25 -3.4375 4.25 -3.8125 C 4.25 -4.125 3.984375 -4.390625 3.6875 -4.390625 C 3.46875 -4.390625 3.3125 -4.265625 3.3125 -4.0625 C 3.3125 -3.890625 3.375 -3.78125 3.5625 -3.625 C 3.6875 -3.5 3.734375 -3.421875 3.734375 -3.3125 C 3.734375 -2.859375 3.078125 -1.75 2.375 -1.015625 L 2.0625 -0.703125 C 2 -2.109375 1.9375 -2.625 1.796875 -3.359375 C 1.609375 -4.375 1.609375 -4.390625 1.515625 -4.390625 C 1.46875 -4.390625 1.390625 -4.390625 1.3125 -4.359375 C 1.140625 -4.3125 0.59375 -4.203125 0.203125 -4.140625 Z M 0.203125 -4.015625 "/> +</symbol> +<symbol overflow="visible" id="glyph6-0"> +<path style="stroke:none;" d="M 4.546875 -9.875 L 0.9375 -8.3125 L 0.9375 -7.9375 C 1.0625 -7.96875 1.171875 -8.015625 1.21875 -8.046875 C 1.609375 -8.1875 1.96875 -8.296875 2.15625 -8.296875 C 2.515625 -8.296875 2.671875 -7.96875 2.671875 -7.296875 L 2.671875 -1.78125 C 2.671875 -0.609375 2.375 -0.359375 0.96875 -0.34375 L 0.96875 0 L 6.34375 0 L 6.34375 -0.34375 C 5.03125 -0.375 4.796875 -0.59375 4.796875 -1.6875 L 4.796875 -9.875 Z M 4.546875 -9.875 "/> +</symbol> +<symbol overflow="visible" id="glyph6-1"> +<path style="stroke:none;" d="M 1.625 -1.375 C 1.625 -0.65625 1.359375 -0.453125 0.28125 -0.359375 L 0.28125 0 L 5.3125 0 L 5.3125 -0.359375 C 4.21875 -0.4375 3.9375 -0.625 3.9375 -1.375 L 3.9375 -8.3125 C 3.9375 -9.0625 4.25 -9.296875 5.3125 -9.34375 L 5.3125 -9.703125 L 0.28125 -9.703125 L 0.28125 -9.34375 C 1.3125 -9.265625 1.625 -9.046875 1.625 -8.3125 Z M 1.625 -1.375 "/> +</symbol> +<symbol overflow="visible" id="glyph6-2"> +<path style="stroke:none;" d="M 3.046875 -6.609375 L 0.296875 -6.609375 L 0.296875 -6.265625 C 0.9375 -6.171875 1.0625 -6.03125 1.0625 -5.421875 L 1.0625 -1.203125 C 1.0625 -0.609375 0.953125 -0.46875 0.296875 -0.34375 L 0.296875 0 L 3.765625 0 L 3.765625 -0.34375 C 3.234375 -0.421875 3.0625 -0.625 3.0625 -1.15625 L 3.0625 -4.984375 C 3.0625 -5.046875 3.15625 -5.1875 3.296875 -5.328125 C 3.609375 -5.65625 3.953125 -5.828125 4.28125 -5.828125 C 4.796875 -5.828125 5.03125 -5.4375 5.03125 -4.640625 L 5.03125 -1.15625 C 5.03125 -0.625 4.84375 -0.40625 4.359375 -0.34375 L 4.359375 0 L 7.734375 0 L 7.734375 -0.34375 C 7.171875 -0.390625 7.03125 -0.5625 7.03125 -1.15625 L 7.03125 -4.765625 C 7.03125 -6 6.265625 -6.78125 5.09375 -6.78125 C 4.234375 -6.78125 3.578125 -6.390625 3.046875 -5.53125 Z M 3.046875 -6.609375 "/> +</symbol> +<symbol overflow="visible" id="glyph6-3"> +<path style="stroke:none;" d="M 4.375 -6.609375 L 3.03125 -6.609375 L 3.03125 -9.03125 L 2.671875 -9.03125 C 1.796875 -7.796875 1.21875 -7.15625 0.28125 -6.375 L 0.28125 -5.984375 L 1.03125 -5.984375 L 1.03125 -1.328125 C 1.03125 -0.40625 1.65625 0.171875 2.640625 0.171875 C 3.59375 0.171875 4.171875 -0.265625 4.765625 -1.4375 L 4.40625 -1.59375 C 4.109375 -1.046875 3.890625 -0.84375 3.578125 -0.84375 C 3.1875 -0.84375 3.03125 -1.09375 3.03125 -1.65625 L 3.03125 -5.984375 L 4.375 -5.984375 Z M 4.375 -6.609375 "/> +</symbol> +<symbol overflow="visible" id="glyph6-4"> +<path style="stroke:none;" d="M 3.125 -6.609375 L 0.421875 -6.609375 L 0.421875 -6.265625 C 1.03125 -6.1875 1.1875 -6 1.1875 -5.421875 L 1.1875 -1.203125 C 1.1875 -0.609375 1.046875 -0.453125 0.421875 -0.34375 L 0.421875 0 L 4.234375 0 L 4.234375 -0.34375 C 3.359375 -0.40625 3.1875 -0.59375 3.1875 -1.484375 L 3.1875 -4.1875 C 3.1875 -4.9375 3.578125 -5.546875 4.0625 -5.546875 C 4.171875 -5.546875 4.296875 -5.453125 4.453125 -5.21875 C 4.734375 -4.828125 4.953125 -4.703125 5.328125 -4.703125 C 5.859375 -4.703125 6.21875 -5.109375 6.21875 -5.671875 C 6.21875 -6.3125 5.734375 -6.78125 5.0625 -6.78125 C 4.34375 -6.78125 3.796875 -6.390625 3.125 -5.4375 Z M 3.125 -6.609375 "/> +</symbol> +<symbol overflow="visible" id="glyph6-5"> +<path style="stroke:none;" d="M 3.59375 -6.78125 C 1.78125 -6.78125 0.359375 -5.28125 0.359375 -3.328125 C 0.359375 -1.28125 1.71875 0.203125 3.59375 0.203125 C 5.453125 0.203125 6.828125 -1.296875 6.828125 -3.28125 C 6.828125 -5.296875 5.453125 -6.78125 3.59375 -6.78125 Z M 3.609375 -6.34375 C 4.453125 -6.34375 4.71875 -5.546875 4.71875 -3.203125 C 4.71875 -0.984375 4.4375 -0.25 3.59375 -0.25 C 2.75 -0.25 2.46875 -0.96875 2.46875 -3.0625 C 2.46875 -5.59375 2.71875 -6.34375 3.609375 -6.34375 Z M 3.609375 -6.34375 "/> +</symbol> +<symbol overflow="visible" id="glyph6-6"> +<path style="stroke:none;" d="M 4.859375 0.1875 C 5.515625 0 5.875 -0.078125 6.765625 -0.171875 L 7.65625 -0.28125 L 7.65625 -0.609375 C 7 -0.65625 6.8125 -0.84375 6.8125 -1.453125 L 6.8125 -9.703125 L 3.734375 -9.703125 L 3.734375 -9.359375 C 4.6875 -9.28125 4.8125 -9.1875 4.8125 -8.515625 L 4.8125 -5.890625 C 4.203125 -6.5625 3.765625 -6.78125 3.109375 -6.78125 C 1.53125 -6.78125 0.359375 -5.25 0.359375 -3.171875 C 0.359375 -1.21875 1.453125 0.203125 2.953125 0.203125 C 3.71875 0.203125 4.1875 -0.03125 4.859375 -0.75 Z M 4.8125 -1.609375 C 4.8125 -1.515625 4.671875 -1.28125 4.5 -1.09375 C 4.21875 -0.765625 3.921875 -0.609375 3.59375 -0.609375 C 2.84375 -0.609375 2.484375 -1.46875 2.484375 -3.28125 C 2.484375 -5.140625 2.875 -5.984375 3.703125 -5.984375 C 4.171875 -5.984375 4.625 -5.640625 4.8125 -5.09375 Z M 4.8125 -1.609375 "/> +</symbol> +<symbol overflow="visible" id="glyph6-7"> +<path style="stroke:none;" d="M 4.921875 0.1875 C 5.53125 -0.03125 5.875 -0.09375 6.8125 -0.1875 L 7.703125 -0.28125 L 7.703125 -0.609375 C 7.078125 -0.640625 6.90625 -0.828125 6.90625 -1.453125 L 6.90625 -6.609375 L 4.015625 -6.609375 L 4.015625 -6.265625 C 4.734375 -6.203125 4.90625 -6.03125 4.90625 -5.421875 L 4.90625 -1.359375 C 4.4375 -0.890625 4.125 -0.734375 3.734375 -0.734375 C 3.140625 -0.734375 2.921875 -1.015625 2.921875 -1.75 L 2.921875 -6.609375 L 0.234375 -6.609375 L 0.234375 -6.265625 C 0.8125 -6.15625 0.9375 -6.03125 0.9375 -5.421875 L 0.9375 -1.8125 C 0.9375 -0.546875 1.65625 0.203125 2.84375 0.203125 C 3.578125 0.203125 4.09375 -0.03125 4.921875 -0.75 Z M 4.921875 0.1875 "/> +</symbol> +<symbol overflow="visible" id="glyph6-8"> +<path style="stroke:none;" d="M 5.90625 -1.5625 C 5.375 -0.96875 5 -0.765625 4.421875 -0.765625 C 3.171875 -0.765625 2.375 -2.015625 2.375 -3.953125 C 2.375 -5.4375 2.84375 -6.34375 3.578125 -6.34375 C 3.8125 -6.34375 4.03125 -6.21875 4.109375 -6.0625 C 4.1875 -5.9375 4.1875 -5.9375 4.1875 -5.328125 C 4.203125 -4.640625 4.453125 -4.296875 4.984375 -4.296875 C 5.59375 -4.296875 5.96875 -4.640625 5.96875 -5.203125 C 5.96875 -6.09375 5 -6.78125 3.75 -6.78125 C 1.796875 -6.78125 0.359375 -5.265625 0.359375 -3.203125 C 0.359375 -1.21875 1.65625 0.203125 3.421875 0.203125 C 4.53125 0.203125 5.328125 -0.25 6.171875 -1.3125 Z M 5.90625 -1.5625 "/> +</symbol> +<symbol overflow="visible" id="glyph6-9"> +<path style="stroke:none;" d="M 2.984375 -6.609375 L 0.234375 -6.609375 L 0.234375 -6.265625 C 0.859375 -6.140625 0.984375 -6.015625 0.984375 -5.421875 L 0.984375 -1.203125 C 0.984375 -0.609375 0.890625 -0.5 0.234375 -0.34375 L 0.234375 0 L 3.65625 0 L 3.65625 -0.34375 C 3.15625 -0.421875 2.984375 -0.609375 2.984375 -1.15625 Z M 1.984375 -9.90625 C 1.359375 -9.90625 0.859375 -9.40625 0.859375 -8.8125 C 0.859375 -8.15625 1.328125 -7.6875 1.96875 -7.6875 C 2.59375 -7.6875 3.078125 -8.15625 3.078125 -8.796875 C 3.078125 -9.40625 2.59375 -9.90625 1.984375 -9.90625 Z M 1.984375 -9.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-0"> +<path style="stroke:none;" d="M 10.171875 -7.21875 L 8 -7.21875 L 8 -7.015625 C 8.5625 -6.953125 8.75 -6.84375 8.75 -6.546875 C 8.75 -6.3125 8.6875 -6.03125 8.578125 -5.71875 L 7.21875 -2.03125 L 5.78125 -5.75 C 5.765625 -5.796875 5.65625 -6.046875 5.65625 -6.078125 C 5.546875 -6.296875 5.484375 -6.515625 5.484375 -6.625 C 5.484375 -6.890625 5.734375 -7 6.328125 -7.015625 L 6.328125 -7.21875 L 3.40625 -7.21875 L 3.40625 -7.015625 C 4.03125 -7 4.140625 -6.90625 4.515625 -6.03125 L 4.875 -5.140625 L 3.703125 -2.0625 L 2.140625 -6.15625 C 2.0625 -6.359375 2.015625 -6.546875 2.015625 -6.65625 C 2.015625 -6.90625 2.171875 -6.984375 2.71875 -7.015625 L 2.71875 -7.21875 L 0.046875 -7.21875 L 0.046875 -7.015625 C 0.609375 -6.953125 0.78125 -6.765625 1.171875 -5.734375 L 3.28125 0.125 L 3.453125 0.125 L 5.125 -4.5 L 6.875 0.125 L 7.03125 0.125 C 7.96875 -2.71875 8.078125 -3.03125 9.265625 -6.234375 C 9.46875 -6.8125 9.59375 -6.90625 10.171875 -7.015625 Z M 10.171875 -7.21875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-1"> +<path style="stroke:none;" d="M 4.453125 -1.78125 C 3.921875 -0.953125 3.453125 -0.640625 2.765625 -0.640625 C 2.140625 -0.640625 1.671875 -0.953125 1.359375 -1.578125 C 1.15625 -2 1.078125 -2.359375 1.0625 -3.015625 L 4.421875 -3.015625 C 4.328125 -3.734375 4.21875 -4.046875 3.953125 -4.390625 C 3.625 -4.78125 3.125 -5.015625 2.546875 -5.015625 C 2 -5.015625 1.5 -4.828125 1.078125 -4.453125 C 0.5625 -4 0.265625 -3.234375 0.265625 -2.328125 C 0.265625 -0.828125 1.0625 0.109375 2.3125 0.109375 C 3.34375 0.109375 4.171875 -0.53125 4.625 -1.71875 Z M 1.078125 -3.375 C 1.203125 -4.21875 1.578125 -4.625 2.234375 -4.625 C 2.90625 -4.625 3.15625 -4.3125 3.296875 -3.375 Z M 1.078125 -3.375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-2"> +<path style="stroke:none;" d="M 1.671875 -7.421875 L 1.609375 -7.453125 C 1.15625 -7.28125 0.859375 -7.203125 0.34375 -7.0625 L 0.03125 -6.96875 L 0.03125 -6.796875 C 0.09375 -6.8125 0.140625 -6.8125 0.21875 -6.8125 C 0.65625 -6.8125 0.75 -6.703125 0.75 -6.25 L 0.75 -0.59375 C 0.75 -0.25 1.671875 0.109375 2.546875 0.109375 C 3.984375 0.109375 5.109375 -1.09375 5.109375 -2.65625 C 5.109375 -3.984375 4.28125 -5.015625 3.1875 -5.015625 C 2.515625 -5.015625 1.890625 -4.625 1.671875 -4.09375 Z M 1.671875 -3.515625 C 1.671875 -3.9375 2.1875 -4.328125 2.75 -4.328125 C 3.59375 -4.328125 4.140625 -3.484375 4.140625 -2.15625 C 4.140625 -0.9375 3.625 -0.234375 2.71875 -0.234375 C 2.15625 -0.234375 1.671875 -0.484375 1.671875 -0.765625 Z M 1.671875 -3.515625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-3"> +<path style="stroke:none;" d="M 0.078125 -4.25 C 0.234375 -4.28125 0.328125 -4.296875 0.453125 -4.296875 C 0.734375 -4.296875 0.828125 -4.125 0.828125 -3.640625 L 0.828125 -0.921875 C 0.828125 -0.375 0.75 -0.296875 0.046875 -0.15625 L 0.046875 0 L 2.671875 0 L 2.671875 -0.15625 C 1.9375 -0.203125 1.75 -0.359375 1.75 -0.984375 L 1.75 -3.4375 C 1.75 -3.78125 2.21875 -4.328125 2.515625 -4.328125 C 2.578125 -4.328125 2.671875 -4.28125 2.796875 -4.171875 C 2.96875 -4.015625 3.09375 -3.953125 3.234375 -3.953125 C 3.484375 -3.953125 3.65625 -4.140625 3.65625 -4.4375 C 3.65625 -4.796875 3.421875 -5.015625 3.046875 -5.015625 C 2.59375 -5.015625 2.28125 -4.765625 1.75 -3.984375 L 1.75 -5 L 1.6875 -5.015625 C 1.109375 -4.78125 0.71875 -4.640625 0.078125 -4.421875 Z M 0.078125 -4.25 "/> +</symbol> +<symbol overflow="visible" id="glyph7-4"> +<path style="stroke:none;" d="M 2.71875 -5.015625 C 1.3125 -5.015625 0.3125 -3.96875 0.3125 -2.46875 C 0.3125 -1 1.328125 0.109375 2.703125 0.109375 C 4.078125 0.109375 5.125 -1.046875 5.125 -2.546875 C 5.125 -3.984375 4.125 -5.015625 2.71875 -5.015625 Z M 2.578125 -4.71875 C 3.5 -4.71875 4.140625 -3.671875 4.140625 -2.171875 C 4.140625 -0.9375 3.65625 -0.203125 2.828125 -0.203125 C 2.40625 -0.203125 2 -0.453125 1.78125 -0.890625 C 1.46875 -1.46875 1.296875 -2.21875 1.296875 -3 C 1.296875 -4.03125 1.8125 -4.71875 2.578125 -4.71875 Z M 2.578125 -4.71875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-5"> +<path style="stroke:none;" d="M 6.234375 -4.90625 L 6.234375 -4.75 C 6.59375 -4.671875 6.703125 -4.59375 6.703125 -4.40625 C 6.703125 -4.25 6.640625 -3.984375 6.515625 -3.6875 L 5.546875 -1.265625 L 4.625 -3.703125 C 4.4375 -4.203125 4.4375 -4.203125 4.4375 -4.359375 C 4.4375 -4.59375 4.5625 -4.671875 5.078125 -4.75 L 5.078125 -4.90625 L 2.859375 -4.90625 L 2.859375 -4.75 C 3.265625 -4.703125 3.390625 -4.578125 3.609375 -3.984375 C 3.6875 -3.78125 3.765625 -3.578125 3.828125 -3.375 L 2.828125 -1.203125 L 1.75 -4.0625 C 1.71875 -4.171875 1.6875 -4.28125 1.6875 -4.390625 C 1.6875 -4.609375 1.828125 -4.703125 2.1875 -4.75 L 2.1875 -4.90625 L 0.234375 -4.90625 L 0.234375 -4.75 C 0.484375 -4.71875 0.5625 -4.609375 0.8125 -4.0625 L 2.28125 -0.328125 C 2.40625 0 2.5 0.15625 2.5625 0.15625 C 2.625 0.15625 2.703125 0.015625 2.828125 -0.265625 L 4.0625 -2.890625 L 5.046875 -0.3125 C 5.203125 0.09375 5.25 0.15625 5.3125 0.15625 C 5.390625 0.15625 5.4375 0.046875 5.609375 -0.375 L 7.125 -4.15625 C 7.3125 -4.609375 7.34375 -4.671875 7.5625 -4.75 L 7.5625 -4.90625 Z M 6.234375 -4.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-6"> +<path style="stroke:none;" d="M 3.4375 -3.421875 L 3.390625 -4.90625 L 3.265625 -4.90625 L 3.25 -4.890625 C 3.15625 -4.8125 3.140625 -4.796875 3.09375 -4.796875 C 3.03125 -4.796875 2.921875 -4.828125 2.796875 -4.875 C 2.5625 -4.96875 2.328125 -5 2.046875 -5 C 1.171875 -5 0.5625 -4.453125 0.5625 -3.671875 C 0.5625 -3.046875 0.90625 -2.625 1.828125 -2.09375 L 2.46875 -1.734375 C 2.84375 -1.515625 3.03125 -1.25 3.03125 -0.921875 C 3.03125 -0.4375 2.6875 -0.125 2.125 -0.125 C 1.75 -0.125 1.421875 -0.265625 1.203125 -0.515625 C 0.984375 -0.78125 0.890625 -1.03125 0.734375 -1.65625 L 0.5625 -1.65625 L 0.5625 0.046875 L 0.703125 0.046875 C 0.78125 -0.0625 0.828125 -0.09375 0.953125 -0.09375 C 1.0625 -0.09375 1.203125 -0.0625 1.46875 0 C 1.765625 0.0625 2.0625 0.109375 2.25 0.109375 C 3.09375 0.109375 3.796875 -0.53125 3.796875 -1.28125 C 3.796875 -1.828125 3.53125 -2.1875 2.875 -2.578125 L 1.703125 -3.28125 C 1.390625 -3.453125 1.234375 -3.734375 1.234375 -4.03125 C 1.234375 -4.453125 1.578125 -4.765625 2.078125 -4.765625 C 2.6875 -4.765625 3.015625 -4.390625 3.265625 -3.421875 Z M 3.4375 -3.421875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-7"> +<path style="stroke:none;" d="M 1.71875 -3.734375 C 2.171875 -4.25 2.5 -4.421875 2.921875 -4.421875 C 3.46875 -4.421875 3.734375 -4.03125 3.734375 -3.265625 L 3.734375 -1.109375 C 3.734375 -0.375 3.625 -0.234375 3 -0.15625 L 3 0 L 5.3125 0 L 5.3125 -0.15625 C 4.71875 -0.265625 4.65625 -0.359375 4.65625 -1.109375 L 4.65625 -3.28125 C 4.65625 -4.421875 4.203125 -5.015625 3.3125 -5.015625 C 2.671875 -5.015625 2.21875 -4.75 1.71875 -4.09375 L 1.71875 -7.421875 L 1.65625 -7.453125 C 1.28125 -7.3125 1.015625 -7.234375 0.40625 -7.0625 L 0.109375 -6.96875 L 0.109375 -6.796875 C 0.15625 -6.8125 0.1875 -6.8125 0.234375 -6.8125 C 0.703125 -6.8125 0.796875 -6.71875 0.796875 -6.25 L 0.796875 -1.109375 C 0.796875 -0.34375 0.734375 -0.25 0.09375 -0.15625 L 0.09375 0 L 2.453125 0 L 2.453125 -0.15625 C 1.828125 -0.234375 1.71875 -0.359375 1.71875 -1.109375 Z M 1.71875 -3.734375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-8"> +<path style="stroke:none;" d="M 4.828125 -0.71875 C 4.640625 -0.5625 4.5 -0.515625 4.34375 -0.515625 C 4.09375 -0.515625 4.015625 -0.671875 4.015625 -1.140625 L 4.015625 -3.265625 C 4.015625 -3.84375 3.953125 -4.15625 3.796875 -4.421875 C 3.5625 -4.8125 3.09375 -5.015625 2.4375 -5.015625 C 1.421875 -5.015625 0.609375 -4.484375 0.609375 -3.796875 C 0.609375 -3.546875 0.828125 -3.328125 1.078125 -3.328125 C 1.34375 -3.328125 1.578125 -3.546875 1.578125 -3.78125 C 1.578125 -3.828125 1.5625 -3.875 1.546875 -3.953125 C 1.53125 -4.0625 1.515625 -4.140625 1.515625 -4.21875 C 1.515625 -4.515625 1.859375 -4.75 2.296875 -4.75 C 2.828125 -4.75 3.125 -4.4375 3.125 -3.84375 L 3.125 -3.1875 C 1.453125 -2.515625 1.265625 -2.421875 0.796875 -2 C 0.5625 -1.78125 0.40625 -1.421875 0.40625 -1.0625 C 0.40625 -0.375 0.890625 0.109375 1.546875 0.109375 C 2.03125 0.109375 2.46875 -0.125 3.140625 -0.6875 C 3.203125 -0.125 3.390625 0.109375 3.84375 0.109375 C 4.203125 0.109375 4.4375 -0.015625 4.828125 -0.4375 Z M 3.125 -1.34375 C 3.125 -1 3.078125 -0.90625 2.84375 -0.78125 C 2.578125 -0.625 2.28125 -0.53125 2.046875 -0.53125 C 1.671875 -0.53125 1.359375 -0.890625 1.359375 -1.359375 L 1.359375 -1.40625 C 1.359375 -2.046875 1.8125 -2.4375 3.125 -2.921875 Z M 3.125 -1.34375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-9"> +<path style="stroke:none;" d="M 5.203125 -4.90625 L 3.6875 -4.90625 L 3.6875 -4.75 C 4.03125 -4.71875 4.203125 -4.609375 4.203125 -4.390625 C 4.203125 -4.28125 4.171875 -4.171875 4.140625 -4.0625 L 3.046875 -1.25 L 1.9375 -4.03125 C 1.875 -4.1875 1.84375 -4.34375 1.84375 -4.4375 C 1.84375 -4.640625 1.96875 -4.71875 2.34375 -4.75 L 2.34375 -4.90625 L 0.203125 -4.90625 L 0.203125 -4.75 C 0.625 -4.71875 0.703125 -4.609375 1.203125 -3.484375 L 2.515625 -0.359375 C 2.53125 -0.296875 2.5625 -0.21875 2.59375 -0.125 C 2.65625 0.0625 2.71875 0.15625 2.796875 0.15625 C 2.859375 0.15625 2.9375 0.015625 3.09375 -0.390625 L 4.5 -3.890625 C 4.8125 -4.640625 4.875 -4.71875 5.203125 -4.75 Z M 5.203125 -4.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-10"> +<path style="stroke:none;" d="M 0.203125 -6.796875 L 0.265625 -6.796875 C 0.390625 -6.8125 0.53125 -6.8125 0.609375 -6.8125 C 0.953125 -6.8125 1.0625 -6.65625 1.0625 -6.15625 L 1.0625 -0.953125 C 1.0625 -0.359375 0.921875 -0.21875 0.234375 -0.15625 L 0.234375 0 L 2.796875 0 L 2.796875 -0.15625 C 2.109375 -0.203125 1.984375 -0.3125 1.984375 -0.921875 L 1.984375 -7.421875 L 1.9375 -7.453125 C 1.375 -7.265625 0.953125 -7.15625 0.203125 -6.96875 Z M 0.203125 -6.796875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-11"> +<path style="stroke:none;" d="M 3.75 0.109375 L 5.359375 -0.453125 L 5.359375 -0.625 C 5.15625 -0.625 5.140625 -0.625 5.109375 -0.625 C 4.71875 -0.625 4.625 -0.734375 4.625 -1.25 L 4.625 -7.421875 L 4.5625 -7.453125 C 4.046875 -7.265625 3.671875 -7.15625 2.96875 -6.96875 L 2.96875 -6.796875 C 3.046875 -6.8125 3.125 -6.8125 3.203125 -6.8125 C 3.609375 -6.8125 3.703125 -6.703125 3.703125 -6.25 L 3.703125 -4.546875 C 3.296875 -4.890625 3 -5.015625 2.5625 -5.015625 C 1.3125 -5.015625 0.296875 -3.78125 0.296875 -2.234375 C 0.296875 -0.84375 1.109375 0.109375 2.3125 0.109375 C 2.921875 0.109375 3.34375 -0.109375 3.703125 -0.625 L 3.703125 0.078125 Z M 3.703125 -1.109375 C 3.703125 -1.03125 3.625 -0.90625 3.515625 -0.78125 C 3.328125 -0.5625 3.046875 -0.453125 2.734375 -0.453125 C 1.828125 -0.453125 1.234375 -1.328125 1.234375 -2.671875 C 1.234375 -3.90625 1.765625 -4.71875 2.59375 -4.71875 C 3.171875 -4.71875 3.703125 -4.203125 3.703125 -3.625 Z M 3.703125 -1.109375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-12"> +<path style="stroke:none;" d="M 2.78125 -4.90625 L 1.671875 -4.90625 L 1.671875 -6.171875 C 1.671875 -6.28125 1.671875 -6.3125 1.609375 -6.3125 C 1.53125 -6.21875 1.46875 -6.125 1.390625 -6.015625 C 0.96875 -5.40625 0.5 -4.890625 0.328125 -4.84375 C 0.203125 -4.765625 0.140625 -4.6875 0.140625 -4.640625 C 0.140625 -4.609375 0.15625 -4.578125 0.1875 -4.5625 L 0.765625 -4.5625 L 0.765625 -1.28125 C 0.765625 -0.359375 1.09375 0.109375 1.734375 0.109375 C 2.265625 0.109375 2.6875 -0.15625 3.046875 -0.71875 L 2.90625 -0.84375 C 2.671875 -0.5625 2.484375 -0.453125 2.25 -0.453125 C 1.84375 -0.453125 1.671875 -0.75 1.671875 -1.4375 L 1.671875 -4.5625 L 2.78125 -4.5625 Z M 2.78125 -4.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-13"> +<path style="stroke:none;" d="M 0.203125 -4.34375 C 0.34375 -4.375 0.4375 -4.390625 0.5625 -4.390625 C 0.84375 -4.390625 0.9375 -4.203125 0.9375 -3.6875 L 0.9375 -0.921875 C 0.9375 -0.34375 0.78125 -0.171875 0.171875 -0.15625 L 0.171875 0 L 2.59375 0 L 2.59375 -0.15625 C 2.015625 -0.1875 1.859375 -0.3125 1.859375 -0.734375 L 1.859375 -3.8125 C 1.859375 -3.828125 1.9375 -3.9375 2.015625 -4.015625 C 2.296875 -4.265625 2.765625 -4.453125 3.140625 -4.453125 C 3.625 -4.453125 3.859375 -4.0625 3.859375 -3.296875 L 3.859375 -0.9375 C 3.859375 -0.328125 3.734375 -0.203125 3.125 -0.15625 L 3.125 0 L 5.5625 0 L 5.5625 -0.15625 C 4.9375 -0.171875 4.78125 -0.359375 4.78125 -1.03125 L 4.78125 -3.78125 C 5.109375 -4.25 5.46875 -4.453125 5.96875 -4.453125 C 6.59375 -4.453125 6.78125 -4.15625 6.78125 -3.25 L 6.78125 -0.953125 C 6.78125 -0.328125 6.703125 -0.234375 6.0625 -0.15625 L 6.0625 0 L 8.453125 0 L 8.453125 -0.15625 L 8.171875 -0.1875 C 7.84375 -0.203125 7.703125 -0.40625 7.703125 -0.828125 L 7.703125 -3.078125 C 7.703125 -4.359375 7.28125 -5.015625 6.4375 -5.015625 C 5.796875 -5.015625 5.25 -4.734375 4.65625 -4.09375 C 4.453125 -4.71875 4.09375 -5.015625 3.5 -5.015625 C 3.015625 -5.015625 2.71875 -4.859375 1.8125 -4.171875 L 1.8125 -5 L 1.734375 -5.015625 C 1.171875 -4.8125 0.8125 -4.6875 0.203125 -4.53125 Z M 0.203125 -4.34375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-14"> +<path style="stroke:none;" d="M 5.21875 -0.546875 L 5.171875 -0.546875 C 4.671875 -0.546875 4.546875 -0.671875 4.546875 -1.171875 L 4.546875 -4.90625 L 2.828125 -4.90625 L 2.828125 -4.71875 C 3.5 -4.6875 3.625 -4.578125 3.625 -4.03125 L 3.625 -1.46875 C 3.625 -1.171875 3.578125 -1.015625 3.421875 -0.890625 C 3.125 -0.65625 2.796875 -0.53125 2.46875 -0.53125 C 2.046875 -0.53125 1.6875 -0.890625 1.6875 -1.359375 L 1.6875 -4.90625 L 0.09375 -4.90625 L 0.09375 -4.75 C 0.625 -4.71875 0.78125 -4.5625 0.78125 -4.0625 L 0.78125 -1.3125 C 0.78125 -0.453125 1.296875 0.109375 2.09375 0.109375 C 2.5 0.109375 2.921875 -0.0625 3.21875 -0.359375 L 3.6875 -0.828125 L 3.6875 0.078125 L 3.734375 0.09375 C 4.28125 -0.125 4.671875 -0.234375 5.21875 -0.390625 Z M 5.21875 -0.546875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-15"> +<path style="stroke:none;" d="M 1.90625 -5.015625 L 0.21875 -4.421875 L 0.21875 -4.25 L 0.3125 -4.265625 C 0.4375 -4.28125 0.578125 -4.296875 0.671875 -4.296875 C 0.9375 -4.296875 1.03125 -4.125 1.03125 -3.640625 L 1.03125 -1.109375 C 1.03125 -0.328125 0.921875 -0.203125 0.171875 -0.15625 L 0.171875 0 L 2.765625 0 L 2.765625 -0.15625 C 2.046875 -0.21875 1.953125 -0.328125 1.953125 -1.109375 L 1.953125 -4.984375 Z M 1.390625 -7.453125 C 1.09375 -7.453125 0.84375 -7.203125 0.84375 -6.890625 C 0.84375 -6.59375 1.09375 -6.34375 1.390625 -6.34375 C 1.71875 -6.34375 1.96875 -6.578125 1.96875 -6.890625 C 1.96875 -7.203125 1.71875 -7.453125 1.390625 -7.453125 Z M 1.390625 -7.453125 "/> +</symbol> +<symbol overflow="visible" id="glyph7-16"> +<path style="stroke:none;" d="M 0.421875 -2.796875 L 0.421875 -2.109375 L 3.109375 -2.109375 L 3.109375 -2.796875 Z M 0.421875 -2.796875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-17"> +<path style="stroke:none;" d="M 0.09375 -4.28125 C 0.203125 -4.296875 0.265625 -4.296875 0.375 -4.296875 C 0.734375 -4.296875 0.8125 -4.1875 0.8125 -3.671875 L 0.8125 1.421875 C 0.8125 2 0.703125 2.109375 0.046875 2.1875 L 0.046875 2.359375 L 2.6875 2.359375 L 2.6875 2.171875 C 1.875 2.15625 1.734375 2.046875 1.734375 1.359375 L 1.734375 -0.359375 C 2.109375 0 2.375 0.109375 2.828125 0.109375 C 4.125 0.109375 5.125 -1.109375 5.125 -2.6875 C 5.125 -4.046875 4.359375 -5.015625 3.296875 -5.015625 C 2.6875 -5.015625 2.21875 -4.75 1.734375 -4.15625 L 1.734375 -5 L 1.671875 -5.015625 C 1.078125 -4.78125 0.703125 -4.640625 0.09375 -4.453125 Z M 1.734375 -3.640625 C 1.734375 -3.96875 2.34375 -4.359375 2.84375 -4.359375 C 3.65625 -4.359375 4.1875 -3.53125 4.1875 -2.265625 C 4.1875 -1.0625 3.65625 -0.234375 2.875 -0.234375 C 2.359375 -0.234375 1.734375 -0.625 1.734375 -0.953125 Z M 1.734375 -3.640625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-18"> +<path style="stroke:none;" d="M 0.171875 -4.34375 C 0.234375 -4.375 0.34375 -4.390625 0.46875 -4.390625 C 0.78125 -4.390625 0.875 -4.21875 0.875 -3.6875 L 0.875 -0.984375 C 0.875 -0.359375 0.75 -0.203125 0.203125 -0.15625 L 0.203125 0 L 2.515625 0 L 2.515625 -0.15625 C 1.953125 -0.203125 1.78125 -0.34375 1.78125 -0.734375 L 1.78125 -3.796875 C 2.3125 -4.28125 2.546875 -4.421875 2.90625 -4.421875 C 3.453125 -4.421875 3.703125 -4.078125 3.703125 -3.359375 L 3.703125 -1.078125 C 3.703125 -0.390625 3.5625 -0.203125 3.015625 -0.15625 L 3.015625 0 L 5.296875 0 L 5.296875 -0.15625 C 4.75 -0.21875 4.625 -0.34375 4.625 -0.890625 L 4.625 -3.375 C 4.625 -4.40625 4.140625 -5.015625 3.34375 -5.015625 C 2.828125 -5.015625 2.5 -4.828125 1.75 -4.140625 L 1.75 -5 L 1.671875 -5.015625 C 1.140625 -4.828125 0.78125 -4.703125 0.171875 -4.53125 Z M 0.171875 -4.34375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-19"> +<path style="stroke:none;" d="M 4.34375 -1.703125 C 3.8125 -0.9375 3.421875 -0.671875 2.796875 -0.671875 C 1.8125 -0.671875 1.109375 -1.546875 1.109375 -2.796875 C 1.109375 -3.921875 1.71875 -4.703125 2.59375 -4.703125 C 2.984375 -4.703125 3.125 -4.578125 3.234375 -4.171875 L 3.296875 -3.9375 C 3.390625 -3.625 3.59375 -3.4375 3.8125 -3.4375 C 4.09375 -3.4375 4.34375 -3.640625 4.34375 -3.890625 C 4.34375 -4.5 3.578125 -5.015625 2.65625 -5.015625 C 2.125 -5.015625 1.578125 -4.796875 1.125 -4.40625 C 0.578125 -3.921875 0.265625 -3.1875 0.265625 -2.328125 C 0.265625 -0.90625 1.140625 0.109375 2.34375 0.109375 C 2.828125 0.109375 3.265625 -0.0625 3.671875 -0.40625 C 3.953125 -0.671875 4.171875 -0.953125 4.5 -1.609375 Z M 4.34375 -1.703125 "/> +</symbol> +<symbol overflow="visible" id="glyph7-20"> +<path style="stroke:none;" d="M 5.125 -4.234375 L 5.125 -4.65625 L 4.28125 -4.65625 C 4.0625 -4.65625 3.90625 -4.6875 3.6875 -4.765625 L 3.453125 -4.859375 C 3.15625 -4.96875 2.859375 -5.015625 2.578125 -5.015625 C 1.5625 -5.015625 0.75 -4.234375 0.75 -3.234375 C 0.75 -2.546875 1.046875 -2.140625 1.765625 -1.78125 C 1.5625 -1.578125 1.359375 -1.390625 1.296875 -1.34375 C 0.9375 -1.03125 0.796875 -0.8125 0.796875 -0.59375 C 0.796875 -0.359375 0.921875 -0.234375 1.375 -0.015625 C 0.59375 0.5625 0.3125 0.921875 0.3125 1.3125 C 0.3125 1.890625 1.15625 2.375 2.1875 2.375 C 3.015625 2.375 3.859375 2.09375 4.421875 1.640625 C 4.84375 1.296875 5.03125 0.953125 5.03125 0.53125 C 5.03125 -0.140625 4.515625 -0.59375 3.703125 -0.625 L 2.296875 -0.703125 C 1.71875 -0.71875 1.453125 -0.8125 1.453125 -1 C 1.453125 -1.203125 1.8125 -1.59375 2.109375 -1.671875 C 2.203125 -1.671875 2.28125 -1.65625 2.3125 -1.65625 C 2.515625 -1.640625 2.65625 -1.625 2.71875 -1.625 C 3.125 -1.625 3.5625 -1.78125 3.90625 -2.078125 C 4.265625 -2.390625 4.421875 -2.765625 4.421875 -3.3125 C 4.421875 -3.625 4.375 -3.875 4.21875 -4.234375 Z M 1.609375 0.015625 C 1.96875 0.09375 2.828125 0.15625 3.375 0.15625 C 4.359375 0.15625 4.71875 0.3125 4.71875 0.703125 C 4.71875 1.328125 3.890625 1.75 2.65625 1.75 C 1.703125 1.75 1.0625 1.4375 1.0625 0.953125 C 1.0625 0.703125 1.140625 0.5625 1.609375 0.015625 Z M 1.65625 -3.6875 C 1.65625 -4.328125 1.96875 -4.71875 2.46875 -4.71875 C 2.796875 -4.71875 3.09375 -4.53125 3.265625 -4.203125 C 3.453125 -3.8125 3.59375 -3.3125 3.59375 -2.890625 C 3.59375 -2.28125 3.265625 -1.890625 2.765625 -1.890625 C 2.109375 -1.890625 1.65625 -2.609375 1.65625 -3.65625 Z M 1.65625 -3.6875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-21"> +<path style="stroke:none;" d="M 3.265625 1.421875 L 2.328125 1.421875 C 1.96875 1.421875 1.78125 1.25 1.78125 0.859375 L 1.78125 -6.46875 C 1.78125 -6.8125 1.9375 -6.953125 2.28125 -6.953125 L 3.265625 -6.953125 L 3.265625 -7.21875 L 0.953125 -7.21875 L 0.953125 1.703125 L 3.265625 1.703125 Z M 3.265625 1.421875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-22"> +<path style="stroke:none;" d="M 1.671875 -3.59375 C 2.3125 -3.59375 2.5625 -3.578125 2.828125 -3.484375 C 3.5 -3.234375 3.921875 -2.625 3.921875 -1.859375 C 3.921875 -0.953125 3.296875 -0.234375 2.5 -0.234375 C 2.203125 -0.234375 1.984375 -0.3125 1.578125 -0.578125 C 1.25 -0.78125 1.0625 -0.84375 0.890625 -0.84375 C 0.625 -0.84375 0.46875 -0.703125 0.46875 -0.46875 C 0.46875 -0.09375 0.9375 0.15625 1.703125 0.15625 C 2.546875 0.15625 3.40625 -0.125 3.921875 -0.578125 C 4.421875 -1.03125 4.71875 -1.65625 4.71875 -2.390625 C 4.71875 -2.9375 4.53125 -3.453125 4.21875 -3.796875 C 4 -4.03125 3.796875 -4.171875 3.3125 -4.375 C 4.0625 -4.890625 4.34375 -5.296875 4.34375 -5.875 C 4.34375 -6.765625 3.640625 -7.375 2.640625 -7.375 C 2.09375 -7.375 1.609375 -7.1875 1.21875 -6.84375 C 0.890625 -6.546875 0.734375 -6.265625 0.484375 -5.609375 L 0.65625 -5.5625 C 1.09375 -6.359375 1.59375 -6.71875 2.28125 -6.71875 C 2.984375 -6.71875 3.484375 -6.234375 3.484375 -5.546875 C 3.484375 -5.15625 3.3125 -4.765625 3.046875 -4.5 C 2.71875 -4.171875 2.40625 -4 1.671875 -3.734375 Z M 1.671875 -3.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-23"> +<path style="stroke:none;" d="M 0.640625 0.234375 C 1.859375 0.09375 2.46875 -0.109375 3.203125 -0.640625 C 4.34375 -1.46875 5 -2.828125 5 -4.296875 C 5 -6.09375 4 -7.375 2.59375 -7.375 C 1.296875 -7.375 0.328125 -6.265625 0.328125 -4.796875 C 0.328125 -3.46875 1.109375 -2.578125 2.296875 -2.578125 C 2.890625 -2.578125 3.34375 -2.765625 3.921875 -3.203125 C 3.484375 -1.421875 2.265625 -0.265625 0.609375 0.015625 Z M 3.953125 -3.875 C 3.953125 -3.65625 3.90625 -3.5625 3.78125 -3.453125 C 3.484375 -3.203125 3.078125 -3.046875 2.6875 -3.046875 C 1.859375 -3.046875 1.328125 -3.875 1.328125 -5.171875 C 1.328125 -5.796875 1.5 -6.453125 1.734375 -6.734375 C 1.921875 -6.953125 2.1875 -7.0625 2.515625 -7.0625 C 3.453125 -7.0625 3.953125 -6.125 3.953125 -4.296875 Z M 3.953125 -3.875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-24"> +<path style="stroke:none;" d="M 0.375 1.421875 L 0.375 1.703125 L 2.671875 1.703125 L 2.671875 -7.21875 L 0.375 -7.21875 L 0.375 -6.953125 L 1.3125 -6.953125 C 1.671875 -6.953125 1.84375 -6.765625 1.84375 -6.375 L 1.84375 0.953125 C 1.84375 1.28125 1.6875 1.421875 1.359375 1.421875 Z M 0.375 1.421875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-25"> +<path style="stroke:none;" d="M 1.359375 -1.09375 C 1.03125 -1.09375 0.765625 -0.8125 0.765625 -0.46875 C 0.765625 -0.15625 1.03125 0.125 1.359375 0.125 C 1.6875 0.125 1.96875 -0.15625 1.96875 -0.46875 C 1.96875 -0.8125 1.6875 -1.09375 1.359375 -1.09375 Z M 1.359375 -1.09375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-26"> +<path style="stroke:none;" d="M 4.875 -7.375 L 4.640625 -7.375 C 4.609375 -7.125 4.484375 -7 4.296875 -7 C 4.1875 -7 4 -7.046875 3.8125 -7.125 C 3.40625 -7.28125 3.015625 -7.375 2.65625 -7.375 C 2.1875 -7.375 1.71875 -7.1875 1.359375 -6.875 C 0.96875 -6.53125 0.78125 -6.078125 0.78125 -5.5 C 0.78125 -4.640625 1.25 -4.03125 2.46875 -3.375 C 3.265625 -2.96875 3.828125 -2.515625 4.09375 -2.109375 C 4.203125 -1.96875 4.25 -1.734375 4.25 -1.46875 C 4.25 -0.734375 3.703125 -0.234375 2.90625 -0.234375 C 1.9375 -0.234375 1.25 -0.84375 0.703125 -2.171875 L 0.453125 -2.171875 L 0.78125 0.140625 L 1.03125 0.140625 C 1.03125 -0.0625 1.171875 -0.21875 1.328125 -0.21875 C 1.453125 -0.21875 1.640625 -0.171875 1.84375 -0.09375 C 2.25 0.0625 2.6875 0.15625 3.125 0.15625 C 4.390625 0.15625 5.359375 -0.703125 5.359375 -1.828125 C 5.359375 -2.71875 4.75 -3.4375 3.3125 -4.203125 C 2.171875 -4.84375 1.71875 -5.328125 1.71875 -5.90625 C 1.71875 -6.515625 2.171875 -6.921875 2.84375 -6.921875 C 3.34375 -6.921875 3.796875 -6.71875 4.171875 -6.328125 C 4.515625 -5.984375 4.671875 -5.6875 4.84375 -5.046875 L 5.109375 -5.046875 Z M 4.875 -7.375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-27"> +<path style="stroke:none;" d="M 5.1875 -4.90625 L 3.703125 -4.90625 L 3.703125 -4.75 C 4.0625 -4.75 4.234375 -4.640625 4.234375 -4.46875 C 4.234375 -4.421875 4.21875 -4.359375 4.1875 -4.28125 L 3.125 -1.28125 L 1.875 -4.03125 C 1.8125 -4.1875 1.765625 -4.328125 1.765625 -4.453125 C 1.765625 -4.640625 1.9375 -4.71875 2.40625 -4.75 L 2.40625 -4.90625 L 0.15625 -4.90625 L 0.15625 -4.75 C 0.4375 -4.71875 0.625 -4.59375 0.703125 -4.40625 L 1.953125 -1.71875 L 1.984375 -1.640625 L 2.15625 -1.3125 C 2.453125 -0.765625 2.625 -0.375 2.625 -0.203125 C 2.625 -0.046875 2.375 0.640625 2.1875 0.96875 C 2.046875 1.25 1.796875 1.46875 1.640625 1.46875 C 1.578125 1.46875 1.484375 1.4375 1.375 1.390625 C 1.171875 1.3125 0.984375 1.265625 0.796875 1.265625 C 0.546875 1.265625 0.328125 1.484375 0.328125 1.75 C 0.328125 2.109375 0.671875 2.375 1.140625 2.375 C 1.859375 2.375 2.390625 1.765625 2.984375 0.203125 L 4.65625 -4.25 C 4.796875 -4.609375 4.921875 -4.71875 5.1875 -4.75 Z M 5.1875 -4.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-28"> +<path style="stroke:none;" d="M 3.9375 -7.375 C 1.859375 -7.375 0.375 -5.796875 0.375 -3.609375 C 0.375 -2.578125 0.71875 -1.59375 1.3125 -0.953125 C 1.9375 -0.265625 2.90625 0.15625 3.875 0.15625 C 6.015625 0.15625 7.5 -1.375 7.5 -3.5625 C 7.5 -4.640625 7.1875 -5.578125 6.59375 -6.234375 C 5.90625 -6.984375 4.984375 -7.375 3.9375 -7.375 Z M 3.9375 -6.984375 C 4.4375 -6.984375 4.9375 -6.78125 5.328125 -6.4375 C 5.921875 -5.90625 6.265625 -4.875 6.265625 -3.578125 C 6.265625 -2.9375 6.125 -2.1875 5.90625 -1.609375 C 5.796875 -1.34375 5.609375 -1.0625 5.359375 -0.8125 C 4.984375 -0.4375 4.5 -0.234375 3.921875 -0.234375 C 3.40625 -0.234375 2.921875 -0.4375 2.546875 -0.78125 C 1.96875 -1.28125 1.609375 -2.375 1.609375 -3.59375 C 1.609375 -4.703125 1.921875 -5.765625 2.375 -6.265625 C 2.796875 -6.734375 3.34375 -6.984375 3.9375 -6.984375 Z M 3.9375 -6.984375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-29"> +<path style="stroke:none;" d="M 0.90625 1.53125 C 1.640625 1.171875 2.125 0.515625 2.125 -0.140625 C 2.125 -0.6875 1.75 -1.109375 1.25 -1.109375 C 0.875 -1.109375 0.609375 -0.859375 0.609375 -0.484375 C 0.609375 -0.140625 0.859375 0.0625 1.25 0.0625 C 1.3125 0.0625 1.390625 0.046875 1.46875 0.046875 C 1.53125 0.015625 1.53125 0.015625 1.546875 0.015625 C 1.640625 0.015625 1.703125 0.09375 1.703125 0.171875 C 1.703125 0.53125 1.390625 0.921875 0.8125 1.328125 Z M 0.90625 1.53125 "/> +</symbol> +<symbol overflow="visible" id="glyph7-30"> +<path style="stroke:none;" d="M 3.171875 -7.375 L 1.203125 -6.375 L 1.203125 -6.234375 C 1.34375 -6.28125 1.46875 -6.328125 1.5 -6.34375 C 1.703125 -6.421875 1.890625 -6.46875 2 -6.46875 C 2.21875 -6.46875 2.328125 -6.296875 2.328125 -5.953125 L 2.328125 -1.015625 C 2.328125 -0.65625 2.234375 -0.40625 2.0625 -0.3125 C 1.890625 -0.203125 1.75 -0.171875 1.28125 -0.15625 L 1.28125 0 L 4.296875 0 L 4.296875 -0.15625 C 3.4375 -0.171875 3.265625 -0.28125 3.265625 -0.8125 L 3.265625 -7.34375 Z M 3.171875 -7.375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-31"> +<path style="stroke:none;" d="M 5.1875 -1.5 L 5.03125 -1.546875 C 4.640625 -0.921875 4.5 -0.828125 4 -0.828125 L 1.390625 -0.828125 L 3.234375 -2.75 C 4.203125 -3.765625 4.625 -4.59375 4.625 -5.4375 C 4.625 -6.53125 3.734375 -7.375 2.609375 -7.375 C 2 -7.375 1.4375 -7.125 1.03125 -6.703125 C 0.6875 -6.328125 0.53125 -5.984375 0.34375 -5.203125 L 0.5625 -5.140625 C 1 -6.21875 1.390625 -6.5625 2.15625 -6.5625 C 3.0625 -6.5625 3.6875 -5.9375 3.6875 -5.03125 C 3.6875 -4.171875 3.1875 -3.15625 2.265625 -2.1875 L 0.328125 -0.125 L 0.328125 0 L 4.578125 0 Z M 5.1875 -1.5 "/> +</symbol> +<symbol overflow="visible" id="glyph7-32"> +<path style="stroke:none;" d="M 5.140625 -2.515625 L 4.03125 -2.515625 L 4.03125 -7.375 L 3.5625 -7.375 L 0.125 -2.515625 L 0.125 -1.828125 L 3.203125 -1.828125 L 3.203125 0 L 4.03125 0 L 4.03125 -1.828125 L 5.140625 -1.828125 Z M 3.1875 -2.515625 L 0.5625 -2.515625 L 3.1875 -6.265625 Z M 3.1875 -2.515625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-33"> +<path style="stroke:none;" d="M 0.078125 -6.796875 C 0.21875 -6.8125 0.3125 -6.8125 0.421875 -6.8125 C 0.78125 -6.8125 0.890625 -6.671875 0.890625 -6.15625 L 0.890625 -0.890625 C 0.890625 -0.328125 0.859375 -0.296875 0.078125 -0.15625 L 0.078125 0 L 2.625 0 L 2.625 -0.15625 L 2.40625 -0.171875 C 1.96875 -0.203125 1.8125 -0.34375 1.8125 -0.734375 L 1.8125 -2.734375 L 3.34375 -0.703125 L 3.375 -0.65625 C 3.390625 -0.625 3.40625 -0.59375 3.453125 -0.5625 C 3.53125 -0.453125 3.5625 -0.375 3.5625 -0.328125 C 3.5625 -0.234375 3.46875 -0.15625 3.34375 -0.15625 L 3.125 -0.15625 L 3.125 0 L 5.5 0 L 5.5 -0.15625 C 5.03125 -0.203125 4.6875 -0.40625 4.234375 -0.953125 L 2.5625 -3.078125 L 2.875 -3.375 C 3.65625 -4.09375 4.328125 -4.59375 4.640625 -4.671875 C 4.8125 -4.71875 4.96875 -4.75 5.140625 -4.75 L 5.234375 -4.75 L 5.234375 -4.90625 L 3.015625 -4.90625 L 3.015625 -4.75 C 3.4375 -4.75 3.5625 -4.703125 3.5625 -4.546875 C 3.5625 -4.453125 3.453125 -4.3125 3.296875 -4.171875 L 1.8125 -2.84375 L 1.8125 -7.421875 L 1.765625 -7.453125 C 1.359375 -7.3125 1.03125 -7.234375 0.40625 -7.0625 L 0.078125 -6.96875 Z M 0.078125 -6.796875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-34"> +<path style="stroke:none;" d="M 1.25 -1.1875 C 1.25 -0.375 1.109375 -0.234375 0.203125 -0.203125 L 0.203125 0 L 3.4375 0 L 3.4375 -0.203125 C 2.546875 -0.234375 2.359375 -0.390625 2.359375 -1.1875 L 2.359375 -6.03125 C 2.359375 -6.828125 2.515625 -6.96875 3.4375 -7.015625 L 3.4375 -7.21875 L 0.203125 -7.21875 L 0.203125 -7.015625 C 1.125 -6.953125 1.25 -6.84375 1.25 -6.03125 Z M 1.25 -1.1875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-35"> +<path style="stroke:none;" d="M 6.515625 -1.84375 L 6.203125 -1.84375 C 5.65625 -0.671875 5.1875 -0.40625 3.625 -0.40625 L 3.34375 -0.40625 C 2.796875 -0.40625 2.34375 -0.453125 2.265625 -0.53125 C 2.21875 -0.5625 2.1875 -0.6875 2.1875 -0.875 L 2.1875 -3.5625 L 3.875 -3.5625 C 4.765625 -3.5625 4.921875 -3.421875 5.078125 -2.515625 L 5.328125 -2.515625 L 5.328125 -5.046875 L 5.078125 -5.046875 C 5 -4.609375 4.953125 -4.4375 4.828125 -4.28125 C 4.6875 -4.09375 4.390625 -4.015625 3.875 -4.015625 L 2.1875 -4.015625 L 2.1875 -6.4375 C 2.1875 -6.734375 2.25 -6.8125 2.546875 -6.8125 L 4.03125 -6.8125 C 5.25 -6.8125 5.5 -6.640625 5.6875 -5.65625 L 5.953125 -5.65625 L 5.921875 -7.21875 L 0.125 -7.21875 L 0.125 -7.015625 C 0.9375 -6.953125 1.078125 -6.796875 1.078125 -6.03125 L 1.078125 -1.1875 C 1.078125 -0.421875 0.921875 -0.265625 0.125 -0.203125 L 0.125 0 L 6.015625 0 Z M 6.515625 -1.84375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-36"> +<path style="stroke:none;" d="M 3.15625 -4.046875 C 4.25 -4.625 4.625 -5.078125 4.625 -5.828125 C 4.625 -6.71875 3.84375 -7.375 2.75 -7.375 C 1.5625 -7.375 0.671875 -6.640625 0.671875 -5.65625 C 0.671875 -4.9375 0.890625 -4.625 2.03125 -3.625 C 0.84375 -2.71875 0.609375 -2.390625 0.609375 -1.640625 C 0.609375 -0.59375 1.46875 0.15625 2.703125 0.15625 C 4.015625 0.15625 4.859375 -0.5625 4.859375 -1.6875 C 4.859375 -2.53125 4.484375 -3.0625 3.15625 -4.046875 Z M 2.96875 -2.921875 C 3.765625 -2.359375 4.03125 -1.96875 4.03125 -1.359375 C 4.03125 -0.640625 3.53125 -0.15625 2.828125 -0.15625 C 2 -0.15625 1.4375 -0.78125 1.4375 -1.734375 C 1.4375 -2.4375 1.671875 -2.890625 2.3125 -3.40625 Z M 2.84375 -4.25 C 1.875 -4.875 1.484375 -5.375 1.484375 -5.984375 C 1.484375 -6.625 1.96875 -7.0625 2.65625 -7.0625 C 3.40625 -7.0625 3.875 -6.59375 3.875 -5.828125 C 3.875 -5.21875 3.5625 -4.71875 2.9375 -4.3125 C 2.890625 -4.28125 2.890625 -4.28125 2.84375 -4.25 Z M 2.84375 -4.25 "/> +</symbol> +<symbol overflow="visible" id="glyph7-37"> +<path style="stroke:none;" d="M 2.765625 -7.375 C 2.171875 -7.375 1.71875 -7.1875 1.3125 -6.8125 C 0.671875 -6.1875 0.265625 -4.9375 0.265625 -3.671875 C 0.265625 -2.46875 0.625 -1.203125 1.140625 -0.59375 C 1.53125 -0.109375 2.09375 0.15625 2.71875 0.15625 C 3.28125 0.15625 3.75 -0.03125 4.140625 -0.421875 C 4.78125 -1.015625 5.1875 -2.28125 5.1875 -3.59375 C 5.1875 -5.828125 4.203125 -7.375 2.765625 -7.375 Z M 2.734375 -7.09375 C 3.65625 -7.09375 4.140625 -5.859375 4.140625 -3.578125 C 4.140625 -1.296875 3.671875 -0.125 2.71875 -0.125 C 1.78125 -0.125 1.3125 -1.296875 1.3125 -3.5625 C 1.3125 -5.875 1.796875 -7.09375 2.734375 -7.09375 Z M 2.734375 -7.09375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-38"> +<path style="stroke:none;" d="M 5.21875 -2.515625 L 5.21875 -5.046875 L 4.96875 -5.046875 C 4.84375 -4.171875 4.640625 -4.015625 3.78125 -4.015625 L 2.1875 -4.015625 L 2.1875 -6.4375 C 2.1875 -6.734375 2.25 -6.8125 2.546875 -6.8125 L 4.03125 -6.8125 C 5.25 -6.8125 5.5 -6.640625 5.6875 -5.65625 L 5.953125 -5.65625 L 5.921875 -7.21875 L 0.125 -7.21875 L 0.125 -7.015625 C 0.9375 -6.953125 1.078125 -6.796875 1.078125 -6.03125 L 1.078125 -1.3125 C 1.078125 -0.40625 0.953125 -0.265625 0.125 -0.203125 L 0.125 0 L 3.1875 0 L 3.1875 -0.203125 C 2.34375 -0.25 2.1875 -0.40625 2.1875 -1.1875 L 2.1875 -3.5625 L 3.78125 -3.5625 C 4.65625 -3.5625 4.84375 -3.40625 4.96875 -2.515625 Z M 5.21875 -2.515625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-39"> +<path style="stroke:none;" d="M 3.375 -4.90625 L 2.03125 -4.90625 L 2.03125 -6.171875 C 2.03125 -6.8125 2.234375 -7.140625 2.65625 -7.140625 C 2.875 -7.140625 3.03125 -7.03125 3.234375 -6.71875 C 3.40625 -6.4375 3.53125 -6.328125 3.71875 -6.328125 C 3.96875 -6.328125 4.171875 -6.515625 4.171875 -6.765625 C 4.171875 -7.171875 3.703125 -7.453125 3.046875 -7.453125 C 2.359375 -7.453125 1.78125 -7.15625 1.5 -6.65625 C 1.21875 -6.171875 1.140625 -5.78125 1.125 -4.90625 L 0.234375 -4.90625 L 0.234375 -4.5625 L 1.125 -4.5625 L 1.125 -1.140625 C 1.125 -0.34375 1 -0.203125 0.21875 -0.15625 L 0.21875 0 L 3.046875 0 L 3.046875 -0.15625 C 2.15625 -0.203125 2.046875 -0.3125 2.046875 -1.140625 L 2.046875 -4.5625 L 3.375 -4.5625 Z M 3.375 -4.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-40"> +<path style="stroke:none;" d="M 3.03125 0 L 5.21875 0 L 5.21875 -0.15625 C 4.890625 -0.15625 4.671875 -0.34375 4.328125 -0.8125 L 2.9375 -2.953125 L 3.84375 -4.265625 C 4.046875 -4.5625 4.375 -4.734375 4.71875 -4.75 L 4.71875 -4.90625 L 3 -4.90625 L 3 -4.75 C 3.328125 -4.71875 3.4375 -4.65625 3.4375 -4.5 C 3.4375 -4.375 3.296875 -4.140625 3.03125 -3.796875 C 2.984375 -3.734375 2.84375 -3.53125 2.703125 -3.3125 L 2.546875 -3.53125 C 2.25 -3.984375 2.046875 -4.359375 2.046875 -4.5 C 2.046875 -4.65625 2.1875 -4.734375 2.515625 -4.75 L 2.515625 -4.90625 L 0.265625 -4.90625 L 0.265625 -4.75 L 0.359375 -4.75 C 0.6875 -4.75 0.859375 -4.609375 1.203125 -4.09375 L 2.21875 -2.515625 L 0.984375 -0.71875 C 0.65625 -0.265625 0.546875 -0.203125 0.1875 -0.15625 L 0.1875 0 L 1.765625 0 L 1.765625 -0.15625 C 1.46875 -0.15625 1.328125 -0.21875 1.328125 -0.359375 C 1.328125 -0.421875 1.40625 -0.59375 1.546875 -0.8125 L 2.40625 -2.15625 L 3.40625 -0.625 C 3.453125 -0.5625 3.46875 -0.484375 3.46875 -0.421875 C 3.46875 -0.234375 3.390625 -0.1875 3.03125 -0.15625 Z M 3.03125 0 "/> +</symbol> +<symbol overflow="visible" id="glyph7-41"> +<path style="stroke:none;" d="M 1.96875 -6.359375 L 4.109375 -6.359375 C 4.28125 -6.359375 4.328125 -6.375 4.359375 -6.453125 L 4.78125 -7.421875 L 4.671875 -7.5 C 4.515625 -7.28125 4.40625 -7.21875 4.171875 -7.21875 L 1.890625 -7.21875 L 0.703125 -4.640625 C 0.703125 -4.609375 0.703125 -4.609375 0.703125 -4.578125 C 0.703125 -4.53125 0.734375 -4.5 0.828125 -4.5 C 1.171875 -4.5 1.609375 -4.421875 2.0625 -4.28125 C 3.3125 -3.875 3.890625 -3.203125 3.890625 -2.109375 C 3.890625 -1.0625 3.234375 -0.25 2.375 -0.25 C 2.15625 -0.25 1.96875 -0.328125 1.640625 -0.5625 C 1.296875 -0.8125 1.046875 -0.921875 0.8125 -0.921875 C 0.5 -0.921875 0.34375 -0.796875 0.34375 -0.53125 C 0.34375 -0.109375 0.859375 0.15625 1.671875 0.15625 C 2.59375 0.15625 3.375 -0.140625 3.921875 -0.703125 C 4.421875 -1.1875 4.65625 -1.8125 4.65625 -2.640625 C 4.65625 -3.421875 4.453125 -3.921875 3.90625 -4.46875 C 3.421875 -4.953125 2.796875 -5.203125 1.515625 -5.4375 Z M 1.96875 -6.359375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-42"> +<path style="stroke:none;" d="M 3.21875 -7.375 C 2.40625 -6.84375 2.078125 -6.5625 1.671875 -6.0625 C 0.90625 -5.109375 0.53125 -4.03125 0.53125 -2.75 C 0.53125 -1.359375 0.921875 -0.296875 1.890625 0.8125 C 2.328125 1.34375 2.625 1.578125 3.1875 1.9375 L 3.3125 1.75 C 2.4375 1.0625 2.140625 0.6875 1.84375 -0.125 C 1.578125 -0.859375 1.46875 -1.6875 1.46875 -2.78125 C 1.46875 -3.921875 1.609375 -4.828125 1.890625 -5.5 C 2.203125 -6.171875 2.53125 -6.5625 3.3125 -7.203125 Z M 3.21875 -7.375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-43"> +<path style="stroke:none;" d="M 0.421875 1.9375 C 1.21875 1.40625 1.546875 1.125 1.953125 0.625 C 2.71875 -0.328125 3.109375 -1.421875 3.109375 -2.6875 C 3.109375 -4.09375 2.703125 -5.140625 1.75 -6.265625 C 1.296875 -6.78125 1.015625 -7.03125 0.453125 -7.375 L 0.3125 -7.203125 C 1.1875 -6.515625 1.484375 -6.125 1.78125 -5.3125 C 2.046875 -4.578125 2.171875 -3.75 2.171875 -2.65625 C 2.171875 -1.53125 2.03125 -0.625 1.734375 0.046875 C 1.421875 0.734375 1.09375 1.125 0.3125 1.75 Z M 0.421875 1.9375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-44"> +<path style="stroke:none;" d="M 6.671875 0.125 L 6.671875 -5.609375 C 6.671875 -6.265625 6.78125 -6.671875 6.984375 -6.8125 C 7.140625 -6.921875 7.296875 -6.96875 7.703125 -7.015625 L 7.703125 -7.21875 L 5.140625 -7.21875 L 5.140625 -7.015625 C 5.5625 -6.984375 5.71875 -6.9375 5.875 -6.84375 C 6.09375 -6.6875 6.1875 -6.296875 6.1875 -5.609375 L 6.1875 -1.9375 L 2 -7.21875 L 0.125 -7.21875 L 0.125 -7.015625 C 0.59375 -7.015625 0.75 -6.921875 1.1875 -6.40625 L 1.1875 -1.609375 C 1.1875 -0.484375 1.03125 -0.265625 0.125 -0.203125 L 0.125 0 L 2.6875 0 L 2.6875 -0.203125 C 1.859375 -0.25 1.671875 -0.5 1.671875 -1.609375 L 1.671875 -5.875 L 6.484375 0.125 Z M 6.671875 0.125 "/> +</symbol> +<symbol overflow="visible" id="glyph7-45"> +<path style="stroke:none;" d="M 7.734375 -3.859375 L 4.953125 -3.859375 L 4.953125 -3.671875 C 5.421875 -3.625 5.546875 -3.59375 5.6875 -3.515625 C 5.84375 -3.40625 5.90625 -3.15625 5.90625 -2.6875 L 5.90625 -0.921875 C 5.90625 -0.578125 5.25 -0.28125 4.453125 -0.28125 C 2.6875 -0.28125 1.59375 -1.53125 1.59375 -3.5625 C 1.59375 -4.578125 1.890625 -5.578125 2.375 -6.109375 C 2.859375 -6.640625 3.53125 -6.9375 4.28125 -6.9375 C 4.890625 -6.9375 5.421875 -6.734375 5.84375 -6.34375 C 6.171875 -6.03125 6.34375 -5.734375 6.625 -5.078125 L 6.875 -5.078125 L 6.78125 -7.375 L 6.546875 -7.375 C 6.484375 -7.171875 6.28125 -7.015625 6.046875 -7.015625 C 5.9375 -7.015625 5.765625 -7.046875 5.578125 -7.125 C 5.078125 -7.28125 4.578125 -7.375 4.09375 -7.375 C 1.90625 -7.375 0.34375 -5.765625 0.34375 -3.546875 C 0.34375 -2.46875 0.640625 -1.65625 1.25 -1.015625 C 1.984375 -0.265625 3.03125 0.15625 4.234375 0.15625 C 5.171875 0.15625 6.53125 -0.234375 6.96875 -0.609375 L 6.96875 -2.828125 C 6.96875 -3.46875 7.09375 -3.609375 7.734375 -3.671875 Z M 7.734375 -3.859375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-46"> +<path style="stroke:none;" d="M 6.765625 -4.90625 L 6.65625 -7.375 L 6.4375 -7.375 C 6.375 -7.140625 6.1875 -7.015625 5.984375 -7.015625 C 5.875 -7.015625 5.71875 -7.046875 5.546875 -7.109375 C 5.015625 -7.28125 4.46875 -7.375 3.953125 -7.375 C 3.0625 -7.375 2.15625 -7.03125 1.484375 -6.4375 C 0.71875 -5.765625 0.3125 -4.75 0.3125 -3.546875 C 0.3125 -2.53125 0.625 -1.59375 1.1875 -0.96875 C 1.84375 -0.265625 2.84375 0.15625 3.921875 0.15625 C 5.15625 0.15625 6.234375 -0.34375 6.90625 -1.234375 L 6.703125 -1.421875 C 5.90625 -0.65625 5.1875 -0.328125 4.28125 -0.328125 C 3.59375 -0.328125 2.984375 -0.546875 2.515625 -0.953125 C 1.90625 -1.5 1.578125 -2.46875 1.578125 -3.6875 C 1.578125 -5.65625 2.578125 -6.9375 4.171875 -6.9375 C 4.78125 -6.9375 5.359375 -6.703125 5.796875 -6.265625 C 6.140625 -5.921875 6.296875 -5.609375 6.515625 -4.90625 Z M 6.765625 -4.90625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-47"> +<path style="stroke:none;" d="M 2.203125 -3.171875 C 2.484375 -3.15625 2.671875 -3.140625 2.953125 -3.140625 C 3.8125 -3.140625 4.390625 -3.25 4.859375 -3.515625 C 5.515625 -3.859375 5.90625 -4.53125 5.90625 -5.25 C 5.90625 -5.703125 5.765625 -6.125 5.46875 -6.4375 C 5.03125 -6.921875 4.078125 -7.21875 3.046875 -7.21875 L 0.171875 -7.21875 L 0.171875 -7.015625 C 0.984375 -6.921875 1.09375 -6.8125 1.09375 -6.03125 L 1.09375 -1.3125 C 1.09375 -0.390625 1 -0.28125 0.171875 -0.203125 L 0.171875 0 L 3.234375 0 L 3.234375 -0.203125 C 2.359375 -0.234375 2.203125 -0.390625 2.203125 -1.1875 Z M 2.203125 -6.453125 C 2.203125 -6.734375 2.28125 -6.8125 2.578125 -6.8125 C 4.046875 -6.8125 4.71875 -6.296875 4.71875 -5.1875 C 4.71875 -4.125 4.078125 -3.578125 2.828125 -3.578125 C 2.609375 -3.578125 2.453125 -3.59375 2.203125 -3.609375 Z M 2.203125 -6.453125 "/> +</symbol> +<symbol overflow="visible" id="glyph7-48"> +<path style="stroke:none;" d="M 0.1875 -7.21875 L 0.1875 -7.015625 C 1.09375 -6.953125 1.234375 -6.84375 1.234375 -6.03125 L 1.234375 -1.1875 C 1.234375 -0.375 1.09375 -0.234375 0.1875 -0.203125 L 0.1875 0 L 3.828125 0 C 4.671875 0 5.453125 -0.234375 5.859375 -0.59375 C 6.25 -0.953125 6.46875 -1.4375 6.46875 -1.96875 C 6.46875 -2.4375 6.265625 -2.875 5.9375 -3.203125 C 5.609375 -3.484375 5.3125 -3.625 4.609375 -3.796875 C 5.171875 -3.9375 5.40625 -4.046875 5.65625 -4.28125 C 5.9375 -4.515625 6.09375 -4.921875 6.09375 -5.359375 C 6.09375 -6.59375 5.125 -7.21875 3.234375 -7.21875 Z M 2.34375 -3.5625 C 3.40625 -3.5625 3.90625 -3.5 4.296875 -3.34375 C 4.921875 -3.078125 5.21875 -2.640625 5.21875 -1.953125 C 5.21875 -1.359375 4.984375 -0.9375 4.546875 -0.6875 C 4.203125 -0.484375 3.75 -0.40625 3.03125 -0.40625 C 2.5 -0.40625 2.34375 -0.5 2.34375 -0.84375 Z M 2.34375 -3.984375 L 2.34375 -6.484375 C 2.34375 -6.71875 2.421875 -6.8125 2.578125 -6.8125 L 3.0625 -6.8125 C 4.3125 -6.8125 4.984375 -6.296875 4.984375 -5.328125 C 4.984375 -4.46875 4.40625 -3.984375 3.375 -3.984375 Z M 2.34375 -3.984375 "/> +</symbol> +<symbol overflow="visible" id="glyph7-49"> +<path style="stroke:none;" d="M 4.5 -7.015625 C 4.640625 -7 4.75 -7 4.78125 -7 C 5.109375 -6.984375 5.25 -6.890625 5.25 -6.671875 C 5.25 -6.4375 5 -6.109375 4.390625 -5.546875 L 2.46875 -3.796875 L 2.46875 -6.03125 C 2.46875 -6.828125 2.578125 -6.953125 3.46875 -7.015625 L 3.46875 -7.21875 L 0.375 -7.21875 L 0.375 -7.015625 C 1.21875 -6.953125 1.359375 -6.8125 1.359375 -6.03125 L 1.359375 -1.3125 C 1.359375 -0.40625 1.234375 -0.265625 0.375 -0.203125 L 0.375 0 L 3.453125 0 L 3.453125 -0.203125 C 2.59375 -0.265625 2.46875 -0.390625 2.46875 -1.1875 L 2.46875 -3.234375 L 2.75 -3.453125 L 3.90625 -2.3125 C 4.734375 -1.5 5.328125 -0.71875 5.328125 -0.453125 C 5.328125 -0.3125 5.171875 -0.234375 4.859375 -0.21875 C 4.8125 -0.21875 4.6875 -0.21875 4.5625 -0.203125 L 4.5625 0 L 7.890625 0 L 7.890625 -0.203125 C 7.3125 -0.21875 7.171875 -0.328125 6.171875 -1.390625 L 3.625 -4.109375 L 5.703125 -6.15625 C 6.453125 -6.875 6.625 -6.953125 7.359375 -7.015625 L 7.359375 -7.21875 L 4.5 -7.21875 Z M 4.5 -7.015625 "/> +</symbol> +<symbol overflow="visible" id="glyph7-50"> +<path style="stroke:none;" d="M 4.5625 -1.46875 L 4.359375 -1.515625 C 4.25 -0.96875 4.1875 -0.796875 4.046875 -0.609375 C 3.890625 -0.421875 3.515625 -0.328125 2.96875 -0.328125 L 1.46875 -0.328125 L 4.390625 -4.75 L 4.390625 -4.90625 L 0.609375 -4.90625 L 0.578125 -3.625 L 0.78125 -3.625 C 0.875 -4.421875 1.03125 -4.578125 1.6875 -4.578125 L 3.203125 -4.578125 L 0.296875 -0.15625 L 0.296875 0 L 4.40625 0 Z M 4.5625 -1.46875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-51"> +<path style="stroke:none;" d="M 1.15625 -4.71875 C 1.890625 -5.078125 2.375 -5.75 2.375 -6.40625 C 2.375 -6.953125 2 -7.375 1.5 -7.375 C 1.125 -7.375 0.859375 -7.125 0.859375 -6.75 C 0.859375 -6.40625 1.109375 -6.1875 1.5 -6.1875 C 1.578125 -6.1875 1.640625 -6.203125 1.71875 -6.21875 C 1.78125 -6.234375 1.78125 -6.234375 1.796875 -6.234375 C 1.890625 -6.234375 1.953125 -6.171875 1.953125 -6.078125 C 1.953125 -5.71875 1.640625 -5.328125 1.0625 -4.921875 Z M 1.15625 -4.71875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-52"> +<path style="stroke:none;" d="M 0.34375 -4.546875 L 1.078125 -4.546875 L 1.078125 -1.03125 C 1.078125 -0.328125 0.953125 -0.1875 0.34375 -0.15625 L 0.34375 0 L 2.75 0 L 2.75 -0.15625 C 2.140625 -0.203125 2 -0.359375 2 -0.953125 L 2 -4.546875 L 2.9375 -4.546875 C 3.984375 -4.546875 4.03125 -4.515625 4.03125 -3.9375 L 4.03125 -1.0625 C 4.03125 -0.3125 3.953125 -0.21875 3.28125 -0.15625 L 3.28125 0 L 5.6875 0 L 5.6875 -0.15625 C 5.078125 -0.21875 4.953125 -0.359375 4.953125 -1.0625 L 4.953125 -3.921875 C 4.953125 -4.15625 4.953125 -4.40625 4.96875 -4.984375 L 4.921875 -5.015625 C 4.453125 -4.9375 4.140625 -4.90625 3.703125 -4.90625 L 1.984375 -4.90625 L 1.984375 -5.375 C 1.984375 -5.859375 2.015625 -6.1875 2.078125 -6.375 C 2.21875 -6.84375 2.65625 -7.171875 3.140625 -7.171875 C 3.453125 -7.171875 3.65625 -7.03125 3.921875 -6.65625 C 4.125 -6.375 4.265625 -6.265625 4.453125 -6.265625 C 4.671875 -6.265625 4.84375 -6.453125 4.84375 -6.6875 C 4.84375 -7.140625 4.296875 -7.453125 3.453125 -7.453125 C 2.625 -7.453125 2 -7.171875 1.609375 -6.625 C 1.28125 -6.1875 1.15625 -5.78125 1.09375 -4.90625 L 0.34375 -4.90625 Z M 0.34375 -4.546875 "/> +</symbol> +<symbol overflow="visible" id="glyph7-53"> +<path style="stroke:none;" d="M 2.765625 -6.765625 L 2.765625 -1.3125 C 2.765625 -0.375 2.65625 -0.265625 1.75 -0.203125 L 1.75 0 L 4.921875 0 L 4.921875 -0.203125 C 4.03125 -0.25 3.875 -0.390625 3.875 -1.1875 L 3.875 -6.765625 L 4.46875 -6.765625 C 5.71875 -6.765625 5.953125 -6.5625 6.203125 -5.359375 L 6.46875 -5.359375 L 6.40625 -7.21875 L 0.25 -7.21875 L 0.1875 -5.359375 L 0.453125 -5.359375 C 0.703125 -6.546875 0.953125 -6.765625 2.1875 -6.765625 Z M 2.765625 -6.765625 "/> +</symbol> +<symbol overflow="visible" id="glyph8-0"> +<path style="stroke:none;" d="M 1.8125 -2.671875 L 3.171875 -2.671875 C 4.4375 -2.671875 5.234375 -3.65625 5.234375 -4.671875 C 5.234375 -5.6875 4.421875 -6.65625 3.171875 -6.65625 L 0.734375 -6.65625 C 0.5625 -6.65625 0.28125 -6.65625 0.28125 -6.34375 C 0.28125 -6 0.5625 -6 0.734375 -6 L 1.0625 -6 L 1.0625 -0.671875 L 0.734375 -0.671875 C 0.5625 -0.671875 0.28125 -0.671875 0.28125 -0.328125 C 0.28125 0 0.5625 0 0.734375 0 L 2.140625 0 C 2.296875 0 2.578125 0 2.578125 -0.328125 C 2.578125 -0.671875 2.296875 -0.671875 2.140625 -0.671875 L 1.8125 -0.671875 Z M 1.8125 -6 L 2.96875 -6 C 4.015625 -6 4.484375 -5.25 4.484375 -4.671875 C 4.484375 -4.09375 4.015625 -3.34375 2.96875 -3.34375 L 1.8125 -3.34375 Z M 1.8125 -6 "/> +</symbol> +<symbol overflow="visible" id="glyph8-1"> +<path style="stroke:none;" d="M 5.09375 -2.359375 C 5.09375 -3.71875 4.078125 -4.796875 2.859375 -4.796875 C 1.640625 -4.796875 0.625 -3.71875 0.625 -2.359375 C 0.625 -0.96875 1.65625 0.0625 2.859375 0.0625 C 4.0625 0.0625 5.09375 -0.984375 5.09375 -2.359375 Z M 2.859375 -0.59375 C 2.046875 -0.59375 1.375 -1.421875 1.375 -2.4375 C 1.375 -3.421875 2.078125 -4.140625 2.859375 -4.140625 C 3.640625 -4.140625 4.34375 -3.421875 4.34375 -2.4375 C 4.34375 -1.421875 3.671875 -0.59375 2.859375 -0.59375 Z M 2.859375 -0.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph8-2"> +<path style="stroke:none;" d="M 3.25 -2.78125 C 3 -2.828125 2.78125 -2.859375 2.515625 -2.90625 C 2.1875 -2.953125 1.453125 -3.09375 1.453125 -3.515625 C 1.453125 -3.796875 1.796875 -4.140625 2.828125 -4.140625 C 3.734375 -4.140625 3.890625 -3.8125 3.921875 -3.515625 C 3.9375 -3.34375 3.953125 -3.15625 4.296875 -3.15625 C 4.671875 -3.15625 4.671875 -3.375 4.671875 -3.59375 L 4.671875 -4.359375 C 4.671875 -4.53125 4.671875 -4.796875 4.359375 -4.796875 C 4.09375 -4.796875 4.046875 -4.640625 4.03125 -4.5625 C 3.546875 -4.796875 3.0625 -4.796875 2.859375 -4.796875 C 1.03125 -4.796875 0.78125 -3.90625 0.78125 -3.515625 C 0.78125 -2.515625 1.9375 -2.328125 2.9375 -2.171875 C 3.46875 -2.078125 4.34375 -1.9375 4.34375 -1.359375 C 4.34375 -0.953125 3.9375 -0.59375 2.9375 -0.59375 C 2.4375 -0.59375 1.828125 -0.71875 1.546875 -1.578125 C 1.5 -1.765625 1.453125 -1.890625 1.171875 -1.890625 C 0.78125 -1.890625 0.78125 -1.65625 0.78125 -1.4375 L 0.78125 -0.375 C 0.78125 -0.203125 0.78125 0.0625 1.109375 0.0625 C 1.203125 0.0625 1.390625 0.046875 1.515625 -0.34375 C 2.046875 0.046875 2.625 0.0625 2.9375 0.0625 C 4.65625 0.0625 5 -0.84375 5 -1.359375 C 5 -2.5 3.59375 -2.71875 3.25 -2.78125 Z M 3.25 -2.78125 "/> +</symbol> +<symbol overflow="visible" id="glyph8-3"> +<path style="stroke:none;" d="M 2.421875 -4.03125 L 4.203125 -4.03125 C 4.375 -4.03125 4.640625 -4.03125 4.640625 -4.359375 C 4.640625 -4.703125 4.390625 -4.703125 4.203125 -4.703125 L 2.421875 -4.703125 L 2.421875 -5.59375 C 2.421875 -5.796875 2.421875 -6.046875 2.046875 -6.046875 C 1.671875 -6.046875 1.671875 -5.8125 1.671875 -5.59375 L 1.671875 -4.703125 L 0.71875 -4.703125 C 0.546875 -4.703125 0.265625 -4.703125 0.265625 -4.359375 C 0.265625 -4.03125 0.546875 -4.03125 0.703125 -4.03125 L 1.671875 -4.03125 L 1.671875 -1.375 C 1.671875 -0.328125 2.40625 0.0625 3.203125 0.0625 C 4.015625 0.0625 4.890625 -0.40625 4.890625 -1.375 C 4.890625 -1.578125 4.890625 -1.796875 4.515625 -1.796875 C 4.15625 -1.796875 4.140625 -1.578125 4.140625 -1.390625 C 4.140625 -0.703125 3.515625 -0.59375 3.265625 -0.59375 C 2.421875 -0.59375 2.421875 -1.171875 2.421875 -1.4375 Z M 2.421875 -4.03125 "/> +</symbol> +<symbol overflow="visible" id="glyph8-4"> +<path style="stroke:none;" d="M 3.15625 -4.03125 C 3.03125 -3.625 2.9375 -3.3125 2.875 -3 L 2.859375 -3 C 2.75 -3.515625 1.78125 -6.34375 1.765625 -6.40625 C 1.65625 -6.65625 1.390625 -6.65625 1.234375 -6.65625 L 0.625 -6.65625 C 0.453125 -6.65625 0.1875 -6.65625 0.1875 -6.34375 C 0.1875 -6 0.421875 -6 0.765625 -6 L 0.765625 -0.671875 C 0.421875 -0.671875 0.1875 -0.671875 0.1875 -0.328125 C 0.1875 0 0.453125 0 0.625 0 L 1.515625 0 C 1.671875 0 1.953125 0 1.953125 -0.328125 C 1.953125 -0.671875 1.71875 -0.671875 1.375 -0.671875 L 1.375 -5.875 L 1.390625 -5.875 C 1.5 -5.359375 2.203125 -3.265625 2.25 -3.125 C 2.328125 -2.875 2.46875 -2.46875 2.53125 -2.390625 C 2.59375 -2.296875 2.71875 -2.21875 2.859375 -2.21875 C 3 -2.21875 3.15625 -2.3125 3.234375 -2.46875 C 3.265625 -2.53125 4.203125 -5.296875 4.328125 -5.875 L 4.34375 -5.875 L 4.34375 -0.671875 C 3.984375 -0.671875 3.765625 -0.671875 3.765625 -0.328125 C 3.765625 0 4.03125 0 4.203125 0 L 5.09375 0 C 5.25 0 5.53125 0 5.53125 -0.328125 C 5.53125 -0.671875 5.296875 -0.671875 4.953125 -0.671875 L 4.953125 -6 C 5.296875 -6 5.53125 -6 5.53125 -6.34375 C 5.53125 -6.65625 5.25 -6.65625 5.09375 -6.65625 L 4.484375 -6.65625 C 4.03125 -6.65625 3.984375 -6.515625 3.890625 -6.234375 Z M 3.15625 -4.03125 "/> +</symbol> +<symbol overflow="visible" id="glyph8-5"> +<path style="stroke:none;" d="M 4.625 -2.078125 C 4.859375 -2.078125 5.0625 -2.078125 5.0625 -2.484375 C 5.0625 -3.734375 4.359375 -4.796875 2.9375 -4.796875 C 1.640625 -4.796875 0.59375 -3.703125 0.59375 -2.359375 C 0.59375 -1.03125 1.703125 0.0625 3.109375 0.0625 C 4.546875 0.0625 5.0625 -0.921875 5.0625 -1.1875 C 5.0625 -1.5 4.75 -1.5 4.671875 -1.5 C 4.484375 -1.5 4.390625 -1.46875 4.3125 -1.25 C 4.078125 -0.703125 3.484375 -0.59375 3.1875 -0.59375 C 2.359375 -0.59375 1.546875 -1.140625 1.375 -2.078125 Z M 1.390625 -2.734375 C 1.53125 -3.53125 2.1875 -4.140625 2.9375 -4.140625 C 3.515625 -4.140625 4.1875 -3.859375 4.28125 -2.734375 Z M 1.390625 -2.734375 "/> +</symbol> +<symbol overflow="visible" id="glyph8-6"> +<path style="stroke:none;" d="M 3.984375 -0.34375 C 4.234375 -0.015625 4.75 0 5.171875 0 C 5.46875 0 5.71875 0 5.71875 -0.34375 C 5.71875 -0.671875 5.4375 -0.671875 5.28125 -0.671875 C 4.828125 -0.671875 4.71875 -0.71875 4.625 -0.75 L 4.625 -3.109375 C 4.625 -3.875 4.03125 -4.796875 2.46875 -4.796875 C 2 -4.796875 0.890625 -4.796875 0.890625 -4 C 0.890625 -3.671875 1.109375 -3.5 1.375 -3.5 C 1.53125 -3.5 1.84375 -3.59375 1.859375 -4 C 1.859375 -4.09375 1.859375 -4.09375 2.078125 -4.125 C 2.234375 -4.140625 2.375 -4.140625 2.46875 -4.140625 C 3.296875 -4.140625 3.875 -3.796875 3.875 -3.015625 C 1.9375 -2.984375 0.59375 -2.4375 0.59375 -1.390625 C 0.59375 -0.640625 1.28125 0.0625 2.40625 0.0625 C 2.796875 0.0625 3.484375 -0.015625 3.984375 -0.34375 Z M 3.875 -2.375 L 3.875 -1.46875 C 3.875 -1.203125 3.875 -0.984375 3.453125 -0.78125 C 3.046875 -0.59375 2.5625 -0.59375 2.46875 -0.59375 C 1.796875 -0.59375 1.359375 -0.96875 1.359375 -1.390625 C 1.359375 -1.9375 2.296875 -2.328125 3.875 -2.375 Z M 3.875 -2.375 "/> +</symbol> +<symbol overflow="visible" id="glyph8-7"> +<path style="stroke:none;" d="M 2.546875 -1.921875 C 1.953125 -1.921875 1.5 -2.4375 1.5 -3.015625 C 1.5 -3.625 1.96875 -4.109375 2.546875 -4.109375 C 3.125 -4.109375 3.59375 -3.59375 3.59375 -3.015625 C 3.59375 -2.40625 3.109375 -1.921875 2.546875 -1.921875 Z M 1.5625 -1.53125 C 1.59375 -1.515625 2 -1.265625 2.546875 -1.265625 C 3.546875 -1.265625 4.34375 -2.046875 4.34375 -3.015625 C 4.34375 -3.34375 4.25 -3.671875 4.0625 -3.96875 C 4.28125 -4.09375 4.546875 -4.140625 4.6875 -4.15625 C 4.75 -3.859375 5 -3.78125 5.109375 -3.78125 C 5.296875 -3.78125 5.546875 -3.921875 5.546875 -4.234375 C 5.546875 -4.484375 5.34375 -4.828125 4.75 -4.828125 C 4.640625 -4.828125 4.09375 -4.8125 3.59375 -4.4375 C 3.421875 -4.5625 3.046875 -4.765625 2.546875 -4.765625 C 1.515625 -4.765625 0.734375 -3.953125 0.734375 -3.015625 C 0.734375 -2.546875 0.921875 -2.1875 1.09375 -1.984375 C 0.96875 -1.8125 0.875 -1.578125 0.875 -1.25 C 0.875 -0.859375 1.03125 -0.59375 1.125 -0.453125 C 0.3125 0.03125 0.3125 0.78125 0.3125 0.890625 C 0.3125 1.828125 1.46875 2.5 2.859375 2.5 C 4.25 2.5 5.40625 1.828125 5.40625 0.890625 C 5.40625 0.484375 5.203125 -0.046875 4.640625 -0.34375 C 4.5 -0.421875 4.046875 -0.671875 3.0625 -0.671875 L 2.296875 -0.671875 C 2.21875 -0.671875 2.078125 -0.671875 1.984375 -0.6875 C 1.828125 -0.6875 1.75 -0.6875 1.625 -0.84375 C 1.5 -1 1.5 -1.203125 1.5 -1.234375 C 1.5 -1.28125 1.515625 -1.421875 1.5625 -1.53125 Z M 2.859375 1.84375 C 1.765625 1.84375 0.953125 1.375 0.953125 0.890625 C 0.953125 0.703125 1.046875 0.34375 1.390625 0.125 C 1.671875 -0.046875 1.765625 -0.046875 2.5625 -0.046875 C 3.53125 -0.046875 4.765625 -0.046875 4.765625 0.890625 C 4.765625 1.375 3.953125 1.84375 2.859375 1.84375 Z M 2.859375 1.84375 "/> +</symbol> +<symbol overflow="visible" id="glyph9-0"> +<path style="stroke:none;" d="M 3.90625 -1.1875 C 3.125 -0.546875 2.78125 -0.375 2.3125 -0.375 C 1.6875 -0.375 1.28125 -0.765625 1.28125 -1.359375 C 1.28125 -1.515625 1.3125 -1.671875 1.390625 -2.03125 L 1.703125 -2.078125 C 3.34375 -2.296875 4.5 -3.125 4.5 -4.0625 C 4.5 -4.53125 4.171875 -4.8125 3.625 -4.8125 C 2.046875 -4.8125 0.34375 -3.015625 0.34375 -1.375 C 0.34375 -0.484375 0.921875 0.125 1.78125 0.125 C 2.546875 0.125 3.390625 -0.328125 4.03125 -1.0625 Z M 1.65625 -2.75 C 2.03125 -3.71875 2.828125 -4.5625 3.390625 -4.5625 C 3.625 -4.5625 3.78125 -4.390625 3.78125 -4.140625 C 3.78125 -3.796875 3.5625 -3.40625 3.234375 -3.078125 C 2.828125 -2.6875 2.40625 -2.5 1.46875 -2.265625 Z M 1.65625 -2.75 "/> +</symbol> +<symbol overflow="visible" id="glyph9-1"> +<path style="stroke:none;" d="M 4.390625 -1.203125 C 4.296875 -1.109375 4.25 -1.046875 4.140625 -0.921875 C 3.890625 -0.59375 3.765625 -0.484375 3.640625 -0.484375 C 3.484375 -0.484375 3.375 -0.625 3.296875 -0.921875 C 3.28125 -1.015625 3.265625 -1.078125 3.265625 -1.109375 C 2.984375 -2.21875 2.875 -2.71875 2.875 -2.875 C 3.34375 -3.71875 3.734375 -4.203125 3.9375 -4.203125 C 4 -4.203125 4.09375 -4.171875 4.203125 -4.109375 C 4.328125 -4.03125 4.40625 -4.015625 4.5 -4.015625 C 4.71875 -4.015625 4.875 -4.171875 4.875 -4.40625 C 4.875 -4.640625 4.6875 -4.8125 4.421875 -4.8125 C 3.953125 -4.8125 3.53125 -4.421875 2.78125 -3.25 L 2.65625 -3.84375 C 2.515625 -4.59375 2.390625 -4.8125 2.09375 -4.8125 C 1.859375 -4.8125 1.484375 -4.71875 0.8125 -4.5 L 0.703125 -4.453125 L 0.734375 -4.28125 C 1.15625 -4.390625 1.25 -4.40625 1.359375 -4.40625 C 1.625 -4.40625 1.6875 -4.3125 1.84375 -3.65625 L 2.15625 -2.3125 L 1.265625 -1.03125 C 1.046875 -0.703125 0.828125 -0.515625 0.703125 -0.515625 C 0.640625 -0.515625 0.53125 -0.546875 0.421875 -0.609375 C 0.28125 -0.6875 0.15625 -0.71875 0.078125 -0.71875 C -0.125 -0.71875 -0.296875 -0.5625 -0.296875 -0.34375 C -0.296875 -0.046875 -0.078125 0.125 0.25 0.125 C 0.59375 0.125 0.71875 0.015625 1.265625 -0.640625 C 1.5625 -1 1.78125 -1.28125 2.25 -1.921875 L 2.578125 -0.609375 C 2.71875 -0.046875 2.859375 0.125 3.203125 0.125 C 3.625 0.125 3.90625 -0.140625 4.53125 -1.125 Z M 4.390625 -1.203125 "/> +</symbol> +<symbol overflow="visible" id="glyph9-2"> +<path style="stroke:none;" d="M 3.8125 -1.15625 C 3.21875 -0.515625 2.796875 -0.265625 2.25 -0.265625 C 1.640625 -0.265625 1.265625 -0.734375 1.265625 -1.515625 C 1.265625 -2.4375 1.640625 -3.40625 2.25 -4.0625 C 2.578125 -4.390625 3.015625 -4.578125 3.4375 -4.578125 C 3.6875 -4.578125 3.84375 -4.5 3.84375 -4.359375 C 3.84375 -4.3125 3.828125 -4.25 3.78125 -4.15625 C 3.703125 -4.015625 3.6875 -3.9375 3.6875 -3.84375 C 3.6875 -3.578125 3.84375 -3.421875 4.109375 -3.421875 C 4.40625 -3.421875 4.640625 -3.640625 4.640625 -3.921875 C 4.640625 -4.421875 4.140625 -4.8125 3.484375 -4.8125 C 1.859375 -4.8125 0.328125 -3.21875 0.328125 -1.515625 C 0.328125 -0.484375 0.921875 0.125 1.9375 0.125 C 2.734375 0.125 3.34375 -0.21875 3.984375 -1.046875 Z M 3.8125 -1.15625 "/> +</symbol> +<symbol overflow="visible" id="glyph9-3"> +<path style="stroke:none;" d="M 2.484375 -1.34375 C 2.390625 -1.21875 2.296875 -1.09375 2.1875 -0.96875 C 1.84375 -0.515625 1.640625 -0.34375 1.4375 -0.34375 C 1.328125 -0.34375 1.28125 -0.421875 1.28125 -0.546875 C 1.28125 -0.625 1.3125 -0.75 1.359375 -0.96875 C 1.375 -1 1.390625 -1.0625 1.390625 -1.078125 L 3.046875 -7.390625 L 2.984375 -7.453125 C 2.34375 -7.3125 1.9375 -7.234375 1.28125 -7.15625 L 1.28125 -6.984375 C 1.8125 -6.984375 2.03125 -6.90625 2.03125 -6.734375 C 2.03125 -6.703125 2.015625 -6.625 1.984375 -6.53125 L 0.484375 -0.78125 C 0.453125 -0.65625 0.4375 -0.546875 0.4375 -0.484375 C 0.4375 -0.09375 0.625 0.125 0.96875 0.125 C 1.53125 0.125 1.921875 -0.203125 2.625 -1.25 Z M 2.484375 -1.34375 "/> +</symbol> +<symbol overflow="visible" id="glyph9-4"> +<path style="stroke:none;" d="M 5.03125 -1.28125 C 4.453125 -0.5625 4.296875 -0.421875 4.125 -0.421875 C 4.046875 -0.421875 4 -0.484375 4 -0.59375 C 4 -0.671875 4 -0.671875 4.203125 -1.453125 L 5.078125 -4.71875 L 4.265625 -4.71875 C 3.640625 -3.015625 3.5625 -2.84375 3.0625 -2.046875 C 2.40625 -1.015625 1.875 -0.453125 1.53125 -0.453125 C 1.390625 -0.453125 1.296875 -0.5625 1.296875 -0.734375 C 1.296875 -0.78125 1.296875 -0.796875 1.3125 -0.828125 L 2.296875 -4.78125 L 2.265625 -4.8125 C 1.640625 -4.671875 1.25 -4.59375 0.625 -4.515625 L 0.625 -4.359375 C 1.046875 -4.359375 1.0625 -4.359375 1.171875 -4.296875 C 1.234375 -4.28125 1.28125 -4.171875 1.28125 -4.09375 C 1.28125 -4.015625 1.234375 -3.734375 1.140625 -3.375 L 0.734375 -1.828125 C 0.53125 -1.046875 0.453125 -0.671875 0.453125 -0.484375 C 0.453125 -0.09375 0.65625 0.125 1.03125 0.125 C 1.8125 0.125 2.390625 -0.46875 3.640625 -2.578125 C 3.328125 -1.40625 3.15625 -0.65625 3.15625 -0.421875 C 3.15625 -0.09375 3.34375 0.09375 3.671875 0.09375 C 4.1875 0.09375 4.4375 -0.125 5.1875 -1.171875 Z M 5.03125 -1.28125 "/> +</symbol> +<symbol overflow="visible" id="glyph9-5"> +<path style="stroke:none;" d="M 0.390625 -1.59375 L 0.171875 0.140625 L 0.34375 0.140625 C 0.4375 -0.03125 0.5 -0.09375 0.609375 -0.09375 C 0.734375 -0.09375 0.921875 -0.046875 1.140625 0.015625 C 1.390625 0.09375 1.578125 0.125 1.75 0.125 C 2.65625 0.125 3.3125 -0.46875 3.3125 -1.296875 C 3.3125 -1.71875 3.09375 -2.1875 2.578125 -2.828125 C 2.15625 -3.34375 1.984375 -3.671875 1.984375 -3.96875 C 1.984375 -4.328125 2.21875 -4.5625 2.59375 -4.5625 C 3.15625 -4.5625 3.484375 -4.15625 3.59375 -3.296875 L 3.78125 -3.296875 L 3.984375 -4.828125 L 3.84375 -4.828125 C 3.75 -4.671875 3.671875 -4.625 3.515625 -4.625 C 3.4375 -4.625 3.328125 -4.640625 3.125 -4.703125 C 2.84375 -4.78125 2.6875 -4.8125 2.515625 -4.8125 C 1.71875 -4.8125 1.1875 -4.34375 1.1875 -3.609375 C 1.1875 -3.265625 1.421875 -2.796875 1.875 -2.203125 C 2.296875 -1.640625 2.484375 -1.265625 2.484375 -0.953125 C 2.484375 -0.453125 2.15625 -0.109375 1.671875 -0.109375 C 1.078125 -0.109375 0.734375 -0.5625 0.5625 -1.59375 Z M 0.390625 -1.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph9-6"> +<path style="stroke:none;" d="M 2.421875 -1.25 C 2.15625 -0.890625 2.078125 -0.8125 1.96875 -0.6875 C 1.78125 -0.5 1.625 -0.390625 1.53125 -0.390625 C 1.4375 -0.390625 1.359375 -0.484375 1.359375 -0.5625 C 1.359375 -0.671875 1.390625 -0.828125 1.46875 -1.03125 C 1.46875 -1.0625 1.5 -1.140625 1.515625 -1.234375 L 1.515625 -1.25 L 1.53125 -1.28125 L 2.484375 -4.78125 L 2.453125 -4.8125 C 1.359375 -4.609375 1.140625 -4.5625 0.703125 -4.53125 L 0.703125 -4.359375 C 1.28125 -4.359375 1.390625 -4.3125 1.390625 -4.09375 C 1.390625 -4.015625 1.359375 -3.84375 1.296875 -3.625 L 0.78125 -1.6875 C 0.59375 -1.0625 0.53125 -0.71875 0.53125 -0.5 C 0.53125 -0.09375 0.703125 0.125 1.03125 0.125 C 1.53125 0.125 1.9375 -0.203125 2.5625 -1.125 Z M 2.328125 -7.125 C 2.046875 -7.125 1.828125 -6.875 1.828125 -6.546875 C 1.828125 -6.203125 2.03125 -5.984375 2.34375 -5.984375 C 2.625 -5.984375 2.875 -6.234375 2.875 -6.53125 C 2.875 -6.84375 2.625 -7.125 2.328125 -7.125 Z M 2.328125 -7.125 "/> +</symbol> +<symbol overflow="visible" id="glyph9-7"> +<path style="stroke:none;" d="M 0.234375 -4.390625 C 0.34375 -4.40625 0.4375 -4.40625 0.5625 -4.40625 C 0.984375 -4.40625 1.09375 -4.21875 1.25 -3.21875 C 1.359375 -2.46875 1.484375 -0.8125 1.484375 -0.1875 C 1.484375 0.125 1.5 0.203125 1.578125 0.203125 C 1.859375 0.203125 2.875 -0.953125 3.984375 -2.515625 C 4.359375 -3.046875 4.640625 -3.765625 4.640625 -4.171875 C 4.640625 -4.515625 4.359375 -4.8125 4.03125 -4.8125 C 3.796875 -4.8125 3.625 -4.671875 3.625 -4.4375 C 3.625 -4.265625 3.703125 -4.140625 3.890625 -3.953125 C 4.03125 -3.828125 4.09375 -3.734375 4.09375 -3.625 C 4.09375 -3.125 3.375 -1.921875 2.59375 -1.109375 L 2.25 -0.765625 C 2.1875 -2.296875 2.109375 -2.875 1.96875 -3.671875 C 1.75 -4.78125 1.75 -4.8125 1.65625 -4.8125 C 1.609375 -4.8125 1.53125 -4.796875 1.4375 -4.765625 C 1.25 -4.71875 0.640625 -4.609375 0.234375 -4.53125 Z M 0.234375 -4.390625 "/> +</symbol> +<symbol overflow="visible" id="glyph9-8"> +<path style="stroke:none;" d="M 0.15625 -4.359375 C 0.3125 -4.390625 0.375 -4.40625 0.5 -4.40625 C 1.125 -4.40625 1.28125 -4.140625 1.78125 -2.25 C 1.96875 -1.546875 2.234375 -0.265625 2.234375 -0.09375 C 2.234375 0.09375 2.171875 0.265625 2 0.453125 C 1.671875 0.90625 1.453125 1.1875 1.328125 1.3125 C 1.09375 1.5625 0.96875 1.640625 0.828125 1.640625 C 0.765625 1.640625 0.6875 1.609375 0.5625 1.53125 C 0.40625 1.390625 0.28125 1.34375 0.15625 1.34375 C -0.078125 1.34375 -0.265625 1.53125 -0.265625 1.765625 C -0.265625 2.046875 -0.015625 2.25 0.296875 2.25 C 1 2.25 2.421875 0.609375 3.59375 -1.546875 C 4.34375 -2.890625 4.640625 -3.671875 4.640625 -4.203125 C 4.640625 -4.53125 4.375 -4.8125 4.046875 -4.8125 C 3.796875 -4.8125 3.625 -4.640625 3.625 -4.40625 C 3.625 -4.25 3.703125 -4.125 3.921875 -3.984375 C 4.140625 -3.859375 4.203125 -3.765625 4.203125 -3.609375 C 4.203125 -3.171875 3.8125 -2.328125 2.875 -0.78125 L 2.65625 -2.046875 C 2.5 -3.015625 1.890625 -4.8125 1.71875 -4.8125 L 1.671875 -4.8125 C 1.671875 -4.796875 1.625 -4.796875 1.578125 -4.796875 C 1.484375 -4.78125 1.09375 -4.71875 0.515625 -4.609375 C 0.453125 -4.609375 0.3125 -4.5625 0.15625 -4.546875 Z M 0.15625 -4.359375 "/> +</symbol> +<symbol overflow="visible" id="glyph9-9"> +<path style="stroke:none;" d="M 5.0625 -1.203125 C 4.890625 -1.03125 4.828125 -0.984375 4.75 -0.890625 C 4.421875 -0.5625 4.28125 -0.453125 4.1875 -0.453125 C 4.09375 -0.453125 4.03125 -0.515625 4.03125 -0.59375 C 4.03125 -0.8125 4.5 -2.671875 5 -4.546875 C 5.03125 -4.65625 5.046875 -4.671875 5.078125 -4.78125 L 5 -4.8125 L 4.328125 -4.734375 L 4.296875 -4.703125 L 4.171875 -4.171875 C 4.09375 -4.578125 3.78125 -4.8125 3.296875 -4.8125 C 1.859375 -4.8125 0.1875 -2.8125 0.1875 -1.09375 C 0.1875 -0.328125 0.59375 0.125 1.296875 0.125 C 2.0625 0.125 2.53125 -0.234375 3.484375 -1.59375 C 3.265625 -0.734375 3.234375 -0.59375 3.234375 -0.34375 C 3.234375 -0.015625 3.375 0.109375 3.671875 0.109375 C 4.109375 0.109375 4.390625 -0.09375 5.1875 -1.09375 Z M 3.375 -4.5625 C 3.734375 -4.546875 3.984375 -4.28125 3.984375 -3.90625 C 3.984375 -2.984375 3.4375 -1.703125 2.6875 -0.890625 C 2.421875 -0.59375 2.046875 -0.421875 1.734375 -0.421875 C 1.359375 -0.421875 1.09375 -0.734375 1.09375 -1.234375 C 1.09375 -1.828125 1.515625 -2.9375 1.984375 -3.625 C 2.421875 -4.25 2.9375 -4.609375 3.375 -4.5625 Z M 3.375 -4.5625 "/> +</symbol> +<symbol overflow="visible" id="glyph10-0"> +<path style="stroke:none;" d="M 2.3125 -5.390625 L 0.890625 -4.65625 L 0.890625 -4.546875 C 0.984375 -4.59375 1.0625 -4.625 1.09375 -4.640625 C 1.25 -4.6875 1.375 -4.71875 1.453125 -4.71875 C 1.625 -4.71875 1.703125 -4.609375 1.703125 -4.34375 L 1.703125 -0.734375 C 1.703125 -0.484375 1.640625 -0.296875 1.5 -0.21875 C 1.390625 -0.15625 1.28125 -0.125 0.9375 -0.125 L 0.9375 0 L 3.140625 0 L 3.140625 -0.125 C 2.515625 -0.125 2.375 -0.203125 2.375 -0.59375 L 2.375 -5.375 Z M 2.3125 -5.390625 "/> +</symbol> +<symbol overflow="visible" id="glyph11-0"> +<path style="stroke:none;" d="M 7.359375 -5.4375 C 7.5 -5.5 7.5625 -5.546875 7.5625 -5.671875 C 7.5625 -5.796875 7.46875 -5.890625 7.34375 -5.890625 C 7.3125 -5.890625 7.296875 -5.890625 7.15625 -5.8125 L 1.109375 -2.96875 C 1 -2.90625 0.90625 -2.859375 0.90625 -2.71875 C 0.90625 -2.59375 1 -2.546875 1.109375 -2.484375 L 7.15625 0.359375 C 7.296875 0.4375 7.3125 0.4375 7.34375 0.4375 C 7.46875 0.4375 7.5625 0.34375 7.5625 0.21875 C 7.5625 0.09375 7.5 0.046875 7.359375 -0.015625 L 1.640625 -2.71875 Z M 7.359375 -5.4375 "/> +</symbol> +<symbol overflow="visible" id="glyph11-1"> +<path style="stroke:none;" d="M 7.359375 -2.484375 C 7.484375 -2.546875 7.5625 -2.59375 7.5625 -2.71875 C 7.5625 -2.859375 7.484375 -2.90625 7.359375 -2.96875 L 1.3125 -5.8125 C 1.171875 -5.890625 1.15625 -5.890625 1.125 -5.890625 C 1 -5.890625 0.90625 -5.796875 0.90625 -5.671875 C 0.90625 -5.578125 0.953125 -5.5 1.109375 -5.4375 L 6.84375 -2.71875 L 1.109375 -0.015625 C 0.953125 0.046875 0.90625 0.125 0.90625 0.21875 C 0.90625 0.34375 1 0.4375 1.125 0.4375 C 1.15625 0.4375 1.171875 0.4375 1.3125 0.359375 Z M 7.359375 -2.484375 "/> +</symbol> +<symbol overflow="visible" id="glyph12-0"> +<path style="stroke:none;" d="M 1.734375 -4.046875 L 0.671875 -3.5 L 0.671875 -3.421875 C 0.734375 -3.453125 0.796875 -3.46875 0.828125 -3.484375 C 0.9375 -3.53125 1.03125 -3.546875 1.09375 -3.546875 C 1.21875 -3.546875 1.28125 -3.453125 1.28125 -3.265625 L 1.28125 -0.5625 C 1.28125 -0.359375 1.234375 -0.21875 1.125 -0.171875 C 1.046875 -0.109375 0.953125 -0.09375 0.703125 -0.09375 L 0.703125 0 L 2.359375 0 L 2.359375 -0.09375 C 1.890625 -0.09375 1.796875 -0.15625 1.796875 -0.4375 L 1.796875 -4.03125 Z M 1.734375 -4.046875 "/> +</symbol> +<symbol overflow="visible" id="glyph13-0"> +<path style="stroke:none;" d="M 1.03125 -0.984375 C 1.03125 -0.3125 0.921875 -0.203125 0.15625 -0.171875 L 0.15625 0 L 2.828125 0 L 2.828125 -0.171875 C 2.09375 -0.203125 1.953125 -0.328125 1.953125 -0.984375 L 1.953125 -4.953125 C 1.953125 -5.609375 2.078125 -5.734375 2.828125 -5.765625 L 2.828125 -5.9375 L 0.15625 -5.9375 L 0.15625 -5.765625 C 0.921875 -5.71875 1.03125 -5.625 1.03125 -4.953125 Z M 1.03125 -0.984375 "/> +</symbol> +<symbol overflow="visible" id="glyph13-1"> +<path style="stroke:none;" d="M 0.140625 -3.5625 C 0.203125 -3.59375 0.28125 -3.609375 0.390625 -3.609375 C 0.640625 -3.609375 0.71875 -3.46875 0.71875 -3.03125 L 0.71875 -0.8125 C 0.71875 -0.296875 0.625 -0.171875 0.15625 -0.140625 L 0.15625 0 L 2.0625 0 L 2.0625 -0.140625 C 1.609375 -0.171875 1.46875 -0.28125 1.46875 -0.59375 L 1.46875 -3.125 C 1.90625 -3.53125 2.09375 -3.625 2.390625 -3.625 C 2.828125 -3.625 3.046875 -3.359375 3.046875 -2.765625 L 3.046875 -0.890625 C 3.046875 -0.328125 2.9375 -0.171875 2.484375 -0.140625 L 2.484375 0 L 4.34375 0 L 4.34375 -0.140625 C 3.90625 -0.171875 3.796875 -0.28125 3.796875 -0.71875 L 3.796875 -2.78125 C 3.796875 -3.625 3.40625 -4.125 2.75 -4.125 C 2.328125 -4.125 2.046875 -3.96875 1.4375 -3.40625 L 1.4375 -4.109375 L 1.375 -4.125 C 0.9375 -3.96875 0.640625 -3.859375 0.140625 -3.71875 Z M 0.140625 -3.5625 "/> +</symbol> +<symbol overflow="visible" id="glyph13-2"> +<path style="stroke:none;" d="M 2.28125 -4.03125 L 1.375 -4.03125 L 1.375 -5.078125 C 1.375 -5.171875 1.375 -5.1875 1.3125 -5.1875 C 1.25 -5.109375 1.203125 -5.03125 1.140625 -4.9375 C 0.796875 -4.453125 0.40625 -4.015625 0.265625 -3.984375 C 0.171875 -3.921875 0.109375 -3.859375 0.109375 -3.8125 C 0.109375 -3.78125 0.125 -3.765625 0.15625 -3.75 L 0.625 -3.75 L 0.625 -1.046875 C 0.625 -0.296875 0.890625 0.09375 1.421875 0.09375 C 1.859375 0.09375 2.203125 -0.125 2.5 -0.59375 L 2.390625 -0.6875 C 2.203125 -0.46875 2.046875 -0.375 1.84375 -0.375 C 1.515625 -0.375 1.375 -0.625 1.375 -1.1875 L 1.375 -3.75 L 2.28125 -3.75 Z M 2.28125 -4.03125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-3"> +<path style="stroke:none;" d="M 1.40625 -3.078125 C 1.78125 -3.484375 2.046875 -3.640625 2.40625 -3.640625 C 2.859375 -3.640625 3.078125 -3.3125 3.078125 -2.6875 L 3.078125 -0.921875 C 3.078125 -0.3125 2.984375 -0.1875 2.46875 -0.140625 L 2.46875 0 L 4.375 0 L 4.375 -0.140625 C 3.890625 -0.21875 3.828125 -0.296875 3.828125 -0.921875 L 3.828125 -2.703125 C 3.828125 -3.640625 3.453125 -4.125 2.71875 -4.125 C 2.203125 -4.125 1.828125 -3.90625 1.40625 -3.375 L 1.40625 -6.09375 L 1.359375 -6.125 C 1.0625 -6.015625 0.828125 -5.953125 0.328125 -5.796875 L 0.09375 -5.734375 L 0.09375 -5.59375 C 0.125 -5.59375 0.15625 -5.59375 0.203125 -5.59375 C 0.578125 -5.59375 0.65625 -5.53125 0.65625 -5.140625 L 0.65625 -0.921875 C 0.65625 -0.28125 0.59375 -0.203125 0.078125 -0.140625 L 0.078125 0 L 2.015625 0 L 2.015625 -0.140625 C 1.5 -0.1875 1.40625 -0.296875 1.40625 -0.921875 Z M 1.40625 -3.078125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-4"> +<path style="stroke:none;" d="M 1.5625 -4.125 L 0.171875 -3.625 L 0.171875 -3.5 L 0.25 -3.5 C 0.359375 -3.53125 0.46875 -3.53125 0.5625 -3.53125 C 0.765625 -3.53125 0.859375 -3.390625 0.859375 -3 L 0.859375 -0.921875 C 0.859375 -0.265625 0.765625 -0.171875 0.140625 -0.140625 L 0.140625 0 L 2.265625 0 L 2.265625 -0.140625 C 1.671875 -0.171875 1.609375 -0.265625 1.609375 -0.921875 L 1.609375 -4.09375 Z M 1.140625 -6.125 C 0.90625 -6.125 0.703125 -5.921875 0.703125 -5.671875 C 0.703125 -5.421875 0.890625 -5.203125 1.140625 -5.203125 C 1.40625 -5.203125 1.609375 -5.40625 1.609375 -5.671875 C 1.609375 -5.921875 1.40625 -6.125 1.140625 -6.125 Z M 1.140625 -6.125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-5"> +<path style="stroke:none;" d="M 2.828125 -2.8125 L 2.796875 -4.03125 L 2.6875 -4.03125 L 2.671875 -4.015625 C 2.59375 -3.953125 2.578125 -3.953125 2.546875 -3.953125 C 2.5 -3.953125 2.40625 -3.96875 2.3125 -4.015625 C 2.109375 -4.078125 1.90625 -4.109375 1.671875 -4.109375 C 0.96875 -4.109375 0.453125 -3.65625 0.453125 -3.015625 C 0.453125 -2.515625 0.75 -2.15625 1.5 -1.71875 L 2.03125 -1.421875 C 2.34375 -1.25 2.5 -1.03125 2.5 -0.75 C 2.5 -0.359375 2.203125 -0.109375 1.75 -0.109375 C 1.4375 -0.109375 1.171875 -0.21875 1 -0.421875 C 0.8125 -0.640625 0.71875 -0.859375 0.609375 -1.359375 L 0.46875 -1.359375 L 0.46875 0.03125 L 0.578125 0.03125 C 0.640625 -0.046875 0.6875 -0.078125 0.796875 -0.078125 C 0.875 -0.078125 1 -0.046875 1.203125 0 C 1.453125 0.046875 1.6875 0.09375 1.859375 0.09375 C 2.546875 0.09375 3.125 -0.4375 3.125 -1.0625 C 3.125 -1.5 2.90625 -1.796875 2.375 -2.125 L 1.40625 -2.703125 C 1.140625 -2.84375 1.015625 -3.0625 1.015625 -3.3125 C 1.015625 -3.671875 1.296875 -3.921875 1.703125 -3.921875 C 2.21875 -3.921875 2.484375 -3.609375 2.6875 -2.8125 Z M 2.828125 -2.8125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-6"> +<path style="stroke:none;" d="M 0.078125 -3.53125 C 0.15625 -3.53125 0.21875 -3.53125 0.3125 -3.53125 C 0.609375 -3.53125 0.671875 -3.4375 0.671875 -3.015625 L 0.671875 1.171875 C 0.671875 1.640625 0.578125 1.734375 0.046875 1.796875 L 0.046875 1.953125 L 2.21875 1.953125 L 2.21875 1.78125 C 1.546875 1.78125 1.421875 1.671875 1.421875 1.109375 L 1.421875 -0.296875 C 1.734375 0 1.953125 0.09375 2.328125 0.09375 C 3.390625 0.09375 4.21875 -0.921875 4.21875 -2.21875 C 4.21875 -3.328125 3.59375 -4.125 2.71875 -4.125 C 2.21875 -4.125 1.828125 -3.90625 1.421875 -3.421875 L 1.421875 -4.109375 L 1.375 -4.125 C 0.890625 -3.9375 0.578125 -3.828125 0.078125 -3.671875 Z M 1.421875 -3 C 1.421875 -3.265625 1.921875 -3.59375 2.34375 -3.59375 C 3 -3.59375 3.4375 -2.90625 3.4375 -1.859375 C 3.4375 -0.875 3 -0.203125 2.359375 -0.203125 C 1.9375 -0.203125 1.421875 -0.515625 1.421875 -0.796875 Z M 1.421875 -3 "/> +</symbol> +<symbol overflow="visible" id="glyph13-7"> +<path style="stroke:none;" d="M 3.96875 -0.59375 C 3.8125 -0.46875 3.703125 -0.421875 3.5625 -0.421875 C 3.359375 -0.421875 3.296875 -0.546875 3.296875 -0.9375 L 3.296875 -2.6875 C 3.296875 -3.15625 3.25 -3.421875 3.125 -3.625 C 2.921875 -3.953125 2.53125 -4.125 2.015625 -4.125 C 1.171875 -4.125 0.5 -3.6875 0.5 -3.125 C 0.5 -2.921875 0.6875 -2.734375 0.890625 -2.734375 C 1.109375 -2.734375 1.296875 -2.921875 1.296875 -3.109375 C 1.296875 -3.140625 1.28125 -3.1875 1.28125 -3.25 C 1.25 -3.34375 1.25 -3.40625 1.25 -3.46875 C 1.25 -3.71875 1.53125 -3.90625 1.890625 -3.90625 C 2.328125 -3.90625 2.578125 -3.65625 2.578125 -3.171875 L 2.578125 -2.625 C 1.1875 -2.0625 1.046875 -1.984375 0.65625 -1.65625 C 0.453125 -1.46875 0.328125 -1.171875 0.328125 -0.875 C 0.328125 -0.3125 0.71875 0.09375 1.28125 0.09375 C 1.671875 0.09375 2.03125 -0.09375 2.578125 -0.5625 C 2.625 -0.09375 2.796875 0.09375 3.15625 0.09375 C 3.46875 0.09375 3.65625 -0.015625 3.96875 -0.359375 Z M 2.578125 -1.109375 C 2.578125 -0.828125 2.53125 -0.75 2.34375 -0.640625 C 2.125 -0.515625 1.875 -0.4375 1.6875 -0.4375 C 1.375 -0.4375 1.125 -0.734375 1.125 -1.125 L 1.125 -1.15625 C 1.125 -1.6875 1.484375 -2.015625 2.578125 -2.40625 Z M 2.578125 -1.109375 "/> +</symbol> +<symbol overflow="visible" id="glyph13-8"> +<path style="stroke:none;" d="M 3.65625 -1.46875 C 3.234375 -0.796875 2.84375 -0.53125 2.265625 -0.53125 C 1.765625 -0.53125 1.375 -0.796875 1.109375 -1.296875 C 0.953125 -1.640625 0.890625 -1.9375 0.875 -2.484375 L 3.625 -2.484375 C 3.5625 -3.0625 3.46875 -3.328125 3.25 -3.609375 C 2.984375 -3.9375 2.5625 -4.125 2.09375 -4.125 C 1.65625 -4.125 1.234375 -3.96875 0.890625 -3.65625 C 0.46875 -3.296875 0.21875 -2.65625 0.21875 -1.921875 C 0.21875 -0.6875 0.875 0.09375 1.90625 0.09375 C 2.75 0.09375 3.421875 -0.4375 3.796875 -1.40625 Z M 0.890625 -2.765625 C 0.984375 -3.46875 1.296875 -3.796875 1.84375 -3.796875 C 2.390625 -3.796875 2.59375 -3.546875 2.71875 -2.765625 Z M 0.890625 -2.765625 "/> +</symbol> +<symbol overflow="visible" id="glyph13-9"> +<path style="stroke:none;" d="M 0.0625 -3.5 C 0.1875 -3.53125 0.265625 -3.53125 0.375 -3.53125 C 0.59375 -3.53125 0.6875 -3.390625 0.6875 -3 L 0.6875 -0.75 C 0.6875 -0.3125 0.625 -0.234375 0.046875 -0.140625 L 0.046875 0 L 2.203125 0 L 2.203125 -0.140625 C 1.59375 -0.15625 1.4375 -0.296875 1.4375 -0.8125 L 1.4375 -2.828125 C 1.4375 -3.109375 1.828125 -3.5625 2.0625 -3.5625 C 2.109375 -3.5625 2.203125 -3.515625 2.296875 -3.421875 C 2.4375 -3.296875 2.53125 -3.25 2.65625 -3.25 C 2.875 -3.25 3 -3.40625 3 -3.65625 C 3 -3.953125 2.8125 -4.125 2.515625 -4.125 C 2.140625 -4.125 1.875 -3.921875 1.4375 -3.28125 L 1.4375 -4.109375 L 1.390625 -4.125 C 0.921875 -3.921875 0.59375 -3.8125 0.0625 -3.640625 Z M 0.0625 -3.5 "/> +</symbol> +<symbol overflow="visible" id="glyph13-10"> +<path style="stroke:none;" d="M 0.75 1.265625 C 1.34375 0.96875 1.75 0.421875 1.75 -0.109375 C 1.75 -0.5625 1.4375 -0.921875 1.03125 -0.921875 C 0.71875 -0.921875 0.5 -0.703125 0.5 -0.40625 C 0.5 -0.109375 0.703125 0.046875 1.03125 0.046875 C 1.078125 0.046875 1.140625 0.046875 1.203125 0.03125 C 1.265625 0.015625 1.265625 0.015625 1.28125 0.015625 C 1.34375 0.015625 1.40625 0.078125 1.40625 0.140625 C 1.40625 0.4375 1.140625 0.765625 0.65625 1.09375 Z M 0.75 1.265625 "/> +</symbol> +<symbol overflow="visible" id="glyph13-11"> +<path style="stroke:none;" d="M 5.125 -4.03125 L 5.125 -3.90625 C 5.421875 -3.84375 5.515625 -3.78125 5.515625 -3.625 C 5.515625 -3.484375 5.46875 -3.28125 5.359375 -3.03125 L 4.5625 -1.046875 L 3.796875 -3.046875 C 3.65625 -3.46875 3.65625 -3.46875 3.65625 -3.578125 C 3.65625 -3.78125 3.75 -3.84375 4.171875 -3.90625 L 4.171875 -4.03125 L 2.34375 -4.03125 L 2.34375 -3.90625 C 2.6875 -3.859375 2.796875 -3.765625 2.96875 -3.28125 C 3.03125 -3.109375 3.09375 -2.9375 3.140625 -2.78125 L 2.328125 -1 L 1.4375 -3.34375 C 1.40625 -3.421875 1.390625 -3.515625 1.390625 -3.609375 C 1.390625 -3.796875 1.5 -3.859375 1.796875 -3.90625 L 1.796875 -4.03125 L 0.1875 -4.03125 L 0.1875 -3.90625 C 0.390625 -3.890625 0.46875 -3.796875 0.65625 -3.34375 L 1.875 -0.265625 C 1.984375 0 2.046875 0.125 2.109375 0.125 C 2.15625 0.125 2.21875 0.015625 2.328125 -0.21875 L 3.34375 -2.375 L 4.15625 -0.265625 C 4.28125 0.078125 4.3125 0.125 4.375 0.125 C 4.4375 0.125 4.46875 0.046875 4.625 -0.3125 L 5.859375 -3.421875 C 6.015625 -3.796875 6.046875 -3.84375 6.21875 -3.90625 L 6.21875 -4.03125 Z M 5.125 -4.03125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-12"> +<path style="stroke:none;" d="M 4.296875 -0.453125 L 4.25 -0.453125 C 3.84375 -0.453125 3.734375 -0.546875 3.734375 -0.953125 L 3.734375 -4.03125 L 2.328125 -4.03125 L 2.328125 -3.890625 C 2.875 -3.859375 2.984375 -3.765625 2.984375 -3.3125 L 2.984375 -1.203125 C 2.984375 -0.953125 2.9375 -0.828125 2.8125 -0.734375 C 2.578125 -0.53125 2.296875 -0.4375 2.03125 -0.4375 C 1.671875 -0.4375 1.390625 -0.734375 1.390625 -1.109375 L 1.390625 -4.03125 L 0.078125 -4.03125 L 0.078125 -3.90625 C 0.515625 -3.890625 0.640625 -3.75 0.640625 -3.34375 L 0.640625 -1.078125 C 0.640625 -0.375 1.0625 0.09375 1.71875 0.09375 C 2.046875 0.09375 2.40625 -0.046875 2.640625 -0.296875 L 3.03125 -0.6875 L 3.03125 0.0625 L 3.0625 0.078125 C 3.515625 -0.09375 3.84375 -0.203125 4.296875 -0.328125 Z M 4.296875 -0.453125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-13"> +<path style="stroke:none;" d="M 1.390625 -6.0625 C 0.796875 -5.765625 0.390625 -5.21875 0.390625 -4.6875 C 0.390625 -4.21875 0.703125 -3.890625 1.109375 -3.890625 C 1.421875 -3.890625 1.625 -4.09375 1.625 -4.390625 C 1.625 -4.6875 1.421875 -4.859375 1.109375 -4.859375 C 1.046875 -4.859375 0.984375 -4.84375 0.9375 -4.828125 C 0.875 -4.8125 0.875 -4.8125 0.859375 -4.8125 C 0.796875 -4.8125 0.734375 -4.875 0.734375 -4.9375 C 0.734375 -5.234375 0.984375 -5.5625 1.46875 -5.890625 Z M 3.46875 -6.0625 C 2.875 -5.765625 2.46875 -5.21875 2.46875 -4.6875 C 2.46875 -4.21875 2.78125 -3.890625 3.1875 -3.890625 C 3.5 -3.890625 3.71875 -4.09375 3.71875 -4.390625 C 3.71875 -4.6875 3.5 -4.859375 3.1875 -4.859375 C 3.125 -4.859375 3.0625 -4.84375 3.015625 -4.828125 C 2.953125 -4.8125 2.953125 -4.8125 2.9375 -4.8125 C 2.875 -4.8125 2.8125 -4.875 2.8125 -4.9375 C 2.8125 -5.234375 3.0625 -5.5625 3.546875 -5.890625 Z M 3.46875 -6.0625 "/> +</symbol> +<symbol overflow="visible" id="glyph13-14"> +<path style="stroke:none;" d="M 3.5625 -1.40625 C 3.140625 -0.765625 2.8125 -0.5625 2.3125 -0.5625 C 1.484375 -0.5625 0.921875 -1.28125 0.921875 -2.3125 C 0.921875 -3.234375 1.40625 -3.859375 2.140625 -3.859375 C 2.453125 -3.859375 2.578125 -3.765625 2.65625 -3.4375 L 2.71875 -3.234375 C 2.796875 -2.984375 2.953125 -2.828125 3.140625 -2.828125 C 3.375 -2.828125 3.5625 -3 3.5625 -3.203125 C 3.5625 -3.703125 2.9375 -4.125 2.1875 -4.125 C 1.75 -4.125 1.296875 -3.953125 0.921875 -3.625 C 0.46875 -3.234375 0.21875 -2.625 0.21875 -1.90625 C 0.21875 -0.75 0.9375 0.09375 1.921875 0.09375 C 2.328125 0.09375 2.6875 -0.046875 3.015625 -0.328125 C 3.25 -0.546875 3.421875 -0.796875 3.6875 -1.3125 Z M 3.5625 -1.40625 "/> +</symbol> +<symbol overflow="visible" id="glyph13-15"> +<path style="stroke:none;" d="M 0.171875 -5.59375 L 0.21875 -5.59375 C 0.328125 -5.59375 0.4375 -5.609375 0.5 -5.609375 C 0.796875 -5.609375 0.875 -5.484375 0.875 -5.0625 L 0.875 -0.78125 C 0.875 -0.296875 0.75 -0.171875 0.1875 -0.140625 L 0.1875 0 L 2.3125 0 L 2.3125 -0.140625 C 1.734375 -0.171875 1.625 -0.265625 1.625 -0.75 L 1.625 -6.109375 L 1.59375 -6.125 C 1.125 -5.96875 0.796875 -5.890625 0.171875 -5.734375 Z M 0.171875 -5.59375 "/> +</symbol> +<symbol overflow="visible" id="glyph13-16"> +<path style="stroke:none;" d="M 0.515625 -3.890625 C 1.109375 -4.171875 1.515625 -4.734375 1.515625 -5.265625 C 1.515625 -5.71875 1.203125 -6.0625 0.796875 -6.0625 C 0.484375 -6.0625 0.265625 -5.859375 0.265625 -5.546875 C 0.265625 -5.265625 0.46875 -5.09375 0.796875 -5.09375 C 0.859375 -5.09375 0.921875 -5.109375 0.96875 -5.109375 C 1.03125 -5.125 1.03125 -5.125 1.046875 -5.125 C 1.109375 -5.125 1.171875 -5.078125 1.171875 -5 C 1.171875 -4.71875 0.921875 -4.390625 0.4375 -4.046875 Z M 2.59375 -3.890625 C 3.1875 -4.171875 3.59375 -4.734375 3.59375 -5.265625 C 3.59375 -5.71875 3.28125 -6.0625 2.875 -6.0625 C 2.5625 -6.0625 2.34375 -5.859375 2.34375 -5.546875 C 2.34375 -5.265625 2.546875 -5.09375 2.875 -5.09375 C 2.9375 -5.09375 3 -5.109375 3.046875 -5.109375 C 3.109375 -5.125 3.109375 -5.125 3.125 -5.125 C 3.1875 -5.125 3.25 -5.078125 3.25 -5 C 3.25 -4.71875 3 -4.390625 2.515625 -4.046875 Z M 2.59375 -3.890625 "/> +</symbol> +<symbol overflow="visible" id="glyph13-17"> +<path style="stroke:none;" d="M 3.078125 0.09375 L 4.40625 -0.375 L 4.40625 -0.515625 C 4.25 -0.515625 4.21875 -0.515625 4.203125 -0.515625 C 3.875 -0.515625 3.796875 -0.609375 3.796875 -1.015625 L 3.796875 -6.109375 L 3.765625 -6.125 C 3.328125 -5.96875 3.015625 -5.890625 2.4375 -5.734375 L 2.4375 -5.59375 C 2.515625 -5.59375 2.5625 -5.59375 2.640625 -5.59375 C 2.96875 -5.59375 3.046875 -5.5 3.046875 -5.140625 L 3.046875 -3.734375 C 2.703125 -4.03125 2.46875 -4.125 2.109375 -4.125 C 1.078125 -4.125 0.234375 -3.109375 0.234375 -1.84375 C 0.234375 -0.6875 0.921875 0.09375 1.90625 0.09375 C 2.40625 0.09375 2.75 -0.09375 3.046875 -0.515625 L 3.046875 0.0625 Z M 3.046875 -0.921875 C 3.046875 -0.859375 2.984375 -0.75 2.890625 -0.640625 C 2.734375 -0.46875 2.515625 -0.375 2.25 -0.375 C 1.5 -0.375 1.015625 -1.09375 1.015625 -2.203125 C 1.015625 -3.203125 1.453125 -3.875 2.140625 -3.875 C 2.609375 -3.875 3.046875 -3.453125 3.046875 -2.984375 Z M 3.046875 -0.921875 "/> +</symbol> +<symbol overflow="visible" id="glyph13-18"> +<path style="stroke:none;" d="M 2.25 -4.125 C 1.078125 -4.125 0.265625 -3.265625 0.265625 -2.03125 C 0.265625 -0.8125 1.09375 0.09375 2.21875 0.09375 C 3.359375 0.09375 4.21875 -0.859375 4.21875 -2.09375 C 4.21875 -3.28125 3.390625 -4.125 2.25 -4.125 Z M 2.125 -3.875 C 2.875 -3.875 3.40625 -3.015625 3.40625 -1.78125 C 3.40625 -0.765625 3 -0.15625 2.328125 -0.15625 C 1.984375 -0.15625 1.65625 -0.375 1.46875 -0.734375 C 1.203125 -1.203125 1.0625 -1.828125 1.0625 -2.46875 C 1.0625 -3.3125 1.484375 -3.875 2.125 -3.875 Z M 2.125 -3.875 "/> +</symbol> +<symbol overflow="visible" id="glyph13-19"> +<path style="stroke:none;" d="M 4.21875 -3.484375 L 4.21875 -3.828125 L 3.53125 -3.828125 C 3.34375 -3.828125 3.203125 -3.859375 3.03125 -3.921875 L 2.828125 -3.984375 C 2.59375 -4.078125 2.34375 -4.125 2.109375 -4.125 C 1.28125 -4.125 0.625 -3.484375 0.625 -2.65625 C 0.625 -2.09375 0.859375 -1.765625 1.453125 -1.46875 C 1.28125 -1.296875 1.125 -1.140625 1.0625 -1.109375 C 0.765625 -0.84375 0.65625 -0.65625 0.65625 -0.484375 C 0.65625 -0.296875 0.765625 -0.1875 1.125 -0.015625 C 0.5 0.453125 0.25 0.75 0.25 1.078125 C 0.25 1.5625 0.953125 1.953125 1.796875 1.953125 C 2.46875 1.953125 3.171875 1.71875 3.640625 1.34375 C 3.984375 1.0625 4.140625 0.78125 4.140625 0.4375 C 4.140625 -0.109375 3.71875 -0.5 3.046875 -0.515625 L 1.890625 -0.578125 C 1.421875 -0.59375 1.1875 -0.671875 1.1875 -0.8125 C 1.1875 -1 1.484375 -1.3125 1.734375 -1.375 C 1.8125 -1.375 1.875 -1.359375 1.90625 -1.359375 C 2.078125 -1.34375 2.1875 -1.34375 2.25 -1.34375 C 2.578125 -1.34375 2.9375 -1.46875 3.203125 -1.71875 C 3.5 -1.96875 3.640625 -2.28125 3.640625 -2.71875 C 3.640625 -2.984375 3.59375 -3.1875 3.46875 -3.484375 Z M 1.3125 0.015625 C 1.609375 0.078125 2.328125 0.140625 2.765625 0.140625 C 3.59375 0.140625 3.890625 0.25 3.890625 0.578125 C 3.890625 1.09375 3.203125 1.4375 2.1875 1.4375 C 1.40625 1.4375 0.875 1.1875 0.875 0.796875 C 0.875 0.578125 0.9375 0.46875 1.3125 0.015625 Z M 1.359375 -3.03125 C 1.359375 -3.5625 1.609375 -3.875 2.03125 -3.875 C 2.3125 -3.875 2.53125 -3.71875 2.6875 -3.453125 C 2.84375 -3.140625 2.953125 -2.71875 2.953125 -2.375 C 2.953125 -1.875 2.6875 -1.5625 2.28125 -1.5625 C 1.734375 -1.5625 1.359375 -2.140625 1.359375 -3 Z M 1.359375 -3.03125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-20"> +<path style="stroke:none;" d="M 1.375 -6.109375 L 1.328125 -6.125 C 0.953125 -5.984375 0.703125 -5.921875 0.28125 -5.796875 L 0.03125 -5.734375 L 0.03125 -5.59375 C 0.078125 -5.59375 0.109375 -5.59375 0.171875 -5.59375 C 0.53125 -5.59375 0.625 -5.515625 0.625 -5.140625 L 0.625 -0.484375 C 0.625 -0.203125 1.375 0.09375 2.09375 0.09375 C 3.28125 0.09375 4.203125 -0.890625 4.203125 -2.171875 C 4.203125 -3.28125 3.515625 -4.125 2.625 -4.125 C 2.078125 -4.125 1.546875 -3.796875 1.375 -3.359375 Z M 1.375 -2.890625 C 1.375 -3.234375 1.796875 -3.5625 2.265625 -3.5625 C 2.953125 -3.5625 3.40625 -2.859375 3.40625 -1.765625 C 3.40625 -0.765625 2.984375 -0.203125 2.25 -0.203125 C 1.78125 -0.203125 1.375 -0.40625 1.375 -0.625 Z M 1.375 -2.890625 "/> +</symbol> +<symbol overflow="visible" id="glyph13-21"> +<path style="stroke:none;" d="M 4.265625 -4.03125 L 3.046875 -4.03125 L 3.046875 -3.90625 C 3.34375 -3.90625 3.484375 -3.828125 3.484375 -3.671875 C 3.484375 -3.640625 3.46875 -3.59375 3.4375 -3.53125 L 2.578125 -1.046875 L 1.546875 -3.3125 C 1.484375 -3.4375 1.453125 -3.5625 1.453125 -3.65625 C 1.453125 -3.828125 1.59375 -3.890625 1.96875 -3.90625 L 1.96875 -4.03125 L 0.125 -4.03125 L 0.125 -3.90625 C 0.359375 -3.875 0.515625 -3.78125 0.578125 -3.625 L 1.609375 -1.421875 L 1.625 -1.34375 L 1.765625 -1.078125 C 2.015625 -0.625 2.15625 -0.3125 2.15625 -0.171875 C 2.15625 -0.03125 1.953125 0.53125 1.796875 0.796875 C 1.671875 1.03125 1.484375 1.203125 1.359375 1.203125 C 1.296875 1.203125 1.21875 1.1875 1.125 1.140625 C 0.953125 1.078125 0.8125 1.046875 0.65625 1.046875 C 0.453125 1.046875 0.265625 1.21875 0.265625 1.4375 C 0.265625 1.734375 0.5625 1.953125 0.9375 1.953125 C 1.53125 1.953125 1.96875 1.453125 2.453125 0.15625 L 3.828125 -3.5 C 3.953125 -3.78125 4.046875 -3.875 4.265625 -3.90625 Z M 4.265625 -4.03125 "/> +</symbol> +<symbol overflow="visible" id="glyph13-22"> +<path style="stroke:none;" d="M 1.125 -0.890625 C 0.859375 -0.890625 0.625 -0.65625 0.625 -0.390625 C 0.625 -0.125 0.859375 0.09375 1.109375 0.09375 C 1.390625 0.09375 1.625 -0.125 1.625 -0.390625 C 1.625 -0.65625 1.390625 -0.890625 1.125 -0.890625 Z M 1.125 -0.890625 "/> +</symbol> +</g> +</defs> +<g id="surface1"> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-0" x="78.351778" y="126.69531"/> + <use xlink:href="#glyph0-1" x="88.870032" y="126.69531"/> + <use xlink:href="#glyph0-2" x="97.477442" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-3" x="109.424526" y="126.69531"/> + <use xlink:href="#glyph0-4" x="124.7285" y="126.69531"/> + <use xlink:href="#glyph0-5" x="133.335909" y="126.69531"/> + <use xlink:href="#glyph0-6" x="138.121629" y="126.69531"/> + <use xlink:href="#glyph0-7" x="142.907349" y="126.69531"/> + <use xlink:href="#glyph0-8" x="147.693068" y="126.69531"/> + <use xlink:href="#glyph0-9" x="153.425603" y="126.69531"/> + <use xlink:href="#glyph0-10" x="162.997042" y="126.69531"/> + <use xlink:href="#glyph0-7" x="168.729577" y="126.69531"/> + <use xlink:href="#glyph0-11" x="173.515296" y="126.69531"/> + <use xlink:href="#glyph0-12" x="182.122706" y="126.69531"/> + <use xlink:href="#glyph0-7" x="189.766085" y="126.69531"/> + <use xlink:href="#glyph0-13" x="194.551805" y="126.69531"/> + <use xlink:href="#glyph0-14" x="203.159214" y="126.69531"/> + <use xlink:href="#glyph0-5" x="210.802594" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-15" x="219.892018" y="126.69531"/> + <use xlink:href="#glyph0-16" x="232.321117" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-17" x="246.196261" y="126.69531"/> + <use xlink:href="#glyph0-18" x="257.678545" y="126.69531"/> + <use xlink:href="#glyph0-11" x="266.285955" y="126.69531"/> + <use xlink:href="#glyph0-19" x="274.893364" y="126.69531"/> + <use xlink:href="#glyph0-6" x="281.589929" y="126.69531"/> + <use xlink:href="#glyph0-10" x="286.375648" y="126.69531"/> + <use xlink:href="#glyph0-4" x="292.108183" y="126.69531"/> + <use xlink:href="#glyph0-12" x="300.715592" y="126.69531"/> + <use xlink:href="#glyph0-6" x="308.358972" y="126.69531"/> + <use xlink:href="#glyph0-7" x="313.144692" y="126.69531"/> + <use xlink:href="#glyph0-18" x="317.930411" y="126.69531"/> + <use xlink:href="#glyph0-11" x="326.537821" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-18" x="339.448935" y="126.69531"/> + <use xlink:href="#glyph0-20" x="348.056344" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-6" x="358.092584" y="126.69531"/> + <use xlink:href="#glyph0-1" x="362.878303" y="126.69531"/> + <use xlink:href="#glyph0-2" x="371.485713" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-21" x="383.432797" y="126.69531"/> + <use xlink:href="#glyph0-14" x="395.861896" y="126.69531"/> + <use xlink:href="#glyph0-22" x="403.505275" y="126.69531"/> + <use xlink:href="#glyph0-2" x="411.148655" y="126.69531"/> + <use xlink:href="#glyph0-5" x="418.792035" y="126.69531"/> + <use xlink:href="#glyph0-5" x="423.577754" y="126.69531"/> + <use xlink:href="#glyph0-2" x="428.363474" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-23" x="440.310558" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-2" x="455.201376" y="126.69531"/> + <use xlink:href="#glyph0-24" x="462.844756" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-25" x="475.738655" y="126.69531"/> + <use xlink:href="#glyph0-10" x="487.220939" y="126.69531"/> + <use xlink:href="#glyph0-18" x="492.953474" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph0-26" x="501.147728" y="126.69531"/> + <use xlink:href="#glyph0-19" x="513.576827" y="126.69531"/> + <use xlink:href="#glyph0-2" x="520.273391" y="126.69531"/> + <use xlink:href="#glyph0-10" x="527.916771" y="126.69531"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-0" x="77.981674" y="157.003794"/> + <use xlink:href="#glyph1-1" x="86.61199" y="157.003794"/> + <use xlink:href="#glyph1-2" x="91.919276" y="157.003794"/> + <use xlink:href="#glyph1-1" x="95.242306" y="157.003794"/> + <use xlink:href="#glyph1-3" x="100.549592" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-4" x="109.502648" y="157.003794"/> + <use xlink:href="#glyph1-5" x="114.152499" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-6" x="120.129172" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-7" x="130.468817" y="157.003794"/> + <use xlink:href="#glyph1-3" x="135.776102" y="157.003794"/> + <use xlink:href="#glyph1-8" x="141.752775" y="157.003794"/> + <use xlink:href="#glyph1-9" x="147.729448" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-10" x="153.694168" y="157.003794"/> + <use xlink:href="#glyph1-11" x="161.66705" y="157.003794"/> + <use xlink:href="#glyph1-12" x="167.643723" y="157.003794"/> + <use xlink:href="#glyph1-13" x="171.624187" y="157.003794"/> + <use xlink:href="#glyph1-14" x="174.947217" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-15" x="182.585405" y="157.003794"/> + <use xlink:href="#glyph1-12" x="191.215721" y="157.003794"/> + <use xlink:href="#glyph1-13" x="195.196185" y="157.003794"/> + <use xlink:href="#glyph1-1" x="198.519215" y="157.003794"/> + <use xlink:href="#glyph1-12" x="203.826501" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-9" x="207.340785" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-16" x="213.305504" y="157.003794"/> + <use xlink:href="#glyph1-2" x="221.93582" y="157.003794"/> + <use xlink:href="#glyph1-1" x="225.25885" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-17" x="230.398789" y="157.003794"/> + <use xlink:href="#glyph1-7" x="236.375462" y="157.003794"/> + <use xlink:href="#glyph1-3" x="241.682748" y="157.003794"/> + <use xlink:href="#glyph1-18" x="247.659421" y="157.003794"/> + <use xlink:href="#glyph1-1" x="253.636094" y="157.003794"/> + <use xlink:href="#glyph1-12" x="258.943379" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-19" x="265.900227" y="157.003794"/> + <use xlink:href="#glyph1-20" x="276.526751" y="157.003794"/> + <use xlink:href="#glyph1-14" x="282.503424" y="157.003794"/> + <use xlink:href="#glyph1-11" x="287.153276" y="157.003794"/> + <use xlink:href="#glyph1-21" x="293.129949" y="157.003794"/> + <use xlink:href="#glyph1-11" x="298.437235" y="157.003794"/> + <use xlink:href="#glyph1-22" x="304.413908" y="157.003794"/> + <use xlink:href="#glyph1-23" x="310.390581" y="157.003794"/> + <use xlink:href="#glyph1-9" x="316.367254" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-24" x="322.343927" y="157.003794"/> + <use xlink:href="#glyph1-7" x="328.989987" y="157.003794"/> + <use xlink:href="#glyph1-25" x="334.297273" y="157.003794"/> + <use xlink:href="#glyph1-22" x="343.596976" y="157.003794"/> + <use xlink:href="#glyph1-1" x="349.573649" y="157.003794"/> + <use xlink:href="#glyph1-2" x="354.880934" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-26" x="361.192301" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-5" x="367.623201" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-27" x="373.587921" y="157.003794"/> + <use xlink:href="#glyph1-13" x="382.218237" y="157.003794"/> + <use xlink:href="#glyph1-3" x="385.541267" y="157.003794"/> + <use xlink:href="#glyph1-8" x="391.51794" y="157.003794"/> + <use xlink:href="#glyph1-9" x="397.494613" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-28" x="403.471286" y="157.003794"/> + <use xlink:href="#glyph1-13" x="410.117346" y="157.003794"/> + <use xlink:href="#glyph1-7" x="413.440377" y="157.003794"/> + <use xlink:href="#glyph1-2" x="418.747662" y="157.003794"/> + <use xlink:href="#glyph1-13" x="422.070692" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-10" x="428.382059" y="157.003794"/> + <use xlink:href="#glyph1-11" x="436.354941" y="157.003794"/> + <use xlink:href="#glyph1-20" x="442.331614" y="157.003794"/> + <use xlink:href="#glyph1-22" x="448.308287" y="157.003794"/> + <use xlink:href="#glyph1-18" x="454.28496" y="157.003794"/> + <use xlink:href="#glyph1-11" x="460.261633" y="157.003794"/> + <use xlink:href="#glyph1-22" x="466.238306" y="157.003794"/> + <use xlink:href="#glyph1-12" x="472.214979" y="157.003794"/> + <use xlink:href="#glyph1-29" x="476.195443" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-9" x="481.407102" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-0" x="487.371822" y="157.003794"/> + <use xlink:href="#glyph1-1" x="496.002137" y="157.003794"/> + <use xlink:href="#glyph1-12" x="501.309423" y="157.003794"/> + <use xlink:href="#glyph1-25" x="505.289887" y="157.003794"/> + <use xlink:href="#glyph1-7" x="514.58959" y="157.003794"/> + <use xlink:href="#glyph1-3" x="519.896876" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-30" x="528.861886" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-1" x="536.177333" y="157.003794"/> + <use xlink:href="#glyph1-3" x="541.484619" y="157.003794"/> + <use xlink:href="#glyph1-31" x="547.461292" y="157.003794"/> + <use xlink:href="#glyph1-1" x="550.784322" y="157.003794"/> + <use xlink:href="#glyph1-12" x="556.091608" y="157.003794"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-19" x="100.367941" y="170.9577"/> + <use xlink:href="#glyph1-13" x="110.994465" y="170.9577"/> + <use xlink:href="#glyph1-21" x="114.317496" y="170.9577"/> + <use xlink:href="#glyph1-12" x="119.624781" y="170.9577"/> + <use xlink:href="#glyph1-20" x="123.605245" y="170.9577"/> + <use xlink:href="#glyph1-14" x="129.581918" y="170.9577"/> + <use xlink:href="#glyph1-20" x="134.23177" y="170.9577"/> + <use xlink:href="#glyph1-32" x="140.208443" y="170.9577"/> + <use xlink:href="#glyph1-31" x="144.188907" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-33" x="150.500274" y="170.9577"/> + <use xlink:href="#glyph1-1" x="158.473156" y="170.9577"/> + <use xlink:href="#glyph1-14" x="163.780441" y="170.9577"/> + <use xlink:href="#glyph1-1" x="168.430293" y="170.9577"/> + <use xlink:href="#glyph1-7" x="173.737579" y="170.9577"/> + <use xlink:href="#glyph1-12" x="179.044864" y="170.9577"/> + <use xlink:href="#glyph1-21" x="183.025328" y="170.9577"/> + <use xlink:href="#glyph1-11" x="188.332614" y="170.9577"/> + <use xlink:href="#glyph1-9" x="194.309287" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-34" x="200.28596" y="170.9577"/> + <use xlink:href="#glyph1-3" x="208.916276" y="170.9577"/> + <use xlink:href="#glyph1-13" x="214.892949" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-35" x="217.929099" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-1" x="223.726472" y="170.9577"/> + <use xlink:href="#glyph1-12" x="229.033757" y="170.9577"/> + <use xlink:href="#glyph1-14" x="233.014221" y="170.9577"/> + <use xlink:href="#glyph1-13" x="237.664073" y="170.9577"/> + <use xlink:href="#glyph1-31" x="240.987103" y="170.9577"/> + <use xlink:href="#glyph1-29" x="244.310133" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-20" x="253.263189" y="170.9577"/> + <use xlink:href="#glyph1-32" x="259.239862" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-36" x="266.208663" y="170.9577"/> + <use xlink:href="#glyph1-2" x="270.189127" y="170.9577"/> + <use xlink:href="#glyph1-2" x="273.512158" y="170.9577"/> + <use xlink:href="#glyph1-13" x="276.835188" y="170.9577"/> + <use xlink:href="#glyph1-3" x="280.158218" y="170.9577"/> + <use xlink:href="#glyph1-20" x="286.134891" y="170.9577"/> + <use xlink:href="#glyph1-13" x="292.111564" y="170.9577"/> + <use xlink:href="#glyph1-14" x="295.434594" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-7" x="303.072782" y="170.9577"/> + <use xlink:href="#glyph1-31" x="308.380068" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-34" x="314.691435" y="170.9577"/> + <use xlink:href="#glyph1-12" x="323.32175" y="170.9577"/> + <use xlink:href="#glyph1-37" x="327.302215" y="170.9577"/> + <use xlink:href="#glyph1-7" x="333.278888" y="170.9577"/> + <use xlink:href="#glyph1-3" x="338.586173" y="170.9577"/> + <use xlink:href="#glyph1-7" x="344.562846" y="170.9577"/> + <use xlink:href="#glyph1-38" x="349.870132" y="170.9577"/> + <use xlink:href="#glyph1-10" x="353.850596" y="170.9577"/> + <use xlink:href="#glyph1-11" x="361.823478" y="170.9577"/> + <use xlink:href="#glyph1-7" x="367.800151" y="170.9577"/> + <use xlink:href="#glyph1-25" x="373.107436" y="170.9577"/> + <use xlink:href="#glyph1-39" x="382.40714" y="170.9577"/> + <use xlink:href="#glyph1-7" x="388.383813" y="170.9577"/> + <use xlink:href="#glyph1-13" x="393.691098" y="170.9577"/> + <use xlink:href="#glyph1-8" x="397.014128" y="170.9577"/> + <use xlink:href="#glyph1-3" x="402.990801" y="170.9577"/> + <use xlink:href="#glyph1-9" x="408.967474" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-34" x="414.944147" y="170.9577"/> + <use xlink:href="#glyph1-3" x="423.574463" y="170.9577"/> + <use xlink:href="#glyph1-13" x="429.551136" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-35" x="432.587286" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-1" x="438.384659" y="170.9577"/> + <use xlink:href="#glyph1-12" x="443.691945" y="170.9577"/> + <use xlink:href="#glyph1-14" x="447.672409" y="170.9577"/> + <use xlink:href="#glyph1-13" x="452.32226" y="170.9577"/> + <use xlink:href="#glyph1-31" x="455.645291" y="170.9577"/> + <use xlink:href="#glyph1-29" x="458.968321" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-20" x="467.921377" y="170.9577"/> + <use xlink:href="#glyph1-32" x="473.89805" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-6" x="480.866851" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-7" x="491.206495" y="170.9577"/> + <use xlink:href="#glyph1-14" x="496.513781" y="170.9577"/> + <use xlink:href="#glyph1-11" x="501.163632" y="170.9577"/> + <use xlink:href="#glyph1-13" x="507.140305" y="170.9577"/> + <use xlink:href="#glyph1-3" x="510.463335" y="170.9577"/> + <use xlink:href="#glyph1-8" x="516.440008" y="170.9577"/> + <use xlink:href="#glyph1-31" x="522.416681" y="170.9577"/> + <use xlink:href="#glyph1-20" x="525.739712" y="170.9577"/> + <use xlink:href="#glyph1-3" x="531.716385" y="170.9577"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph2-0" x="89.634936" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-11" x="95.606608" y="184.901604"/> + <use xlink:href="#glyph1-1" x="101.583281" y="184.901604"/> + <use xlink:href="#glyph1-2" x="106.890567" y="184.901604"/> + <use xlink:href="#glyph1-1" x="110.213597" y="184.901604"/> + <use xlink:href="#glyph1-3" x="115.520882" y="184.901604"/> + <use xlink:href="#glyph1-40" x="121.497555" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-9" x="129.362857" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-39" x="135.327577" y="184.901604"/> + <use xlink:href="#glyph1-13" x="141.30425" y="184.901604"/> + <use xlink:href="#glyph1-7" x="144.62728" y="184.901604"/> + <use xlink:href="#glyph1-2" x="149.934566" y="184.901604"/> + <use xlink:href="#glyph1-13" x="153.257596" y="184.901604"/> + <use xlink:href="#glyph1-21" x="156.580626" y="184.901604"/> + <use xlink:href="#glyph1-9" x="161.887912" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-11" x="167.864585" y="184.901604"/> + <use xlink:href="#glyph1-1" x="173.841258" y="184.901604"/> + <use xlink:href="#glyph1-12" x="179.148543" y="184.901604"/> + <use xlink:href="#glyph1-25" x="183.129007" y="184.901604"/> + <use xlink:href="#glyph1-7" x="192.428711" y="184.901604"/> + <use xlink:href="#glyph1-3" x="197.735996" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-35" x="203.246489" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph2-1" x="209.228413" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-41" x="215.210088" y="184.901604"/> + <use xlink:href="#glyph1-25" x="226.219119" y="184.901604"/> + <use xlink:href="#glyph1-13" x="235.518822" y="184.901604"/> + <use xlink:href="#glyph1-21" x="238.841853" y="184.901604"/> + <use xlink:href="#glyph1-12" x="244.149138" y="184.901604"/> + <use xlink:href="#glyph1-20" x="248.129602" y="184.901604"/> + <use xlink:href="#glyph1-14" x="254.106275" y="184.901604"/> + <use xlink:href="#glyph1-20" x="258.756127" y="184.901604"/> + <use xlink:href="#glyph1-32" x="264.7328" y="184.901604"/> + <use xlink:href="#glyph1-31" x="268.713264" y="184.901604"/> + <use xlink:href="#glyph1-5" x="272.036294" y="184.901604"/> + <use xlink:href="#glyph1-21" x="275.024631" y="184.901604"/> + <use xlink:href="#glyph1-20" x="280.331917" y="184.901604"/> + <use xlink:href="#glyph1-25" x="286.30859" y="184.901604"/> + <use xlink:href="#glyph1-9" x="295.608293" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph2-0" x="301.594268" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-8" x="307.575943" y="184.901604"/> + <use xlink:href="#glyph1-12" x="313.552616" y="184.901604"/> + <use xlink:href="#glyph1-13" x="317.53308" y="184.901604"/> + <use xlink:href="#glyph1-1" x="320.85611" y="184.901604"/> + <use xlink:href="#glyph1-12" x="326.163396" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-9" x="329.665726" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-23" x="335.630446" y="184.901604"/> + <use xlink:href="#glyph1-13" x="341.607119" y="184.901604"/> + <use xlink:href="#glyph1-3" x="344.930149" y="184.901604"/> + <use xlink:href="#glyph1-8" x="350.906822" y="184.901604"/> + <use xlink:href="#glyph1-14" x="356.883495" y="184.901604"/> + <use xlink:href="#glyph1-31" x="361.533347" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph2-1" x="364.871981" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-41" x="370.853656" y="184.901604"/> + <use xlink:href="#glyph1-22" x="381.862687" y="184.901604"/> + <use xlink:href="#glyph1-13" x="387.83936" y="184.901604"/> + <use xlink:href="#glyph1-22" x="391.16239" y="184.901604"/> + <use xlink:href="#glyph1-21" x="397.139063" y="184.901604"/> + <use xlink:href="#glyph1-5" x="402.446349" y="184.901604"/> + <use xlink:href="#glyph1-1" x="405.434686" y="184.901604"/> + <use xlink:href="#glyph1-18" x="410.741971" y="184.901604"/> + <use xlink:href="#glyph1-22" x="416.718644" y="184.901604"/> + <use xlink:href="#glyph1-9" x="422.695317" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-7" x="428.67199" y="184.901604"/> + <use xlink:href="#glyph1-3" x="433.979276" y="184.901604"/> + <use xlink:href="#glyph1-25" x="439.955949" y="184.901604"/> + <use xlink:href="#glyph1-41" x="449.255652" y="184.901604"/> + <use xlink:href="#glyph1-21" x="460.264684" y="184.901604"/> + <use xlink:href="#glyph1-14" x="465.571969" y="184.901604"/> + <use xlink:href="#glyph1-5" x="470.221821" y="184.901604"/> + <use xlink:href="#glyph1-40" x="473.210157" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-7" x="481.732893" y="184.901604"/> + <use xlink:href="#glyph1-14" x="487.040179" y="184.901604"/> + <use xlink:href="#glyph1-11" x="491.69003" y="184.901604"/> + <use xlink:href="#glyph1-13" x="497.666703" y="184.901604"/> + <use xlink:href="#glyph1-3" x="500.989734" y="184.901604"/> + <use xlink:href="#glyph1-8" x="506.966407" y="184.901604"/> + <use xlink:href="#glyph1-31" x="512.94308" y="184.901604"/> + <use xlink:href="#glyph1-20" x="516.26611" y="184.901604"/> + <use xlink:href="#glyph1-3" x="522.242783" y="184.901604"/> + <use xlink:href="#glyph1-5" x="528.219456" y="184.901604"/> + <use xlink:href="#glyph1-1" x="531.207792" y="184.901604"/> + <use xlink:href="#glyph1-18" x="536.515078" y="184.901604"/> + <use xlink:href="#glyph1-22" x="542.491751" y="184.901604"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-19" x="218.36097" y="212.799413"/> + <use xlink:href="#glyph1-24" x="228.987494" y="212.799413"/> + <use xlink:href="#glyph1-33" x="235.633555" y="212.799413"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-26" x="246.58282" y="212.799413"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-1" x="253.061533" y="212.799413"/> + <use xlink:href="#glyph1-21" x="258.368819" y="212.799413"/> + <use xlink:href="#glyph1-11" x="263.676104" y="212.799413"/> + <use xlink:href="#glyph1-3" x="269.652777" y="212.799413"/> + <use xlink:href="#glyph1-13" x="275.62945" y="212.799413"/> + <use xlink:href="#glyph1-21" x="278.95248" y="212.799413"/> + <use xlink:href="#glyph1-7" x="284.259766" y="212.799413"/> + <use xlink:href="#glyph1-2" x="289.567052" y="212.799413"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-33" x="295.866465" y="212.799413"/> + <use xlink:href="#glyph1-1" x="303.839347" y="212.799413"/> + <use xlink:href="#glyph1-39" x="309.146632" y="212.799413"/> + <use xlink:href="#glyph1-20" x="315.123305" y="212.799413"/> + <use xlink:href="#glyph1-12" x="321.099978" y="212.799413"/> + <use xlink:href="#glyph1-31" x="325.080443" y="212.799413"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph1-19" x="331.391809" y="212.799413"/> + <use xlink:href="#glyph1-24" x="342.018334" y="212.799413"/> + <use xlink:href="#glyph1-33" x="348.664394" y="212.799413"/> + <use xlink:href="#glyph1-38" x="356.637276" y="212.799413"/> + <use xlink:href="#glyph1-26" x="360.61774" y="212.799413"/> + <use xlink:href="#glyph1-33" x="367.921235" y="212.799413"/> + <use xlink:href="#glyph1-38" x="375.894117" y="212.799413"/> + <use xlink:href="#glyph1-42" x="379.874581" y="212.799413"/> + <use xlink:href="#glyph1-43" x="385.851254" y="212.799413"/> + <use xlink:href="#glyph1-43" x="391.827927" y="212.799413"/> + <use xlink:href="#glyph1-44" x="397.8046" y="212.799413"/> + <use xlink:href="#glyph1-38" x="403.781273" y="212.799413"/> + <use xlink:href="#glyph1-45" x="407.761737" y="212.799413"/> + <use xlink:href="#glyph1-46" x="413.73841" y="212.799413"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph3-0" x="287.460312" y="253.160711"/> + <use xlink:href="#glyph3-1" x="294.653446" y="253.160711"/> + <use xlink:href="#glyph3-2" x="300.192756" y="253.160711"/> + <use xlink:href="#glyph3-3" x="304.068281" y="253.160711"/> + <use xlink:href="#glyph3-4" x="307.38589" y="253.160711"/> + <use xlink:href="#glyph3-5" x="311.809368" y="253.160711"/> + <use xlink:href="#glyph3-6" x="316.790762" y="253.160711"/> + <use xlink:href="#glyph3-3" x="321.21424" y="253.160711"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-0" x="114.221819" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="122.829668" y="272.086008"/> + <use xlink:href="#glyph4-2" x="127.253147" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-2" x="135.30308" y="272.086008"/> + <use xlink:href="#glyph4-3" x="140.284474" y="272.086008"/> + <use xlink:href="#glyph4-4" x="143.602083" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="148.34437" y="272.086008"/> + <use xlink:href="#glyph4-6" x="155.537504" y="272.086008"/> + <use xlink:href="#glyph4-1" x="159.413029" y="272.086008"/> + <use xlink:href="#glyph4-3" x="163.836507" y="272.086008"/> + <use xlink:href="#glyph4-6" x="167.154116" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="174.098179" y="272.086008"/> + <use xlink:href="#glyph4-3" x="179.079574" y="272.086008"/> + <use xlink:href="#glyph4-7" x="182.397183" y="272.086008"/> + <use xlink:href="#glyph4-8" x="185.166838" y="272.086008"/> + <use xlink:href="#glyph4-7" x="190.148232" y="272.086008"/> + <use xlink:href="#glyph4-9" x="192.917888" y="272.086008"/> + <use xlink:href="#glyph4-10" x="197.899282" y="272.086008"/> + <use xlink:href="#glyph4-11" x="202.32276" y="272.086008"/> + <use xlink:href="#glyph4-1" x="205.092415" y="272.086008"/> + <use xlink:href="#glyph4-12" x="209.515894" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="217.585753" y="272.086008"/> + <use xlink:href="#glyph4-6" x="222.009231" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="228.963257" y="272.086008"/> + <use xlink:href="#glyph4-13" x="233.386736" y="272.086008"/> + <use xlink:href="#glyph4-13" x="238.36813" y="272.086008"/> + <use xlink:href="#glyph4-14" x="243.349524" y="272.086008"/> + <use xlink:href="#glyph4-7" x="246.11918" y="272.086008"/> + <use xlink:href="#glyph4-15" x="248.888835" y="272.086008"/> + <use xlink:href="#glyph4-10" x="253.312313" y="272.086008"/> + <use xlink:href="#glyph4-11" x="257.735791" y="272.086008"/> + <use xlink:href="#glyph4-7" x="260.505447" y="272.086008"/> + <use xlink:href="#glyph4-4" x="263.275102" y="272.086008"/> + <use xlink:href="#glyph4-9" x="268.256496" y="272.086008"/> + <use xlink:href="#glyph4-6" x="273.237891" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="280.191917" y="272.086008"/> + <use xlink:href="#glyph4-16" x="282.961573" y="272.086008"/> + <use xlink:href="#glyph4-10" x="287.942967" y="272.086008"/> + <use xlink:href="#glyph4-11" x="292.366445" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="298.214602" y="272.086008"/> + <use xlink:href="#glyph4-1" x="303.195997" y="272.086008"/> + <use xlink:href="#glyph4-4" x="307.619475" y="272.086008"/> + <use xlink:href="#glyph4-13" x="312.600869" y="272.086008"/> + <use xlink:href="#glyph4-14" x="317.582264" y="272.086008"/> + <use xlink:href="#glyph4-1" x="320.351919" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-17" x="327.853899" y="272.086008"/> + <use xlink:href="#glyph4-6" x="332.835293" y="272.086008"/> + <use xlink:href="#glyph4-1" x="336.710818" y="272.086008"/> + <use xlink:href="#glyph4-12" x="341.134297" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="349.194193" y="272.086008"/> + <use xlink:href="#glyph4-4" x="351.963848" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="360.033707" y="272.086008"/> + <use xlink:href="#glyph4-7" x="365.015101" y="272.086008"/> + <use xlink:href="#glyph4-1" x="367.784757" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="371.969128" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="382.2308" y="272.086008"/> + <use xlink:href="#glyph4-11" x="386.106325" y="272.086008"/> + <use xlink:href="#glyph4-10" x="388.87598" y="272.086008"/> + <use xlink:href="#glyph4-11" x="393.299459" y="272.086008"/> + <use xlink:href="#glyph4-7" x="396.069114" y="272.086008"/> + <use xlink:href="#glyph4-15" x="398.838769" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="406.340749" y="272.086008"/> + <use xlink:href="#glyph4-1" x="413.533883" y="272.086008"/> + <use xlink:href="#glyph4-2" x="417.957361" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="426.017257" y="272.086008"/> + <use xlink:href="#glyph4-7" x="429.892782" y="272.086008"/> + <use xlink:href="#glyph4-11" x="432.662437" y="272.086008"/> + <use xlink:href="#glyph4-1" x="435.432093" y="272.086008"/> + <use xlink:href="#glyph4-6" x="439.855571" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="446.809597" y="272.086008"/> + <use xlink:href="#glyph4-1" x="450.685122" y="272.086008"/> + <use xlink:href="#glyph4-19" x="455.1086" y="272.086008"/> + <use xlink:href="#glyph4-17" x="460.089995" y="272.086008"/> + <use xlink:href="#glyph4-1" x="465.071389" y="272.086008"/> + <use xlink:href="#glyph4-9" x="469.494867" y="272.086008"/> + <use xlink:href="#glyph4-11" x="474.476262" y="272.086008"/> + <use xlink:href="#glyph4-7" x="477.245917" y="272.086008"/> + <use xlink:href="#glyph4-10" x="480.015572" y="272.086008"/> + <use xlink:href="#glyph4-14" x="484.439051" y="272.086008"/> + <use xlink:href="#glyph4-14" x="487.208706" y="272.086008"/> + <use xlink:href="#glyph4-20" x="489.978361" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-21" x="494.322137" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-22" x="501.664712" y="272.086008"/> + <use xlink:href="#glyph4-6" x="508.857846" y="272.086008"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="99.277636" y="284.049357"/> + <use xlink:href="#glyph4-1" x="106.470769" y="284.049357"/> + <use xlink:href="#glyph4-2" x="110.894247" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="118.90433" y="284.049357"/> + <use xlink:href="#glyph4-7" x="122.779854" y="284.049357"/> + <use xlink:href="#glyph4-11" x="125.54951" y="284.049357"/> + <use xlink:href="#glyph4-1" x="128.319165" y="284.049357"/> + <use xlink:href="#glyph4-6" x="132.742643" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="139.656819" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="143.84119" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="148.623329" y="284.049357"/> + <use xlink:href="#glyph4-14" x="153.604723" y="284.049357"/> + <use xlink:href="#glyph4-18" x="156.374378" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="161.206331" y="284.049357"/> + <use xlink:href="#glyph4-12" x="165.629809" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="173.639891" y="284.049357"/> + <use xlink:href="#glyph4-9" x="176.409546" y="284.049357"/> + <use xlink:href="#glyph4-11" x="181.390941" y="284.049357"/> + <use xlink:href="#glyph4-4" x="184.160596" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-12" x="192.170678" y="284.049357"/> + <use xlink:href="#glyph4-20" x="197.152073" y="284.049357"/> + <use xlink:href="#glyph4-9" x="202.133467" y="284.049357"/> + <use xlink:href="#glyph4-10" x="207.114862" y="284.049357"/> + <use xlink:href="#glyph4-23" x="211.53834" y="284.049357"/> + <use xlink:href="#glyph4-7" x="219.289389" y="284.049357"/> + <use xlink:href="#glyph4-15" x="222.059045" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="229.521174" y="284.049357"/> + <use xlink:href="#glyph4-1" x="236.714307" y="284.049357"/> + <use xlink:href="#glyph4-2" x="241.137785" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="249.15783" y="284.049357"/> + <use xlink:href="#glyph4-13" x="253.581309" y="284.049357"/> + <use xlink:href="#glyph4-13" x="258.562703" y="284.049357"/> + <use xlink:href="#glyph4-14" x="263.544097" y="284.049357"/> + <use xlink:href="#glyph4-7" x="266.313753" y="284.049357"/> + <use xlink:href="#glyph4-15" x="269.083408" y="284.049357"/> + <use xlink:href="#glyph4-10" x="273.506886" y="284.049357"/> + <use xlink:href="#glyph4-11" x="277.930364" y="284.049357"/> + <use xlink:href="#glyph4-7" x="280.70002" y="284.049357"/> + <use xlink:href="#glyph4-4" x="283.469675" y="284.049357"/> + <use xlink:href="#glyph4-9" x="288.451069" y="284.049357"/> + <use xlink:href="#glyph4-6" x="293.432464" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="300.346639" y="284.049357"/> + <use xlink:href="#glyph4-4" x="304.770117" y="284.049357"/> + <use xlink:href="#glyph4-23" x="309.751512" y="284.049357"/> + <use xlink:href="#glyph4-13" x="317.502561" y="284.049357"/> + <use xlink:href="#glyph4-4" x="322.483956" y="284.049357"/> + <use xlink:href="#glyph4-6" x="327.46535" y="284.049357"/> + <use xlink:href="#glyph4-7" x="331.340875" y="284.049357"/> + <use xlink:href="#glyph4-9" x="334.11053" y="284.049357"/> + <use xlink:href="#glyph4-8" x="339.091925" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="347.11197" y="284.049357"/> + <use xlink:href="#glyph4-4" x="351.535448" y="284.049357"/> + <use xlink:href="#glyph4-9" x="356.516842" y="284.049357"/> + <use xlink:href="#glyph4-11" x="361.498237" y="284.049357"/> + <use xlink:href="#glyph4-1" x="364.267892" y="284.049357"/> + <use xlink:href="#glyph4-9" x="368.69137" y="284.049357"/> + <use xlink:href="#glyph4-11" x="373.672765" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-24" x="379.481071" y="284.049357"/> + <use xlink:href="#glyph4-3" x="382.798679" y="284.049357"/> + <use xlink:href="#glyph4-4" x="386.116288" y="284.049357"/> + <use xlink:href="#glyph4-23" x="391.097682" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="401.887383" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="406.62967" y="284.049357"/> + <use xlink:href="#glyph4-3" x="411.053148" y="284.049357"/> + <use xlink:href="#glyph4-7" x="414.370757" y="284.049357"/> + <use xlink:href="#glyph4-4" x="417.140412" y="284.049357"/> + <use xlink:href="#glyph4-17" x="422.121807" y="284.049357"/> + <use xlink:href="#glyph4-6" x="427.103201" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="434.007414" y="284.049357"/> + <use xlink:href="#glyph4-1" x="441.200547" y="284.049357"/> + <use xlink:href="#glyph4-2" x="445.624025" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="453.634108" y="284.049357"/> + <use xlink:href="#glyph4-7" x="457.509632" y="284.049357"/> + <use xlink:href="#glyph4-11" x="460.279288" y="284.049357"/> + <use xlink:href="#glyph4-1" x="463.048943" y="284.049357"/> + <use xlink:href="#glyph4-6" x="467.472421" y="284.049357"/> + <use xlink:href="#glyph4-25" x="471.347946" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-2" x="477.016773" y="284.049357"/> + <use xlink:href="#glyph4-3" x="481.998167" y="284.049357"/> + <use xlink:href="#glyph4-4" x="485.315776" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="490.058063" y="284.049357"/> + <use xlink:href="#glyph4-6" x="497.251197" y="284.049357"/> + <use xlink:href="#glyph4-1" x="501.126722" y="284.049357"/> + <use xlink:href="#glyph4-3" x="505.5502" y="284.049357"/> + <use xlink:href="#glyph4-6" x="508.867809" y="284.049357"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-16" x="99.277636" y="296.002703"/> + <use xlink:href="#glyph4-10" x="104.25903" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="108.483252" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="113.315205" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-2" x="120.408711" y="296.002703"/> + <use xlink:href="#glyph4-1" x="125.390105" y="296.002703"/> + <use xlink:href="#glyph4-15" x="129.813583" y="296.002703"/> + <use xlink:href="#glyph4-4" x="134.237061" y="296.002703"/> + <use xlink:href="#glyph4-23" x="139.218456" y="296.002703"/> + <use xlink:href="#glyph4-1" x="146.969506" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-23" x="154.072974" y="296.002703"/> + <use xlink:href="#glyph4-17" x="161.824024" y="296.002703"/> + <use xlink:href="#glyph4-14" x="166.805418" y="296.002703"/> + <use xlink:href="#glyph4-11" x="169.575073" y="296.002703"/> + <use xlink:href="#glyph4-7" x="172.344729" y="296.002703"/> + <use xlink:href="#glyph4-26" x="175.114384" y="296.002703"/> + <use xlink:href="#glyph4-13" x="178.431993" y="296.002703"/> + <use xlink:href="#glyph4-3" x="183.413387" y="296.002703"/> + <use xlink:href="#glyph4-7" x="186.730996" y="296.002703"/> + <use xlink:href="#glyph4-9" x="189.500651" y="296.002703"/> + <use xlink:href="#glyph4-15" x="194.482045" y="296.002703"/> + <use xlink:href="#glyph4-7" x="198.905524" y="296.002703"/> + <use xlink:href="#glyph4-13" x="201.675179" y="296.002703"/> + <use xlink:href="#glyph4-10" x="206.656573" y="296.002703"/> + <use xlink:href="#glyph4-14" x="211.080052" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="216.53966" y="296.002703"/> + <use xlink:href="#glyph4-13" x="221.521054" y="296.002703"/> + <use xlink:href="#glyph4-1" x="226.502449" y="296.002703"/> + <use xlink:href="#glyph4-3" x="230.925927" y="296.002703"/> + <use xlink:href="#glyph4-10" x="234.243535" y="296.002703"/> + <use xlink:href="#glyph4-11" x="238.667014" y="296.002703"/> + <use xlink:href="#glyph4-7" x="241.436669" y="296.002703"/> + <use xlink:href="#glyph4-9" x="244.206324" y="296.002703"/> + <use xlink:href="#glyph4-8" x="249.187719" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="256.849103" y="296.002703"/> + <use xlink:href="#glyph4-9" x="261.272581" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="265.865427" y="296.002703"/> + <use xlink:href="#glyph4-7" x="270.846822" y="296.002703"/> + <use xlink:href="#glyph4-3" x="273.616477" y="296.002703"/> + <use xlink:href="#glyph4-4" x="276.934085" y="296.002703"/> + <use xlink:href="#glyph4-9" x="281.91548" y="296.002703"/> + <use xlink:href="#glyph4-23" x="286.896874" y="296.002703"/> + <use xlink:href="#glyph4-1" x="294.647924" y="296.002703"/> + <use xlink:href="#glyph4-9" x="299.071402" y="296.002703"/> + <use xlink:href="#glyph4-11" x="304.052797" y="296.002703"/> + <use xlink:href="#glyph4-6" x="306.822452" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="313.368004" y="296.002703"/> + <use xlink:href="#glyph4-7" x="320.561138" y="296.002703"/> + <use xlink:href="#glyph4-11" x="323.330793" y="296.002703"/> + <use xlink:href="#glyph4-16" x="326.100448" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-3" x="333.761833" y="296.002703"/> + <use xlink:href="#glyph4-1" x="337.079441" y="296.002703"/> + <use xlink:href="#glyph4-6" x="341.50292" y="296.002703"/> + <use xlink:href="#glyph4-4" x="345.378445" y="296.002703"/> + <use xlink:href="#glyph4-17" x="350.359839" y="296.002703"/> + <use xlink:href="#glyph4-3" x="355.341233" y="296.002703"/> + <use xlink:href="#glyph4-15" x="358.658842" y="296.002703"/> + <use xlink:href="#glyph4-1" x="363.08232" y="296.002703"/> + <use xlink:href="#glyph4-6" x="367.505798" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="374.061313" y="296.002703"/> + <use xlink:href="#glyph4-16" x="377.936838" y="296.002703"/> + <use xlink:href="#glyph4-10" x="382.918233" y="296.002703"/> + <use xlink:href="#glyph4-3" x="387.341711" y="296.002703"/> + <use xlink:href="#glyph4-1" x="390.65932" y="296.002703"/> + <use xlink:href="#glyph4-12" x="395.082798" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="402.754145" y="296.002703"/> + <use xlink:href="#glyph4-23" x="407.177623" y="296.002703"/> + <use xlink:href="#glyph4-4" x="414.928673" y="296.002703"/> + <use xlink:href="#glyph4-9" x="419.910068" y="296.002703"/> + <use xlink:href="#glyph4-8" x="424.891462" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-23" x="432.552847" y="296.002703"/> + <use xlink:href="#glyph4-17" x="440.303896" y="296.002703"/> + <use xlink:href="#glyph4-11" x="445.285291" y="296.002703"/> + <use xlink:href="#glyph4-17" x="448.054946" y="296.002703"/> + <use xlink:href="#glyph4-10" x="453.03634" y="296.002703"/> + <use xlink:href="#glyph4-14" x="457.459819" y="296.002703"/> + <use xlink:href="#glyph4-14" x="460.229474" y="296.002703"/> + <use xlink:href="#glyph4-20" x="462.999129" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-12" x="470.660514" y="296.002703"/> + <use xlink:href="#glyph4-7" x="475.641908" y="296.002703"/> + <use xlink:href="#glyph4-6" x="478.411563" y="296.002703"/> + <use xlink:href="#glyph4-11" x="482.287088" y="296.002703"/> + <use xlink:href="#glyph4-3" x="485.056744" y="296.002703"/> + <use xlink:href="#glyph4-17" x="488.374352" y="296.002703"/> + <use xlink:href="#glyph4-6" x="493.355747" y="296.002703"/> + <use xlink:href="#glyph4-11" x="497.231271" y="296.002703"/> + <use xlink:href="#glyph4-7" x="500.000927" y="296.002703"/> + <use xlink:href="#glyph4-9" x="502.770582" y="296.002703"/> + <use xlink:href="#glyph4-8" x="507.751976" y="296.002703"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="99.277636" y="307.956049"/> + <use xlink:href="#glyph4-1" x="106.470769" y="307.956049"/> + <use xlink:href="#glyph4-2" x="110.894247" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="118.874441" y="307.956049"/> + <use xlink:href="#glyph4-7" x="122.749966" y="307.956049"/> + <use xlink:href="#glyph4-11" x="125.519621" y="307.956049"/> + <use xlink:href="#glyph4-1" x="128.289277" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph5-0" x="135.717836" y="307.956049"/> + <use xlink:href="#glyph5-1" x="140.69923" y="307.956049"/> + <use xlink:href="#glyph5-2" x="144.574755" y="307.956049"/> + <use xlink:href="#glyph5-3" x="147.344411" y="307.956049"/> + <use xlink:href="#glyph5-4" x="152.325805" y="307.956049"/> + <use xlink:href="#glyph5-2" x="156.749283" y="307.956049"/> + <use xlink:href="#glyph5-0" x="159.518938" y="307.956049"/> + <use xlink:href="#glyph5-5" x="164.500333" y="307.956049"/> + <use xlink:href="#glyph5-6" x="169.481727" y="307.956049"/> + <use xlink:href="#glyph5-7" x="172.251383" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-21" x="176.129148" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-27" x="183.242579" y="307.956049"/> + <use xlink:href="#glyph4-1" x="190.435713" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="194.620084" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="199.452037" y="307.956049"/> + <use xlink:href="#glyph4-3" x="203.875515" y="307.956049"/> + <use xlink:href="#glyph4-11" x="207.193123" y="307.956049"/> + <use xlink:href="#glyph4-16" x="209.962779" y="307.956049"/> + <use xlink:href="#glyph4-1" x="214.944173" y="307.956049"/> + <use xlink:href="#glyph4-14" x="219.367651" y="307.956049"/> + <use xlink:href="#glyph4-1" x="222.137307" y="307.956049"/> + <use xlink:href="#glyph4-6" x="226.560785" y="307.956049"/> + <use xlink:href="#glyph4-6" x="230.43631" y="307.956049"/> + <use xlink:href="#glyph4-25" x="234.311835" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-9" x="239.920885" y="307.956049"/> + <use xlink:href="#glyph4-4" x="244.902279" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="252.892436" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-28" x="257.176435" y="307.956049"/> + <use xlink:href="#glyph4-7" x="262.157829" y="307.956049"/> + <use xlink:href="#glyph4-6" x="264.927485" y="307.956049"/> + <use xlink:href="#glyph4-11" x="268.803009" y="307.956049"/> + <use xlink:href="#glyph4-7" x="271.572665" y="307.956049"/> + <use xlink:href="#glyph4-9" x="274.34232" y="307.956049"/> + <use xlink:href="#glyph4-8" x="279.323714" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-2" x="287.293945" y="307.956049"/> + <use xlink:href="#glyph4-3" x="292.27534" y="307.956049"/> + <use xlink:href="#glyph4-4" x="295.592949" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="300.335236" y="307.956049"/> + <use xlink:href="#glyph4-6" x="307.528369" y="307.956049"/> + <use xlink:href="#glyph4-1" x="311.403894" y="307.956049"/> + <use xlink:href="#glyph4-3" x="315.827373" y="307.956049"/> + <use xlink:href="#glyph4-6" x="319.144981" y="307.956049"/> + <use xlink:href="#glyph4-25" x="323.020506" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="328.639519" y="307.956049"/> + <use xlink:href="#glyph4-9" x="331.409174" y="307.956049"/> + <use xlink:href="#glyph4-15" x="336.390569" y="307.956049"/> + <use xlink:href="#glyph4-14" x="340.814047" y="307.956049"/> + <use xlink:href="#glyph4-17" x="343.583702" y="307.956049"/> + <use xlink:href="#glyph4-12" x="348.565097" y="307.956049"/> + <use xlink:href="#glyph4-7" x="353.546491" y="307.956049"/> + <use xlink:href="#glyph4-9" x="356.316146" y="307.956049"/> + <use xlink:href="#glyph4-8" x="361.297541" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-9" x="369.277734" y="307.956049"/> + <use xlink:href="#glyph4-1" x="374.259129" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="378.4435" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="388.635433" y="307.956049"/> + <use xlink:href="#glyph4-3" x="393.058911" y="307.956049"/> + <use xlink:href="#glyph4-15" x="396.37652" y="307.956049"/> + <use xlink:href="#glyph4-16" x="400.799998" y="307.956049"/> + <use xlink:href="#glyph4-7" x="405.781393" y="307.956049"/> + <use xlink:href="#glyph4-11" x="408.551048" y="307.956049"/> + <use xlink:href="#glyph4-1" x="411.320703" y="307.956049"/> + <use xlink:href="#glyph4-15" x="415.744181" y="307.956049"/> + <use xlink:href="#glyph4-11" x="420.16766" y="307.956049"/> + <use xlink:href="#glyph4-17" x="422.937315" y="307.956049"/> + <use xlink:href="#glyph4-3" x="427.918709" y="307.956049"/> + <use xlink:href="#glyph4-1" x="431.236318" y="307.956049"/> + <use xlink:href="#glyph4-6" x="435.659796" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-14" x="442.534121" y="307.956049"/> + <use xlink:href="#glyph4-7" x="445.303776" y="307.956049"/> + <use xlink:href="#glyph4-29" x="448.073431" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="452.96516" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-30" x="460.387438" y="307.956049"/> + <use xlink:href="#glyph4-31" x="463.705047" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-32" x="472.79111" y="307.956049"/> + <use xlink:href="#glyph4-25" x="477.772504" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-33" x="483.40148" y="307.956049"/> + <use xlink:href="#glyph4-4" x="490.594614" y="307.956049"/> + <use xlink:href="#glyph4-4" x="495.576008" y="307.956049"/> + <use xlink:href="#glyph4-8" x="500.557402" y="307.956049"/> + <use xlink:href="#glyph4-14" x="505.538797" y="307.956049"/> + <use xlink:href="#glyph4-1" x="508.308452" y="307.956049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-34" x="99.277636" y="319.909395"/> + <use xlink:href="#glyph4-16" x="105.922816" y="319.909395"/> + <use xlink:href="#glyph4-3" x="110.90421" y="319.909395"/> + <use xlink:href="#glyph4-4" x="114.221819" y="319.909395"/> + <use xlink:href="#glyph4-23" x="119.203213" y="319.909395"/> + <use xlink:href="#glyph4-1" x="126.954263" y="319.909395"/> + <use xlink:href="#glyph4-25" x="131.377741" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="136.100103" y="319.909395"/> + <use xlink:href="#glyph4-9" x="140.523581" y="319.909395"/> + <use xlink:href="#glyph4-12" x="145.504976" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-35" x="152.668221" y="319.909395"/> + <use xlink:href="#glyph4-36" x="159.861354" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-25" x="164.304758" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-16" x="168.967343" y="319.909395"/> + <use xlink:href="#glyph4-10" x="173.948738" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="178.182923" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="183.014875" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="189.610242" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-23" x="196.215571" y="319.909395"/> + <use xlink:href="#glyph4-17" x="203.96662" y="319.909395"/> + <use xlink:href="#glyph4-14" x="208.948015" y="319.909395"/> + <use xlink:href="#glyph4-11" x="211.71767" y="319.909395"/> + <use xlink:href="#glyph4-7" x="214.487325" y="319.909395"/> + <use xlink:href="#glyph4-26" x="217.256981" y="319.909395"/> + <use xlink:href="#glyph4-13" x="220.574589" y="319.909395"/> + <use xlink:href="#glyph4-3" x="225.555984" y="319.909395"/> + <use xlink:href="#glyph4-7" x="228.873592" y="319.909395"/> + <use xlink:href="#glyph4-9" x="231.643248" y="319.909395"/> + <use xlink:href="#glyph4-15" x="236.624642" y="319.909395"/> + <use xlink:href="#glyph4-7" x="241.04812" y="319.909395"/> + <use xlink:href="#glyph4-13" x="243.817776" y="319.909395"/> + <use xlink:href="#glyph4-10" x="248.79917" y="319.909395"/> + <use xlink:href="#glyph4-14" x="253.222648" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="258.174154" y="319.909395"/> + <use xlink:href="#glyph4-13" x="263.155549" y="319.909395"/> + <use xlink:href="#glyph4-1" x="268.136943" y="319.909395"/> + <use xlink:href="#glyph4-3" x="272.560421" y="319.909395"/> + <use xlink:href="#glyph4-10" x="275.87803" y="319.909395"/> + <use xlink:href="#glyph4-11" x="280.301508" y="319.909395"/> + <use xlink:href="#glyph4-7" x="283.071163" y="319.909395"/> + <use xlink:href="#glyph4-9" x="285.840819" y="319.909395"/> + <use xlink:href="#glyph4-8" x="290.822213" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="297.985458" y="319.909395"/> + <use xlink:href="#glyph4-20" x="301.860983" y="319.909395"/> + <use xlink:href="#glyph4-6" x="306.842377" y="319.909395"/> + <use xlink:href="#glyph4-11" x="310.717902" y="319.909395"/> + <use xlink:href="#glyph4-1" x="313.487558" y="319.909395"/> + <use xlink:href="#glyph4-23" x="317.911036" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="327.843936" y="319.909395"/> + <use xlink:href="#glyph4-4" x="332.267414" y="319.909395"/> + <use xlink:href="#glyph4-9" x="337.248809" y="319.909395"/> + <use xlink:href="#glyph4-6" x="342.230203" y="319.909395"/> + <use xlink:href="#glyph4-11" x="346.105728" y="319.909395"/> + <use xlink:href="#glyph4-3" x="348.875383" y="319.909395"/> + <use xlink:href="#glyph4-17" x="352.192992" y="319.909395"/> + <use xlink:href="#glyph4-15" x="357.174386" y="319.909395"/> + <use xlink:href="#glyph4-11" x="361.597865" y="319.909395"/> + <use xlink:href="#glyph4-7" x="364.36752" y="319.909395"/> + <use xlink:href="#glyph4-4" x="367.137175" y="319.909395"/> + <use xlink:href="#glyph4-9" x="372.11857" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="379.271852" y="319.909395"/> + <use xlink:href="#glyph4-16" x="382.041507" y="319.909395"/> + <use xlink:href="#glyph4-10" x="387.022902" y="319.909395"/> + <use xlink:href="#glyph4-11" x="391.44638" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-8" x="396.397886" y="319.909395"/> + <use xlink:href="#glyph4-7" x="401.37928" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="403.909829" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="408.741781" y="319.909395"/> + <use xlink:href="#glyph4-6" x="413.16526" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="419.212672" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-2" x="425.818001" y="319.909395"/> + <use xlink:href="#glyph4-3" x="430.799396" y="319.909395"/> + <use xlink:href="#glyph4-4" x="434.117004" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="438.859292" y="319.909395"/> + <use xlink:href="#glyph4-6" x="446.052425" y="319.909395"/> + <use xlink:href="#glyph4-1" x="449.92795" y="319.909395"/> + <use xlink:href="#glyph4-3" x="454.351428" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-26" x="457.469781" y="319.909395"/> + <use xlink:href="#glyph4-2" x="460.78739" y="319.909395"/> + <use xlink:href="#glyph4-10" x="465.768784" y="319.909395"/> + <use xlink:href="#glyph4-6" x="470.192263" y="319.909395"/> + <use xlink:href="#glyph4-1" x="474.067787" y="319.909395"/> + <use xlink:href="#glyph4-12" x="478.491266" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-35" x="485.644548" y="319.909395"/> + <use xlink:href="#glyph4-37" x="492.837682" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="500.558843" y="319.909395"/> + <use xlink:href="#glyph4-16" x="503.328498" y="319.909395"/> + <use xlink:href="#glyph4-1" x="508.309893" y="319.909395"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph5-8" x="99.277636" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph5-9" x="103.501858" y="331.862741"/> + <use xlink:href="#glyph5-4" x="107.925336" y="331.862741"/> + <use xlink:href="#glyph5-6" x="112.348815" y="331.862741"/> + <use xlink:href="#glyph5-10" x="115.11847" y="331.862741"/> + <use xlink:href="#glyph5-7" x="120.099864" y="331.862741"/> + <use xlink:href="#glyph5-2" x="123.975389" y="331.862741"/> + <use xlink:href="#glyph5-11" x="126.745044" y="331.862741"/> + <use xlink:href="#glyph5-8" x="131.168523" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="138.078497" y="331.862741"/> + <use xlink:href="#glyph4-4" x="142.501975" y="331.862741"/> + <use xlink:href="#glyph4-9" x="147.483369" y="331.862741"/> + <use xlink:href="#glyph4-11" x="152.464764" y="331.862741"/> + <use xlink:href="#glyph4-3" x="155.234419" y="331.862741"/> + <use xlink:href="#glyph4-4" x="158.552028" y="331.862741"/> + <use xlink:href="#glyph4-14" x="163.533422" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="168.783812" y="331.862741"/> + <use xlink:href="#glyph4-4" x="171.553467" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-23" x="179.025559" y="331.862741"/> + <use xlink:href="#glyph4-10" x="186.776608" y="331.862741"/> + <use xlink:href="#glyph4-9" x="191.200087" y="331.862741"/> + <use xlink:href="#glyph4-10" x="196.181481" y="331.862741"/> + <use xlink:href="#glyph4-8" x="200.604959" y="331.862741"/> + <use xlink:href="#glyph4-1" x="205.586354" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="212.500529" y="331.862741"/> + <use xlink:href="#glyph4-16" x="215.270184" y="331.862741"/> + <use xlink:href="#glyph4-1" x="220.251579" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="227.165754" y="331.862741"/> + <use xlink:href="#glyph4-3" x="232.147149" y="331.862741"/> + <use xlink:href="#glyph4-4" x="235.464757" y="331.862741"/> + <use xlink:href="#glyph4-11" x="240.446152" y="331.862741"/> + <use xlink:href="#glyph4-1" x="243.215807" y="331.862741"/> + <use xlink:href="#glyph4-15" x="247.639285" y="331.862741"/> + <use xlink:href="#glyph4-11" x="252.062763" y="331.862741"/> + <use xlink:href="#glyph4-7" x="254.832419" y="331.862741"/> + <use xlink:href="#glyph4-4" x="257.602074" y="331.862741"/> + <use xlink:href="#glyph4-9" x="262.583468" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="270.05556" y="331.862741"/> + <use xlink:href="#glyph4-24" x="275.036954" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="280.84526" y="331.862741"/> + <use xlink:href="#glyph4-14" x="285.268739" y="331.862741"/> + <use xlink:href="#glyph4-14" x="288.038394" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="293.298746" y="331.862741"/> + <use xlink:href="#glyph4-20" x="297.174271" y="331.862741"/> + <use xlink:href="#glyph4-6" x="302.155666" y="331.862741"/> + <use xlink:href="#glyph4-11" x="306.03119" y="331.862741"/> + <use xlink:href="#glyph4-1" x="308.800846" y="331.862741"/> + <use xlink:href="#glyph4-23" x="313.224324" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-3" x="323.466071" y="331.862741"/> + <use xlink:href="#glyph4-1" x="326.783679" y="331.862741"/> + <use xlink:href="#glyph4-6" x="331.207158" y="331.862741"/> + <use xlink:href="#glyph4-4" x="335.082683" y="331.862741"/> + <use xlink:href="#glyph4-17" x="340.064077" y="331.862741"/> + <use xlink:href="#glyph4-3" x="345.045471" y="331.862741"/> + <use xlink:href="#glyph4-15" x="348.36308" y="331.862741"/> + <use xlink:href="#glyph4-1" x="352.786558" y="331.862741"/> + <use xlink:href="#glyph4-6" x="357.210036" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="363.576258" y="331.862741"/> + <use xlink:href="#glyph4-23" x="367.999737" y="331.862741"/> + <use xlink:href="#glyph4-4" x="375.750786" y="331.862741"/> + <use xlink:href="#glyph4-9" x="380.732181" y="331.862741"/> + <use xlink:href="#glyph4-8" x="385.713575" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="393.185667" y="331.862741"/> + <use xlink:href="#glyph4-1" x="400.3788" y="331.862741"/> + <use xlink:href="#glyph4-2" x="404.802279" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="412.27437" y="331.862741"/> + <use xlink:href="#glyph4-7" x="416.149895" y="331.862741"/> + <use xlink:href="#glyph4-11" x="418.91955" y="331.862741"/> + <use xlink:href="#glyph4-1" x="421.689206" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="428.603381" y="331.862741"/> + <use xlink:href="#glyph4-3" x="433.584775" y="331.862741"/> + <use xlink:href="#glyph4-7" x="436.902384" y="331.862741"/> + <use xlink:href="#glyph4-9" x="439.672039" y="331.862741"/> + <use xlink:href="#glyph4-15" x="444.653434" y="331.862741"/> + <use xlink:href="#glyph4-7" x="449.076912" y="331.862741"/> + <use xlink:href="#glyph4-13" x="451.846567" y="331.862741"/> + <use xlink:href="#glyph4-10" x="456.827962" y="331.862741"/> + <use xlink:href="#glyph4-14" x="461.25144" y="331.862741"/> + <use xlink:href="#glyph4-6" x="464.021095" y="331.862741"/> + <use xlink:href="#glyph4-21" x="467.89662" y="331.862741"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-30" x="114.221819" y="343.82609"/> + <use xlink:href="#glyph4-9" x="117.539427" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="126.565714" y="343.82609"/> + <use xlink:href="#glyph4-16" x="129.335369" y="343.82609"/> + <use xlink:href="#glyph4-7" x="134.316764" y="343.82609"/> + <use xlink:href="#glyph4-6" x="137.086419" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="145.006836" y="343.82609"/> + <use xlink:href="#glyph4-10" x="149.988231" y="343.82609"/> + <use xlink:href="#glyph4-13" x="154.411709" y="343.82609"/> + <use xlink:href="#glyph4-1" x="159.393103" y="343.82609"/> + <use xlink:href="#glyph4-3" x="163.816581" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-25" x="166.745641" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="173.66978" y="343.82609"/> + <use xlink:href="#glyph4-1" x="180.862913" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="189.331284" y="343.82609"/> + <use xlink:href="#glyph4-9" x="192.100939" y="343.82609"/> + <use xlink:href="#glyph4-11" x="197.082333" y="343.82609"/> + <use xlink:href="#glyph4-3" x="199.851989" y="343.82609"/> + <use xlink:href="#glyph4-4" x="203.169597" y="343.82609"/> + <use xlink:href="#glyph4-12" x="208.150992" y="343.82609"/> + <use xlink:href="#glyph4-17" x="213.132386" y="343.82609"/> + <use xlink:href="#glyph4-15" x="218.11378" y="343.82609"/> + <use xlink:href="#glyph4-1" x="222.537259" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-33" x="231.015592" y="343.82609"/> + <use xlink:href="#glyph4-10" x="238.208725" y="343.82609"/> + <use xlink:href="#glyph4-38" x="242.632204" y="343.82609"/> + <use xlink:href="#glyph4-1" x="247.055682" y="343.82609"/> + <use xlink:href="#glyph4-14" x="251.47916" y="343.82609"/> + <use xlink:href="#glyph4-14" x="254.248815" y="343.82609"/> + <use xlink:href="#glyph4-1" x="257.018471" y="343.82609"/> + <use xlink:href="#glyph4-25" x="261.441949" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="268.37605" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="276.84442" y="343.82609"/> + <use xlink:href="#glyph4-1" x="280.719945" y="343.82609"/> + <use xlink:href="#glyph4-15" x="285.143423" y="343.82609"/> + <use xlink:href="#glyph4-17" x="289.566902" y="343.82609"/> + <use xlink:href="#glyph4-3" x="294.548296" y="343.82609"/> + <use xlink:href="#glyph4-1" x="297.865905" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="306.344238" y="343.82609"/> + <use xlink:href="#glyph4-1" x="313.537372" y="343.82609"/> + <use xlink:href="#glyph4-2" x="317.96085" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-2" x="326.987136" y="343.82609"/> + <use xlink:href="#glyph4-3" x="331.968531" y="343.82609"/> + <use xlink:href="#glyph4-4" x="335.286139" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="340.028427" y="343.82609"/> + <use xlink:href="#glyph4-6" x="347.22156" y="343.82609"/> + <use xlink:href="#glyph4-1" x="351.097085" y="343.82609"/> + <use xlink:href="#glyph4-3" x="355.520564" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="362.883064" y="343.82609"/> + <use xlink:href="#glyph4-4" x="367.306543" y="343.82609"/> + <use xlink:href="#glyph4-9" x="372.287937" y="343.82609"/> + <use xlink:href="#glyph4-6" x="377.269331" y="343.82609"/> + <use xlink:href="#glyph4-11" x="381.144856" y="343.82609"/> + <use xlink:href="#glyph4-3" x="383.914512" y="343.82609"/> + <use xlink:href="#glyph4-17" x="387.23212" y="343.82609"/> + <use xlink:href="#glyph4-15" x="392.213515" y="343.82609"/> + <use xlink:href="#glyph4-11" x="396.636993" y="343.82609"/> + <use xlink:href="#glyph4-1" x="399.406648" y="343.82609"/> + <use xlink:href="#glyph4-12" x="403.830126" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="412.856413" y="343.82609"/> + <use xlink:href="#glyph4-6" x="417.279891" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="425.210271" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-23" x="433.678642" y="343.82609"/> + <use xlink:href="#glyph4-17" x="441.429691" y="343.82609"/> + <use xlink:href="#glyph4-14" x="446.411086" y="343.82609"/> + <use xlink:href="#glyph4-11" x="449.180741" y="343.82609"/> + <use xlink:href="#glyph4-7" x="451.950396" y="343.82609"/> + <use xlink:href="#glyph4-26" x="454.720052" y="343.82609"/> + <use xlink:href="#glyph4-13" x="458.03766" y="343.82609"/> + <use xlink:href="#glyph4-3" x="463.019055" y="343.82609"/> + <use xlink:href="#glyph4-7" x="466.336663" y="343.82609"/> + <use xlink:href="#glyph4-9" x="469.106319" y="343.82609"/> + <use xlink:href="#glyph4-15" x="474.087713" y="343.82609"/> + <use xlink:href="#glyph4-7" x="478.511191" y="343.82609"/> + <use xlink:href="#glyph4-13" x="481.280847" y="343.82609"/> + <use xlink:href="#glyph4-10" x="486.262241" y="343.82609"/> + <use xlink:href="#glyph4-14" x="490.685719" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-35" x="497.51023" y="343.82609"/> + <use xlink:href="#glyph4-37" x="504.703363" y="343.82609"/> + <use xlink:href="#glyph4-21" x="510.242674" y="343.82609"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-33" x="99.277636" y="355.779436"/> + <use xlink:href="#glyph4-10" x="106.470769" y="355.779436"/> + <use xlink:href="#glyph4-38" x="110.894247" y="355.779436"/> + <use xlink:href="#glyph4-1" x="115.317726" y="355.779436"/> + <use xlink:href="#glyph4-14" x="119.741204" y="355.779436"/> + <use xlink:href="#glyph4-14" x="122.510859" y="355.779436"/> + <use xlink:href="#glyph4-1" x="125.280514" y="355.779436"/> + <use xlink:href="#glyph4-39" x="129.703993" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="132.473648" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-40" x="138.780093" y="355.779436"/> + <use xlink:href="#glyph4-3" x="145.425273" y="355.779436"/> + <use xlink:href="#glyph4-4" x="148.742882" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="153.485169" y="355.779436"/> + <use xlink:href="#glyph4-6" x="160.678303" y="355.779436"/> + <use xlink:href="#glyph4-1" x="164.553828" y="355.779436"/> + <use xlink:href="#glyph4-3" x="168.977306" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-41" x="174.735798" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="181.689825" y="355.779436"/> + <use xlink:href="#glyph4-3" x="186.113303" y="355.779436"/> + <use xlink:href="#glyph4-9" x="189.430911" y="355.779436"/> + <use xlink:href="#glyph4-1" x="194.412306" y="355.779436"/> + <use xlink:href="#glyph4-14" x="198.835784" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="204.03636" y="355.779436"/> + <use xlink:href="#glyph4-6" x="206.806015" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="213.132386" y="355.779436"/> + <use xlink:href="#glyph4-9" x="217.555864" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="224.978142" y="355.779436"/> + <use xlink:href="#glyph4-13" x="229.959536" y="355.779436"/> + <use xlink:href="#glyph4-1" x="234.940931" y="355.779436"/> + <use xlink:href="#glyph4-3" x="239.364409" y="355.779436"/> + <use xlink:href="#glyph4-10" x="242.682018" y="355.779436"/> + <use xlink:href="#glyph4-11" x="247.105496" y="355.779436"/> + <use xlink:href="#glyph4-7" x="249.875151" y="355.779436"/> + <use xlink:href="#glyph4-9" x="252.644806" y="355.779436"/> + <use xlink:href="#glyph4-8" x="257.626201" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="265.058441" y="355.779436"/> + <use xlink:href="#glyph4-20" x="268.933966" y="355.779436"/> + <use xlink:href="#glyph4-6" x="273.91536" y="355.779436"/> + <use xlink:href="#glyph4-11" x="277.790885" y="355.779436"/> + <use xlink:href="#glyph4-1" x="280.560541" y="355.779436"/> + <use xlink:href="#glyph4-23" x="284.984019" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="295.175952" y="355.779436"/> + <use xlink:href="#glyph4-16" x="297.945607" y="355.779436"/> + <use xlink:href="#glyph4-10" x="302.927001" y="355.779436"/> + <use xlink:href="#glyph4-11" x="307.35048" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="312.570981" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-28" x="316.85498" y="355.779436"/> + <use xlink:href="#glyph4-15" x="321.836375" y="355.779436"/> + <use xlink:href="#glyph4-14" x="326.259853" y="355.779436"/> + <use xlink:href="#glyph4-17" x="329.029508" y="355.779436"/> + <use xlink:href="#glyph4-6" x="334.010903" y="355.779436"/> + <use xlink:href="#glyph4-7" x="337.886427" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="340.407013" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="345.238965" y="355.779436"/> + <use xlink:href="#glyph4-14" x="349.662444" y="355.779436"/> + <use xlink:href="#glyph4-20" x="352.432099" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-23" x="359.844414" y="355.779436"/> + <use xlink:href="#glyph4-10" x="367.595464" y="355.779436"/> + <use xlink:href="#glyph4-9" x="372.018942" y="355.779436"/> + <use xlink:href="#glyph4-10" x="377.000336" y="355.779436"/> + <use xlink:href="#glyph4-8" x="381.423814" y="355.779436"/> + <use xlink:href="#glyph4-1" x="386.405209" y="355.779436"/> + <use xlink:href="#glyph4-6" x="390.828687" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-3" x="397.155058" y="355.779436"/> + <use xlink:href="#glyph4-1" x="400.472667" y="355.779436"/> + <use xlink:href="#glyph4-6" x="404.896145" y="355.779436"/> + <use xlink:href="#glyph4-4" x="408.77167" y="355.779436"/> + <use xlink:href="#glyph4-17" x="413.753064" y="355.779436"/> + <use xlink:href="#glyph4-3" x="418.734458" y="355.779436"/> + <use xlink:href="#glyph4-15" x="422.052067" y="355.779436"/> + <use xlink:href="#glyph4-1" x="426.475545" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="433.339907" y="355.779436"/> + <use xlink:href="#glyph4-3" x="438.321301" y="355.779436"/> + <use xlink:href="#glyph4-4" x="441.63891" y="355.779436"/> + <use xlink:href="#glyph4-11" x="446.620304" y="355.779436"/> + <use xlink:href="#glyph4-1" x="449.38996" y="355.779436"/> + <use xlink:href="#glyph4-15" x="453.813438" y="355.779436"/> + <use xlink:href="#glyph4-11" x="458.236916" y="355.779436"/> + <use xlink:href="#glyph4-7" x="461.006571" y="355.779436"/> + <use xlink:href="#glyph4-4" x="463.776227" y="355.779436"/> + <use xlink:href="#glyph4-9" x="468.757621" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="476.189861" y="355.779436"/> + <use xlink:href="#glyph4-9" x="480.61334" y="355.779436"/> + <use xlink:href="#glyph4-12" x="485.594734" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="493.017012" y="355.779436"/> + <use xlink:href="#glyph4-16" x="496.892537" y="355.779436"/> + <use xlink:href="#glyph4-10" x="501.873931" y="355.779436"/> + <use xlink:href="#glyph4-3" x="506.297409" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-26" x="509.425725" y="355.779436"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="99.277636" y="367.732782"/> + <use xlink:href="#glyph4-9" x="102.047291" y="367.732782"/> + <use xlink:href="#glyph4-8" x="107.028685" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="114.988954" y="367.732782"/> + <use xlink:href="#glyph4-15" x="119.412432" y="367.732782"/> + <use xlink:href="#glyph4-3" x="123.83591" y="367.732782"/> + <use xlink:href="#glyph4-4" x="127.153519" y="367.732782"/> + <use xlink:href="#glyph4-6" x="132.134913" y="367.732782"/> + <use xlink:href="#glyph4-6" x="136.010438" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="142.874799" y="367.732782"/> + <use xlink:href="#glyph4-1" x="150.067933" y="367.732782"/> + <use xlink:href="#glyph4-2" x="154.491411" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="162.461642" y="367.732782"/> + <use xlink:href="#glyph4-7" x="166.337167" y="367.732782"/> + <use xlink:href="#glyph4-11" x="169.106822" y="367.732782"/> + <use xlink:href="#glyph4-1" x="171.876478" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="179.288792" y="367.732782"/> + <use xlink:href="#glyph4-3" x="184.270187" y="367.732782"/> + <use xlink:href="#glyph4-7" x="187.587796" y="367.732782"/> + <use xlink:href="#glyph4-9" x="190.357451" y="367.732782"/> + <use xlink:href="#glyph4-15" x="195.338845" y="367.732782"/> + <use xlink:href="#glyph4-7" x="199.762323" y="367.732782"/> + <use xlink:href="#glyph4-13" x="202.531979" y="367.732782"/> + <use xlink:href="#glyph4-10" x="207.513373" y="367.732782"/> + <use xlink:href="#glyph4-14" x="211.936851" y="367.732782"/> + <use xlink:href="#glyph4-6" x="214.706507" y="367.732782"/> + <use xlink:href="#glyph4-21" x="218.582031" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-42" x="225.655612" y="367.732782"/> + <use xlink:href="#glyph4-16" x="231.742875" y="367.732782"/> + <use xlink:href="#glyph4-7" x="236.72427" y="367.732782"/> + <use xlink:href="#glyph4-6" x="239.493925" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="246.358287" y="367.732782"/> + <use xlink:href="#glyph4-4" x="250.781765" y="367.732782"/> + <use xlink:href="#glyph4-9" x="255.763159" y="367.732782"/> + <use xlink:href="#glyph4-6" x="260.744554" y="367.732782"/> + <use xlink:href="#glyph4-11" x="264.620079" y="367.732782"/> + <use xlink:href="#glyph4-3" x="267.389734" y="367.732782"/> + <use xlink:href="#glyph4-17" x="270.707342" y="367.732782"/> + <use xlink:href="#glyph4-15" x="275.688737" y="367.732782"/> + <use xlink:href="#glyph4-11" x="280.112215" y="367.732782"/> + <use xlink:href="#glyph4-7" x="282.88187" y="367.732782"/> + <use xlink:href="#glyph4-4" x="285.651526" y="367.732782"/> + <use xlink:href="#glyph4-9" x="290.63292" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="298.613114" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-28" x="302.897113" y="367.732782"/> + <use xlink:href="#glyph4-13" x="307.878507" y="367.732782"/> + <use xlink:href="#glyph4-4" x="312.859902" y="367.732782"/> + <use xlink:href="#glyph4-6" x="317.841296" y="367.732782"/> + <use xlink:href="#glyph4-1" x="321.716821" y="367.732782"/> + <use xlink:href="#glyph4-6" x="326.140299" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="332.994698" y="367.732782"/> + <use xlink:href="#glyph4-9" x="335.764353" y="367.732782"/> + <use xlink:href="#glyph4-11" x="340.745748" y="367.732782"/> + <use xlink:href="#glyph4-3" x="343.515403" y="367.732782"/> + <use xlink:href="#glyph4-7" x="346.833012" y="367.732782"/> + <use xlink:href="#glyph4-15" x="349.602667" y="367.732782"/> + <use xlink:href="#glyph4-10" x="354.026145" y="367.732782"/> + <use xlink:href="#glyph4-11" x="358.449623" y="367.732782"/> + <use xlink:href="#glyph4-1" x="361.219279" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-12" x="368.631594" y="367.732782"/> + <use xlink:href="#glyph4-1" x="373.612988" y="367.732782"/> + <use xlink:href="#glyph4-6" x="378.036466" y="367.732782"/> + <use xlink:href="#glyph4-7" x="381.911991" y="367.732782"/> + <use xlink:href="#glyph4-8" x="384.681646" y="367.732782"/> + <use xlink:href="#glyph4-9" x="389.663041" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="397.633272" y="367.732782"/> + <use xlink:href="#glyph4-6" x="400.402927" y="367.732782"/> + <use xlink:href="#glyph4-6" x="404.278452" y="367.732782"/> + <use xlink:href="#glyph4-17" x="408.153977" y="367.732782"/> + <use xlink:href="#glyph4-1" x="413.135371" y="367.732782"/> + <use xlink:href="#glyph4-6" x="417.558849" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="424.423211" y="367.732782"/> + <use xlink:href="#glyph4-16" x="427.192866" y="367.732782"/> + <use xlink:href="#glyph4-10" x="432.174261" y="367.732782"/> + <use xlink:href="#glyph4-11" x="436.597739" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-9" x="442.356231" y="367.732782"/> + <use xlink:href="#glyph4-4" x="447.337625" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="455.307856" y="367.732782"/> + <use xlink:href="#glyph4-3" x="460.289251" y="367.732782"/> + <use xlink:href="#glyph4-1" x="463.606859" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="467.791231" y="367.732782"/> + <use xlink:href="#glyph4-7" x="472.772625" y="367.732782"/> + <use xlink:href="#glyph4-4" x="475.54228" y="367.732782"/> + <use xlink:href="#glyph4-17" x="480.523675" y="367.732782"/> + <use xlink:href="#glyph4-6" x="485.505069" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="492.359468" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="499.462936" y="367.732782"/> + <use xlink:href="#glyph4-3" x="504.444331" y="367.732782"/> + <use xlink:href="#glyph4-29" x="507.761939" y="367.732782"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-16" x="99.277636" y="379.686128"/> + <use xlink:href="#glyph4-10" x="104.25903" y="379.686128"/> + <use xlink:href="#glyph4-6" x="108.682508" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="115.138395" y="379.686128"/> + <use xlink:href="#glyph4-12" x="117.908051" y="379.686128"/> + <use xlink:href="#glyph4-1" x="122.889445" y="379.686128"/> + <use xlink:href="#glyph4-9" x="127.312923" y="379.686128"/> + <use xlink:href="#glyph4-11" x="132.294318" y="379.686128"/> + <use xlink:href="#glyph4-7" x="135.063973" y="379.686128"/> + <use xlink:href="#glyph4-43" x="137.833628" y="379.686128"/> + <use xlink:href="#glyph4-1" x="143.372939" y="379.686128"/> + <use xlink:href="#glyph4-12" x="147.796417" y="379.686128"/> + <use xlink:href="#glyph4-25" x="152.777811" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="157.888722" y="379.686128"/> + <use xlink:href="#glyph4-17" x="161.764247" y="379.686128"/> + <use xlink:href="#glyph4-15" x="166.745641" y="379.686128"/> + <use xlink:href="#glyph4-16" x="171.16912" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="178.740839" y="379.686128"/> + <use xlink:href="#glyph4-6" x="183.164317" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-14" x="189.630167" y="379.686128"/> + <use xlink:href="#glyph4-1" x="192.399823" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-8" x="196.683822" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="201.615402" y="379.686128"/> + <use xlink:href="#glyph4-15" x="206.03888" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-20" x="210.312917" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="217.874673" y="379.686128"/> + <use xlink:href="#glyph4-3" x="222.856068" y="379.686128"/> + <use xlink:href="#glyph4-4" x="226.173677" y="379.686128"/> + <use xlink:href="#glyph4-11" x="231.155071" y="379.686128"/> + <use xlink:href="#glyph4-1" x="233.924726" y="379.686128"/> + <use xlink:href="#glyph4-15" x="238.348204" y="379.686128"/> + <use xlink:href="#glyph4-11" x="242.771683" y="379.686128"/> + <use xlink:href="#glyph4-7" x="245.541338" y="379.686128"/> + <use xlink:href="#glyph4-4" x="248.310993" y="379.686128"/> + <use xlink:href="#glyph4-9" x="253.292388" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="260.864107" y="379.686128"/> + <use xlink:href="#glyph4-24" x="265.845502" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="271.753435" y="379.686128"/> + <use xlink:href="#glyph4-3" x="276.176914" y="379.686128"/> + <use xlink:href="#glyph4-4" x="279.494522" y="379.686128"/> + <use xlink:href="#glyph4-6" x="284.475917" y="379.686128"/> + <use xlink:href="#glyph4-6" x="288.351441" y="379.686128"/> + <use xlink:href="#glyph4-26" x="292.226966" y="379.686128"/> + <use xlink:href="#glyph4-4" x="295.544575" y="379.686128"/> + <use xlink:href="#glyph4-3" x="300.525969" y="379.686128"/> + <use xlink:href="#glyph4-7" x="303.843578" y="379.686128"/> + <use xlink:href="#glyph4-8" x="306.613233" y="379.686128"/> + <use xlink:href="#glyph4-7" x="311.594628" y="379.686128"/> + <use xlink:href="#glyph4-9" x="314.364283" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="321.936002" y="379.686128"/> + <use xlink:href="#glyph4-15" x="325.811527" y="379.686128"/> + <use xlink:href="#glyph4-3" x="330.235006" y="379.686128"/> + <use xlink:href="#glyph4-7" x="333.552614" y="379.686128"/> + <use xlink:href="#glyph4-13" x="336.32227" y="379.686128"/> + <use xlink:href="#glyph4-11" x="341.303664" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="346.663644" y="379.686128"/> + <use xlink:href="#glyph4-4" x="350.539169" y="379.686128"/> + <use xlink:href="#glyph4-17" x="355.520564" y="379.686128"/> + <use xlink:href="#glyph4-3" x="360.501958" y="379.686128"/> + <use xlink:href="#glyph4-15" x="363.819567" y="379.686128"/> + <use xlink:href="#glyph4-1" x="368.243045" y="379.686128"/> + <use xlink:href="#glyph4-25" x="372.666523" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="377.767471" y="379.686128"/> + <use xlink:href="#glyph4-9" x="382.190949" y="379.686128"/> + <use xlink:href="#glyph4-12" x="387.172344" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="394.744063" y="379.686128"/> + <use xlink:href="#glyph4-3" x="399.167541" y="379.686128"/> + <use xlink:href="#glyph4-4" x="402.48515" y="379.686128"/> + <use xlink:href="#glyph4-6" x="407.466544" y="379.686128"/> + <use xlink:href="#glyph4-6" x="411.342069" y="379.686128"/> + <use xlink:href="#glyph4-26" x="415.217594" y="379.686128"/> + <use xlink:href="#glyph4-13" x="418.535203" y="379.686128"/> + <use xlink:href="#glyph4-3" x="423.516597" y="379.686128"/> + <use xlink:href="#glyph4-7" x="426.834206" y="379.686128"/> + <use xlink:href="#glyph4-9" x="429.603861" y="379.686128"/> + <use xlink:href="#glyph4-15" x="434.585255" y="379.686128"/> + <use xlink:href="#glyph4-7" x="439.008734" y="379.686128"/> + <use xlink:href="#glyph4-13" x="441.778389" y="379.686128"/> + <use xlink:href="#glyph4-10" x="446.759783" y="379.686128"/> + <use xlink:href="#glyph4-14" x="451.183262" y="379.686128"/> + <use xlink:href="#glyph4-25" x="453.952917" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="459.063828" y="379.686128"/> + <use xlink:href="#glyph4-3" x="463.487306" y="379.686128"/> + <use xlink:href="#glyph4-4" x="466.804914" y="379.686128"/> + <use xlink:href="#glyph4-6" x="471.786309" y="379.686128"/> + <use xlink:href="#glyph4-6" x="475.661834" y="379.686128"/> + <use xlink:href="#glyph4-26" x="479.537359" y="379.686128"/> + <use xlink:href="#glyph4-13" x="482.854967" y="379.686128"/> + <use xlink:href="#glyph4-3" x="487.836362" y="379.686128"/> + <use xlink:href="#glyph4-4" x="491.15397" y="379.686128"/> + <use xlink:href="#glyph4-15" x="496.135365" y="379.686128"/> + <use xlink:href="#glyph4-1" x="500.558843" y="379.686128"/> + <use xlink:href="#glyph4-6" x="504.982321" y="379.686128"/> + <use xlink:href="#glyph4-6" x="508.857846" y="379.686128"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-12" x="99.277636" y="391.639474"/> + <use xlink:href="#glyph4-7" x="104.25903" y="391.639474"/> + <use xlink:href="#glyph4-6" x="107.028685" y="391.639474"/> + <use xlink:href="#glyph4-13" x="110.90421" y="391.639474"/> + <use xlink:href="#glyph4-14" x="115.885605" y="391.639474"/> + <use xlink:href="#glyph4-10" x="118.65526" y="391.639474"/> + <use xlink:href="#glyph4-20" x="123.078738" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="130.540867" y="391.639474"/> + <use xlink:href="#glyph4-9" x="134.964345" y="391.639474"/> + <use xlink:href="#glyph4-12" x="139.945739" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="147.417831" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="151.602202" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="156.434155" y="391.639474"/> + <use xlink:href="#glyph4-9" x="160.857633" y="391.639474"/> + <use xlink:href="#glyph4-11" x="165.839028" y="391.639474"/> + <use xlink:href="#glyph4-6" x="168.608683" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="174.964942" y="391.639474"/> + <use xlink:href="#glyph4-3" x="179.946337" y="391.639474"/> + <use xlink:href="#glyph4-4" x="183.263945" y="391.639474"/> + <use xlink:href="#glyph4-11" x="188.24534" y="391.639474"/> + <use xlink:href="#glyph4-1" x="191.014995" y="391.639474"/> + <use xlink:href="#glyph4-15" x="195.438473" y="391.639474"/> + <use xlink:href="#glyph4-11" x="199.861951" y="391.639474"/> + <use xlink:href="#glyph4-7" x="202.631607" y="391.639474"/> + <use xlink:href="#glyph4-4" x="205.401262" y="391.639474"/> + <use xlink:href="#glyph4-9" x="210.382656" y="391.639474"/> + <use xlink:href="#glyph4-21" x="215.364051" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-0" x="220.943212" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="229.561025" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="236.465237" y="391.639474"/> + <use xlink:href="#glyph4-14" x="240.888716" y="391.639474"/> + <use xlink:href="#glyph4-10" x="243.658371" y="391.639474"/> + <use xlink:href="#glyph4-2" x="248.081849" y="391.639474"/> + <use xlink:href="#glyph4-4" x="253.063244" y="391.639474"/> + <use xlink:href="#glyph4-3" x="258.044638" y="391.639474"/> + <use xlink:href="#glyph4-10" x="261.362247" y="391.639474"/> + <use xlink:href="#glyph4-11" x="265.785725" y="391.639474"/> + <use xlink:href="#glyph4-1" x="268.55538" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="275.469556" y="391.639474"/> + <use xlink:href="#glyph4-9" x="280.45095" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="287.923042" y="391.639474"/> + <use xlink:href="#glyph4-16" x="290.692697" y="391.639474"/> + <use xlink:href="#glyph4-1" x="295.674091" y="391.639474"/> + <use xlink:href="#glyph4-6" x="300.097569" y="391.639474"/> + <use xlink:href="#glyph4-1" x="303.973094" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="310.88727" y="391.639474"/> + <use xlink:href="#glyph4-6" x="313.656925" y="391.639474"/> + <use xlink:href="#glyph4-6" x="317.53245" y="391.639474"/> + <use xlink:href="#glyph4-17" x="321.407975" y="391.639474"/> + <use xlink:href="#glyph4-1" x="326.389369" y="391.639474"/> + <use xlink:href="#glyph4-6" x="330.812847" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="337.179069" y="391.639474"/> + <use xlink:href="#glyph4-9" x="341.602548" y="391.639474"/> + <use xlink:href="#glyph4-12" x="346.583942" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="354.056034" y="391.639474"/> + <use xlink:href="#glyph4-3" x="359.037428" y="391.639474"/> + <use xlink:href="#glyph4-4" x="362.355037" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="367.196952" y="391.639474"/> + <use xlink:href="#glyph4-7" x="372.178346" y="391.639474"/> + <use xlink:href="#glyph4-12" x="374.948002" y="391.639474"/> + <use xlink:href="#glyph4-1" x="379.929396" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="386.833609" y="391.639474"/> + <use xlink:href="#glyph4-4" x="391.257087" y="391.639474"/> + <use xlink:href="#glyph4-23" x="396.238481" y="391.639474"/> + <use xlink:href="#glyph4-13" x="403.989531" y="391.639474"/> + <use xlink:href="#glyph4-3" x="408.970925" y="391.639474"/> + <use xlink:href="#glyph4-1" x="412.288534" y="391.639474"/> + <use xlink:href="#glyph4-16" x="416.712012" y="391.639474"/> + <use xlink:href="#glyph4-1" x="421.693407" y="391.639474"/> + <use xlink:href="#glyph4-9" x="426.116885" y="391.639474"/> + <use xlink:href="#glyph4-6" x="431.098279" y="391.639474"/> + <use xlink:href="#glyph4-7" x="434.973804" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="437.504353" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="442.336305" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="449.240518" y="391.639474"/> + <use xlink:href="#glyph4-4" x="453.116043" y="391.639474"/> + <use xlink:href="#glyph4-14" x="458.097437" y="391.639474"/> + <use xlink:href="#glyph4-17" x="460.867092" y="391.639474"/> + <use xlink:href="#glyph4-11" x="465.848487" y="391.639474"/> + <use xlink:href="#glyph4-7" x="468.618142" y="391.639474"/> + <use xlink:href="#glyph4-4" x="471.387797" y="391.639474"/> + <use xlink:href="#glyph4-9" x="476.369192" y="391.639474"/> + <use xlink:href="#glyph4-6" x="481.350586" y="391.639474"/> + <use xlink:href="#glyph4-21" x="485.226111" y="391.639474"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-35" x="114.221819" y="403.59282"/> + <use xlink:href="#glyph4-17" x="121.414952" y="403.59282"/> + <use xlink:href="#glyph4-3" x="126.396347" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="132.852234" y="403.59282"/> + <use xlink:href="#glyph4-3" x="137.833628" y="403.59282"/> + <use xlink:href="#glyph4-4" x="141.151237" y="403.59282"/> + <use xlink:href="#glyph4-11" x="146.132631" y="403.59282"/> + <use xlink:href="#glyph4-4" x="148.902287" y="403.59282"/> + <use xlink:href="#glyph4-11" x="153.883681" y="403.59282"/> + <use xlink:href="#glyph4-20" x="156.653336" y="403.59282"/> + <use xlink:href="#glyph4-13" x="161.634731" y="403.59282"/> + <use xlink:href="#glyph4-1" x="166.616125" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="174.177882" y="403.59282"/> + <use xlink:href="#glyph4-23" x="176.947537" y="403.59282"/> + <use xlink:href="#glyph4-13" x="184.698587" y="403.59282"/> + <use xlink:href="#glyph4-14" x="189.679981" y="403.59282"/> + <use xlink:href="#glyph4-1" x="192.449636" y="403.59282"/> + <use xlink:href="#glyph4-23" x="196.873115" y="403.59282"/> + <use xlink:href="#glyph4-1" x="204.624164" y="403.59282"/> + <use xlink:href="#glyph4-9" x="209.047643" y="403.59282"/> + <use xlink:href="#glyph4-11" x="214.029037" y="403.59282"/> + <use xlink:href="#glyph4-10" x="216.798692" y="403.59282"/> + <use xlink:href="#glyph4-11" x="221.222171" y="403.59282"/> + <use xlink:href="#glyph4-7" x="223.991826" y="403.59282"/> + <use xlink:href="#glyph4-4" x="226.761481" y="403.59282"/> + <use xlink:href="#glyph4-9" x="231.742875" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="239.872511" y="403.59282"/> + <use xlink:href="#glyph4-9" x="244.295989" y="403.59282"/> + <use xlink:href="#glyph4-12" x="249.277384" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="257.407019" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="261.591391" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="266.323715" y="403.59282"/> + <use xlink:href="#glyph4-14" x="270.747194" y="403.59282"/> + <use xlink:href="#glyph4-17" x="273.516849" y="403.59282"/> + <use xlink:href="#glyph4-10" x="278.498243" y="403.59282"/> + <use xlink:href="#glyph4-11" x="282.921722" y="403.59282"/> + <use xlink:href="#glyph4-7" x="285.691377" y="403.59282"/> + <use xlink:href="#glyph4-4" x="288.461032" y="403.59282"/> + <use xlink:href="#glyph4-9" x="293.442427" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="301.562099" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-28" x="305.846099" y="403.59282"/> + <use xlink:href="#glyph4-13" x="310.827493" y="403.59282"/> + <use xlink:href="#glyph4-1" x="315.808887" y="403.59282"/> + <use xlink:href="#glyph4-3" x="320.232366" y="403.59282"/> + <use xlink:href="#glyph4-7" x="323.549974" y="403.59282"/> + <use xlink:href="#glyph4-1" x="326.31963" y="403.59282"/> + <use xlink:href="#glyph4-9" x="330.743108" y="403.59282"/> + <use xlink:href="#glyph4-15" x="335.724502" y="403.59282"/> + <use xlink:href="#glyph4-1" x="340.14798" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="347.699774" y="403.59282"/> + <use xlink:href="#glyph4-9" x="350.46943" y="403.59282"/> + <use xlink:href="#glyph4-12" x="355.450824" y="403.59282"/> + <use xlink:href="#glyph4-7" x="360.432218" y="403.59282"/> + <use xlink:href="#glyph4-15" x="363.201874" y="403.59282"/> + <use xlink:href="#glyph4-10" x="367.625352" y="403.59282"/> + <use xlink:href="#glyph4-11" x="372.04883" y="403.59282"/> + <use xlink:href="#glyph4-1" x="374.818485" y="403.59282"/> + <use xlink:href="#glyph4-6" x="379.241964" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="386.26573" y="403.59282"/> + <use xlink:href="#glyph4-16" x="389.035385" y="403.59282"/> + <use xlink:href="#glyph4-10" x="394.016779" y="403.59282"/> + <use xlink:href="#glyph4-11" x="398.440258" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="404.358154" y="403.59282"/> + <use xlink:href="#glyph4-11" x="407.12781" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="413.035743" y="403.59282"/> + <use xlink:href="#glyph4-6" x="415.805399" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-3" x="422.829165" y="403.59282"/> + <use xlink:href="#glyph4-1" x="426.146773" y="403.59282"/> + <use xlink:href="#glyph4-10" x="430.570252" y="403.59282"/> + <use xlink:href="#glyph4-14" x="434.99373" y="403.59282"/> + <use xlink:href="#glyph4-7" x="437.763385" y="403.59282"/> + <use xlink:href="#glyph4-6" x="440.53304" y="403.59282"/> + <use xlink:href="#glyph4-11" x="444.408565" y="403.59282"/> + <use xlink:href="#glyph4-7" x="447.17822" y="403.59282"/> + <use xlink:href="#glyph4-15" x="449.947876" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="457.519595" y="403.59282"/> + <use xlink:href="#glyph4-4" x="460.289251" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="468.418886" y="403.59282"/> + <use xlink:href="#glyph4-17" x="471.188542" y="403.59282"/> + <use xlink:href="#glyph4-3" x="476.169936" y="403.59282"/> + <use xlink:href="#glyph4-9" x="479.487545" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="487.607217" y="403.59282"/> + <use xlink:href="#glyph4-9" x="492.030696" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="500.160331" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-28" x="504.444331" y="403.59282"/> + <use xlink:href="#glyph4-26" x="509.425725" y="403.59282"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="99.277636" y="415.556169"/> + <use xlink:href="#glyph4-6" x="102.047291" y="415.556169"/> + <use xlink:href="#glyph4-11" x="105.922816" y="415.556169"/> + <use xlink:href="#glyph4-7" x="108.692471" y="415.556169"/> + <use xlink:href="#glyph4-9" x="111.462126" y="415.556169"/> + <use xlink:href="#glyph4-8" x="116.443521" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-2" x="124.563194" y="415.556169"/> + <use xlink:href="#glyph4-3" x="129.544588" y="415.556169"/> + <use xlink:href="#glyph4-4" x="132.862197" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="137.604484" y="415.556169"/> + <use xlink:href="#glyph4-6" x="144.797618" y="415.556169"/> + <use xlink:href="#glyph4-1" x="148.673142" y="415.556169"/> + <use xlink:href="#glyph4-3" x="153.096621" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="159.542545" y="415.556169"/> + <use xlink:href="#glyph4-9" x="162.3122" y="415.556169"/> + <use xlink:href="#glyph4-11" x="167.293595" y="415.556169"/> + <use xlink:href="#glyph4-4" x="170.06325" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="178.192886" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-23" x="185.764605" y="415.556169"/> + <use xlink:href="#glyph4-17" x="193.515655" y="415.556169"/> + <use xlink:href="#glyph4-14" x="198.497049" y="415.556169"/> + <use xlink:href="#glyph4-11" x="201.266705" y="415.556169"/> + <use xlink:href="#glyph4-7" x="204.03636" y="415.556169"/> + <use xlink:href="#glyph4-26" x="206.806015" y="415.556169"/> + <use xlink:href="#glyph4-13" x="210.123624" y="415.556169"/> + <use xlink:href="#glyph4-3" x="215.105018" y="415.556169"/> + <use xlink:href="#glyph4-7" x="218.422627" y="415.556169"/> + <use xlink:href="#glyph4-9" x="221.192282" y="415.556169"/> + <use xlink:href="#glyph4-15" x="226.173677" y="415.556169"/> + <use xlink:href="#glyph4-7" x="230.597155" y="415.556169"/> + <use xlink:href="#glyph4-13" x="233.36681" y="415.556169"/> + <use xlink:href="#glyph4-10" x="238.348204" y="415.556169"/> + <use xlink:href="#glyph4-14" x="242.771683" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-35" x="248.679616" y="415.556169"/> + <use xlink:href="#glyph4-37" x="255.87275" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="264.560302" y="415.556169"/> + <use xlink:href="#glyph4-16" x="267.329957" y="415.556169"/> + <use xlink:href="#glyph4-10" x="272.311351" y="415.556169"/> + <use xlink:href="#glyph4-11" x="276.73483" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-20" x="282.642763" y="415.556169"/> + <use xlink:href="#glyph4-7" x="287.624158" y="415.556169"/> + <use xlink:href="#glyph4-1" x="290.393813" y="415.556169"/> + <use xlink:href="#glyph4-14" x="294.817291" y="415.556169"/> + <use xlink:href="#glyph4-12" x="297.586947" y="415.556169"/> + <use xlink:href="#glyph4-6" x="302.568341" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="309.592107" y="415.556169"/> + <use xlink:href="#glyph4-7" x="313.467632" y="415.556169"/> + <use xlink:href="#glyph4-8" x="316.237287" y="415.556169"/> + <use xlink:href="#glyph4-9" x="321.218682" y="415.556169"/> + <use xlink:href="#glyph4-7" x="326.200076" y="415.556169"/> + <use xlink:href="#glyph4-43" x="328.969731" y="415.556169"/> + <use xlink:href="#glyph4-15" x="334.509042" y="415.556169"/> + <use xlink:href="#glyph4-10" x="338.93252" y="415.556169"/> + <use xlink:href="#glyph4-9" x="343.355998" y="415.556169"/> + <use xlink:href="#glyph4-11" x="348.337393" y="415.556169"/> + <use xlink:href="#glyph4-14" x="351.107048" y="415.556169"/> + <use xlink:href="#glyph4-20" x="353.876703" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="362.006339" y="415.556169"/> + <use xlink:href="#glyph4-11" x="365.881864" y="415.556169"/> + <use xlink:href="#glyph4-3" x="368.651519" y="415.556169"/> + <use xlink:href="#glyph4-4" x="371.969128" y="415.556169"/> + <use xlink:href="#glyph4-9" x="376.950522" y="415.556169"/> + <use xlink:href="#glyph4-8" x="381.931917" y="415.556169"/> + <use xlink:href="#glyph4-1" x="386.913311" y="415.556169"/> + <use xlink:href="#glyph4-3" x="391.336789" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="397.792676" y="415.556169"/> + <use xlink:href="#glyph4-1" x="401.668201" y="415.556169"/> + <use xlink:href="#glyph4-15" x="406.091679" y="415.556169"/> + <use xlink:href="#glyph4-17" x="410.515158" y="415.556169"/> + <use xlink:href="#glyph4-3" x="415.496552" y="415.556169"/> + <use xlink:href="#glyph4-7" x="418.814161" y="415.556169"/> + <use xlink:href="#glyph4-11" x="421.583816" y="415.556169"/> + <use xlink:href="#glyph4-20" x="424.353471" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="432.483107" y="415.556169"/> + <use xlink:href="#glyph4-9" x="436.906585" y="415.556169"/> + <use xlink:href="#glyph4-12" x="441.88798" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-3" x="450.007653" y="415.556169"/> + <use xlink:href="#glyph4-4" x="453.325261" y="415.556169"/> + <use xlink:href="#glyph4-2" x="458.306656" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-17" x="463.098757" y="415.556169"/> + <use xlink:href="#glyph4-6" x="468.080151" y="415.556169"/> + <use xlink:href="#glyph4-11" x="471.955676" y="415.556169"/> + <use xlink:href="#glyph4-9" x="474.725332" y="415.556169"/> + <use xlink:href="#glyph4-1" x="479.706726" y="415.556169"/> + <use xlink:href="#glyph4-6" x="484.130204" y="415.556169"/> + <use xlink:href="#glyph4-6" x="488.005729" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="495.019532" y="415.556169"/> + <use xlink:href="#glyph4-7" x="502.212666" y="415.556169"/> + <use xlink:href="#glyph4-11" x="504.982321" y="415.556169"/> + <use xlink:href="#glyph4-16" x="507.751976" y="415.556169"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="99.277636" y="427.509515"/> + <use xlink:href="#glyph4-15" x="103.701114" y="427.509515"/> + <use xlink:href="#glyph4-15" x="108.124592" y="427.509515"/> + <use xlink:href="#glyph4-1" x="112.54807" y="427.509515"/> + <use xlink:href="#glyph4-13" x="116.971549" y="427.509515"/> + <use xlink:href="#glyph4-11" x="121.952943" y="427.509515"/> + <use xlink:href="#glyph4-10" x="124.722598" y="427.509515"/> + <use xlink:href="#glyph4-2" x="129.146076" y="427.509515"/> + <use xlink:href="#glyph4-14" x="134.127471" y="427.509515"/> + <use xlink:href="#glyph4-1" x="136.897126" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="144.857394" y="427.509515"/> + <use xlink:href="#glyph4-1" x="149.838789" y="427.509515"/> + <use xlink:href="#glyph4-3" x="154.262267" y="427.509515"/> + <use xlink:href="#glyph4-24" x="157.579876" y="427.509515"/> + <use xlink:href="#glyph4-4" x="160.897484" y="427.509515"/> + <use xlink:href="#glyph4-3" x="165.878879" y="427.509515"/> + <use xlink:href="#glyph4-23" x="169.196487" y="427.509515"/> + <use xlink:href="#glyph4-10" x="176.947537" y="427.509515"/> + <use xlink:href="#glyph4-9" x="181.371015" y="427.509515"/> + <use xlink:href="#glyph4-15" x="186.35241" y="427.509515"/> + <use xlink:href="#glyph4-1" x="190.775888" y="427.509515"/> + <use xlink:href="#glyph4-21" x="195.199366" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-35" x="203.956658" y="427.509515"/> + <use xlink:href="#glyph4-17" x="211.149791" y="427.509515"/> + <use xlink:href="#glyph4-3" x="216.131185" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="222.995547" y="427.509515"/> + <use xlink:href="#glyph4-1" x="226.871072" y="427.509515"/> + <use xlink:href="#glyph4-15" x="231.29455" y="427.509515"/> + <use xlink:href="#glyph4-17" x="235.718028" y="427.509515"/> + <use xlink:href="#glyph4-3" x="240.699423" y="427.509515"/> + <use xlink:href="#glyph4-7" x="244.017031" y="427.509515"/> + <use xlink:href="#glyph4-11" x="246.786687" y="427.509515"/> + <use xlink:href="#glyph4-20" x="249.556342" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="258.084489" y="427.509515"/> + <use xlink:href="#glyph4-4" x="263.065883" y="427.509515"/> + <use xlink:href="#glyph4-14" x="268.047278" y="427.509515"/> + <use xlink:href="#glyph4-7" x="270.816933" y="427.509515"/> + <use xlink:href="#glyph4-15" x="273.586588" y="427.509515"/> + <use xlink:href="#glyph4-7" x="278.010067" y="427.509515"/> + <use xlink:href="#glyph4-1" x="280.779722" y="427.509515"/> + <use xlink:href="#glyph4-6" x="285.2032" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-13" x="292.635441" y="427.509515"/> + <use xlink:href="#glyph4-4" x="297.616835" y="427.509515"/> + <use xlink:href="#glyph4-6" x="302.598229" y="427.509515"/> + <use xlink:href="#glyph4-1" x="306.473754" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-6" x="314.443985" y="427.509515"/> + <use xlink:href="#glyph4-4" x="318.31951" y="427.509515"/> + <use xlink:href="#glyph4-23" x="323.300905" y="427.509515"/> + <use xlink:href="#glyph4-1" x="331.051954" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="339.022185" y="427.509515"/> + <use xlink:href="#glyph4-9" x="341.791841" y="427.509515"/> + <use xlink:href="#glyph4-15" x="346.773235" y="427.509515"/> + <use xlink:href="#glyph4-4" x="351.196713" y="427.509515"/> + <use xlink:href="#glyph4-23" x="356.178108" y="427.509515"/> + <use xlink:href="#glyph4-13" x="363.929157" y="427.509515"/> + <use xlink:href="#glyph4-10" x="368.910552" y="427.509515"/> + <use xlink:href="#glyph4-11" x="373.33403" y="427.509515"/> + <use xlink:href="#glyph4-7" x="376.103685" y="427.509515"/> + <use xlink:href="#glyph4-2" x="378.87334" y="427.509515"/> + <use xlink:href="#glyph4-7" x="383.854735" y="427.509515"/> + <use xlink:href="#glyph4-14" x="386.62439" y="427.509515"/> + <use xlink:href="#glyph4-7" x="389.394045" y="427.509515"/> + <use xlink:href="#glyph4-11" x="392.163701" y="427.509515"/> + <use xlink:href="#glyph4-20" x="394.933356" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-25" x="399.277132" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-11" x="405.573614" y="427.509515"/> + <use xlink:href="#glyph4-16" x="408.34327" y="427.509515"/> + <use xlink:href="#glyph4-1" x="413.324664" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-15" x="421.294895" y="427.509515"/> + <use xlink:href="#glyph4-4" x="425.718373" y="427.509515"/> + <use xlink:href="#glyph4-6" x="430.699768" y="427.509515"/> + <use xlink:href="#glyph4-11" x="434.575293" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-4" x="440.891701" y="427.509515"/> + <use xlink:href="#glyph4-24" x="445.873095" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-5" x="452.747419" y="427.509515"/> + <use xlink:href="#glyph4-16" x="459.940553" y="427.509515"/> + <use xlink:href="#glyph4-7" x="464.921947" y="427.509515"/> + <use xlink:href="#glyph4-15" x="467.691603" y="427.509515"/> + <use xlink:href="#glyph4-16" x="472.115081" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-3" x="480.643228" y="427.509515"/> + <use xlink:href="#glyph4-1" x="483.960837" y="427.509515"/> + <use xlink:href="#glyph4-19" x="488.384315" y="427.509515"/> + <use xlink:href="#glyph4-17" x="493.365709" y="427.509515"/> + <use xlink:href="#glyph4-7" x="498.347104" y="427.509515"/> + <use xlink:href="#glyph4-3" x="501.116759" y="427.509515"/> + <use xlink:href="#glyph4-1" x="504.434368" y="427.509515"/> + <use xlink:href="#glyph4-6" x="508.857846" y="427.509515"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-24" x="99.277636" y="439.462861"/> + <use xlink:href="#glyph4-17" x="102.595244" y="439.462861"/> + <use xlink:href="#glyph4-3" x="107.576639" y="439.462861"/> + <use xlink:href="#glyph4-11" x="110.894247" y="439.462861"/> + <use xlink:href="#glyph4-16" x="113.663903" y="439.462861"/> + <use xlink:href="#glyph4-1" x="118.645297" y="439.462861"/> + <use xlink:href="#glyph4-3" x="123.068775" y="439.462861"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-7" x="128.867118" y="439.462861"/> + <use xlink:href="#glyph4-9" x="131.636774" y="439.462861"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-18" x="136.229619" y="439.462861"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-1" x="141.061572" y="439.462861"/> + <use xlink:href="#glyph4-6" x="145.48505" y="439.462861"/> + <use xlink:href="#glyph4-11" x="149.360575" y="439.462861"/> + <use xlink:href="#glyph4-7" x="152.13023" y="439.462861"/> + <use xlink:href="#glyph4-8" x="154.899885" y="439.462861"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph4-10" x="159.831466" y="439.462861"/> + <use xlink:href="#glyph4-11" x="164.254944" y="439.462861"/> + <use xlink:href="#glyph4-7" x="167.024599" y="439.462861"/> + <use xlink:href="#glyph4-4" x="169.794255" y="439.462861"/> + <use xlink:href="#glyph4-9" x="174.775649" y="439.462861"/> + <use xlink:href="#glyph4-21" x="179.757044" y="439.462861"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph6-0" x="72" y="474.572689"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph6-1" x="93.516023" y="474.572689"/> + <use xlink:href="#glyph6-2" x="99.095845" y="474.572689"/> + <use xlink:href="#glyph6-3" x="107.071117" y="474.572689"/> + <use xlink:href="#glyph6-4" x="111.847674" y="474.572689"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph6-5" x="117.972569" y="474.572689"/> + <use xlink:href="#glyph6-6" x="125.144576" y="474.572689"/> + <use xlink:href="#glyph6-7" x="133.119849" y="474.572689"/> + <use xlink:href="#glyph6-8" x="141.095121" y="474.572689"/> + <use xlink:href="#glyph6-3" x="147.463864" y="474.572689"/> + <use xlink:href="#glyph6-9" x="152.240421" y="474.572689"/> + <use xlink:href="#glyph6-5" x="156.228057" y="474.572689"/> + <use xlink:href="#glyph6-2" x="163.400065" y="474.572689"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-0" x="72" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="81.439792" y="499.419644"/> + <use xlink:href="#glyph7-2" x="86.285189" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="96.357938" y="499.419644"/> + <use xlink:href="#glyph7-3" x="101.814466" y="499.419644"/> + <use xlink:href="#glyph7-4" x="105.448513" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="110.643127" y="499.419644"/> + <use xlink:href="#glyph7-6" x="118.522353" y="499.419644"/> + <use xlink:href="#glyph7-1" x="122.767531" y="499.419644"/> + <use xlink:href="#glyph7-3" x="127.612927" y="499.419644"/> + <use xlink:href="#glyph7-6" x="131.246975" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-7" x="140.119288" y="499.419644"/> + <use xlink:href="#glyph7-8" x="145.575815" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="150.213864" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="155.506695" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="164.979227" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="169.56271" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="174.800976" y="499.419644"/> + <use xlink:href="#glyph7-10" x="180.257504" y="499.419644"/> + <use xlink:href="#glyph7-9" x="183.291333" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="188.584164" y="499.419644"/> + <use xlink:href="#glyph7-11" x="193.429561" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="203.513223" y="499.419644"/> + <use xlink:href="#glyph7-4" x="206.547053" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="216.630715" y="499.419644"/> + <use xlink:href="#glyph7-1" x="222.087243" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="231.570687" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-13" x="241.054132" y="499.419644"/> + <use xlink:href="#glyph7-14" x="249.544489" y="499.419644"/> + <use xlink:href="#glyph7-10" x="255.001016" y="499.419644"/> + <use xlink:href="#glyph7-12" x="258.034845" y="499.419644"/> + <use xlink:href="#glyph7-15" x="261.068674" y="499.419644"/> + <use xlink:href="#glyph7-16" x="264.102504" y="499.419644"/> + <use xlink:href="#glyph7-17" x="267.736551" y="499.419644"/> + <use xlink:href="#glyph7-3" x="273.193078" y="499.419644"/> + <use xlink:href="#glyph7-15" x="276.827126" y="499.419644"/> + <use xlink:href="#glyph7-18" x="279.860955" y="499.419644"/> + <use xlink:href="#glyph7-19" x="285.317482" y="499.419644"/> + <use xlink:href="#glyph7-15" x="290.162879" y="499.419644"/> + <use xlink:href="#glyph7-17" x="293.196708" y="499.419644"/> + <use xlink:href="#glyph7-8" x="298.653235" y="499.419644"/> + <use xlink:href="#glyph7-10" x="303.498631" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="311.170509" y="499.419644"/> + <use xlink:href="#glyph7-17" x="316.627036" y="499.419644"/> + <use xlink:href="#glyph7-1" x="322.083564" y="499.419644"/> + <use xlink:href="#glyph7-3" x="326.92896" y="499.419644"/> + <use xlink:href="#glyph7-8" x="330.563007" y="499.419644"/> + <use xlink:href="#glyph7-12" x="335.408404" y="499.419644"/> + <use xlink:href="#glyph7-15" x="338.442233" y="499.419644"/> + <use xlink:href="#glyph7-18" x="341.476062" y="499.419644"/> + <use xlink:href="#glyph7-20" x="346.93259" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="357.016252" y="499.419644"/> + <use xlink:href="#glyph7-18" x="361.861649" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="366.892567" y="499.419644"/> + <use xlink:href="#glyph7-15" x="372.349094" y="499.419644"/> + <use xlink:href="#glyph7-3" x="375.382923" y="499.419644"/> + <use xlink:href="#glyph7-4" x="379.016971" y="499.419644"/> + <use xlink:href="#glyph7-18" x="384.473498" y="499.419644"/> + <use xlink:href="#glyph7-13" x="389.930025" y="499.419644"/> + <use xlink:href="#glyph7-1" x="398.420382" y="499.419644"/> + <use xlink:href="#glyph7-18" x="403.265778" y="499.419644"/> + <use xlink:href="#glyph7-12" x="408.722306" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="416.38327" y="499.419644"/> + <use xlink:href="#glyph7-7" x="424.262496" y="499.419644"/> + <use xlink:href="#glyph7-1" x="429.719023" y="499.419644"/> + <use xlink:href="#glyph7-3" x="434.56442" y="499.419644"/> + <use xlink:href="#glyph7-1" x="438.198467" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="447.681911" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="457.165356" y="499.419644"/> + <use xlink:href="#glyph7-3" x="462.621884" y="499.419644"/> + <use xlink:href="#glyph7-15" x="466.255931" y="499.419644"/> + <use xlink:href="#glyph7-18" x="469.28976" y="499.419644"/> + <use xlink:href="#glyph7-19" x="474.746287" y="499.419644"/> + <use xlink:href="#glyph7-15" x="479.591684" y="499.419644"/> + <use xlink:href="#glyph7-17" x="482.625513" y="499.419644"/> + <use xlink:href="#glyph7-8" x="488.08204" y="499.419644"/> + <use xlink:href="#glyph7-10" x="492.927437" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="500.588401" y="499.419644"/> + <use xlink:href="#glyph7-6" x="503.62223" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="512.505457" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="521.988902" y="499.419644"/> + <use xlink:href="#glyph7-1" x="529.868127" y="499.419644"/> + <use xlink:href="#glyph7-2" x="534.713524" y="499.419644"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="72" y="512.963435"/> + <use xlink:href="#glyph7-15" x="76.245178" y="512.963435"/> + <use xlink:href="#glyph7-12" x="79.279008" y="512.963435"/> + <use xlink:href="#glyph7-1" x="82.312837" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="90.00654" y="512.963435"/> + <use xlink:href="#glyph7-22" x="93.640588" y="512.963435"/> + <use xlink:href="#glyph7-23" x="99.097115" y="512.963435"/> + <use xlink:href="#glyph7-24" x="104.553642" y="512.963435"/> + <use xlink:href="#glyph7-25" x="108.18769" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-26" x="114.670044" y="512.963435"/> + <use xlink:href="#glyph7-15" x="120.737703" y="512.963435"/> + <use xlink:href="#glyph7-13" x="123.771532" y="512.963435"/> + <use xlink:href="#glyph7-15" x="132.261889" y="512.963435"/> + <use xlink:href="#glyph7-10" x="135.295718" y="512.963435"/> + <use xlink:href="#glyph7-8" x="138.329547" y="512.963435"/> + <use xlink:href="#glyph7-3" x="143.174943" y="512.963435"/> + <use xlink:href="#glyph7-10" x="146.808991" y="512.963435"/> + <use xlink:href="#glyph7-27" x="149.84282" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="158.147655" y="512.963435"/> + <use xlink:href="#glyph7-4" x="161.181484" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="169.486319" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-13" x="177.180022" y="512.963435"/> + <use xlink:href="#glyph7-14" x="185.670379" y="512.963435"/> + <use xlink:href="#glyph7-10" x="191.126906" y="512.963435"/> + <use xlink:href="#glyph7-12" x="194.160735" y="512.963435"/> + <use xlink:href="#glyph7-15" x="197.194565" y="512.963435"/> + <use xlink:href="#glyph7-16" x="200.228394" y="512.963435"/> + <use xlink:href="#glyph7-17" x="203.862441" y="512.963435"/> + <use xlink:href="#glyph7-3" x="209.318969" y="512.963435"/> + <use xlink:href="#glyph7-15" x="212.953016" y="512.963435"/> + <use xlink:href="#glyph7-18" x="215.986845" y="512.963435"/> + <use xlink:href="#glyph7-19" x="221.443372" y="512.963435"/> + <use xlink:href="#glyph7-15" x="226.288769" y="512.963435"/> + <use xlink:href="#glyph7-17" x="229.322598" y="512.963435"/> + <use xlink:href="#glyph7-8" x="234.779125" y="512.963435"/> + <use xlink:href="#glyph7-10" x="239.624522" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="245.517571" y="512.963435"/> + <use xlink:href="#glyph7-26" x="253.396797" y="512.963435"/> + <use xlink:href="#glyph7-29" x="259.464455" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-3" x="265.041026" y="512.963435"/> + <use xlink:href="#glyph7-1" x="268.675074" y="512.963435"/> + <use xlink:href="#glyph7-19" x="273.52047" y="512.963435"/> + <use xlink:href="#glyph7-1" x="278.365866" y="512.963435"/> + <use xlink:href="#glyph7-18" x="283.211263" y="512.963435"/> + <use xlink:href="#glyph7-12" x="288.66779" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="294.549927" y="512.963435"/> + <use xlink:href="#glyph7-3" x="300.006454" y="512.963435"/> + <use xlink:href="#glyph7-4" x="303.640501" y="512.963435"/> + <use xlink:href="#glyph7-17" x="309.097029" y="512.963435"/> + <use xlink:href="#glyph7-4" x="314.553556" y="512.963435"/> + <use xlink:href="#glyph7-6" x="320.010083" y="512.963435"/> + <use xlink:href="#glyph7-8" x="324.255262" y="512.963435"/> + <use xlink:href="#glyph7-10" x="329.100658" y="512.963435"/> + <use xlink:href="#glyph7-6" x="332.134487" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="339.227973" y="512.963435"/> + <use xlink:href="#glyph7-30" x="342.86202" y="512.963435"/> + <use xlink:href="#glyph7-30" x="348.318548" y="512.963435"/> + <use xlink:href="#glyph7-29" x="353.775075" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-30" x="358.00934" y="512.963435"/> + <use xlink:href="#glyph7-31" x="363.465868" y="512.963435"/> + <use xlink:href="#glyph7-29" x="368.922395" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-31" x="373.145747" y="512.963435"/> + <use xlink:href="#glyph7-31" x="378.602275" y="512.963435"/> + <use xlink:href="#glyph7-29" x="384.058802" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-22" x="388.293067" y="512.963435"/> + <use xlink:href="#glyph7-23" x="393.749595" y="512.963435"/> + <use xlink:href="#glyph7-29" x="399.206122" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-32" x="403.429474" y="512.963435"/> + <use xlink:href="#glyph7-31" x="408.886002" y="512.963435"/> + <use xlink:href="#glyph7-24" x="414.342529" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="420.835797" y="512.963435"/> + <use xlink:href="#glyph7-18" x="425.681193" y="512.963435"/> + <use xlink:href="#glyph7-11" x="431.13772" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="439.442555" y="512.963435"/> + <use xlink:href="#glyph7-3" x="444.899083" y="512.963435"/> + <use xlink:href="#glyph7-4" x="448.53313" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="453.727744" y="512.963435"/> + <use xlink:href="#glyph7-6" x="461.606969" y="512.963435"/> + <use xlink:href="#glyph7-1" x="465.852148" y="512.963435"/> + <use xlink:href="#glyph7-3" x="470.697544" y="512.963435"/> + <use xlink:href="#glyph7-6" x="474.331591" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-10" x="481.414164" y="512.963435"/> + <use xlink:href="#glyph7-15" x="484.447993" y="512.963435"/> + <use xlink:href="#glyph7-33" x="487.481822" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="492.840132" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-34" x="500.522923" y="512.963435"/> + <use xlink:href="#glyph7-35" x="504.15697" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-36" x="513.684067" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="521.988902" y="512.963435"/> + <use xlink:href="#glyph7-22" x="525.622949" y="512.963435"/> + <use xlink:href="#glyph7-37" x="531.079476" y="512.963435"/> + <use xlink:href="#glyph7-24" x="536.536004" y="512.963435"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="72" y="526.517229"/> + <use xlink:href="#glyph7-18" x="76.845396" y="526.517229"/> + <use xlink:href="#glyph7-11" x="82.301924" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-38" x="89.821018" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="95.89959" y="526.517229"/> + <use xlink:href="#glyph7-3" x="98.933419" y="526.517229"/> + <use xlink:href="#glyph7-1" x="102.567466" y="526.517229"/> + <use xlink:href="#glyph7-39" x="107.412863" y="526.517229"/> + <use xlink:href="#glyph7-4" x="111.04691" y="526.517229"/> + <use xlink:href="#glyph7-40" x="116.503437" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-22" x="124.022532" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="131.563453" y="526.517229"/> + <use xlink:href="#glyph7-30" x="135.1975" y="526.517229"/> + <use xlink:href="#glyph7-41" x="140.654028" y="526.517229"/> + <use xlink:href="#glyph7-24" x="146.110555" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="151.80717" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-11" x="156.663479" y="526.517229"/> + <use xlink:href="#glyph7-9" x="162.120007" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="167.369186" y="526.517229"/> + <use xlink:href="#glyph7-19" x="172.825713" y="526.517229"/> + <use xlink:href="#glyph7-8" x="177.67111" y="526.517229"/> + <use xlink:href="#glyph7-12" x="182.516506" y="526.517229"/> + <use xlink:href="#glyph7-1" x="185.550335" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="192.458299" y="526.517229"/> + <use xlink:href="#glyph7-18" x="197.303695" y="526.517229"/> + <use xlink:href="#glyph7-11" x="202.760223" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="210.29023" y="526.517229"/> + <use xlink:href="#glyph7-14" x="214.535409" y="526.517229"/> + <use xlink:href="#glyph7-17" x="219.991936" y="526.517229"/> + <use xlink:href="#glyph7-17" x="225.448464" y="526.517229"/> + <use xlink:href="#glyph7-4" x="230.904991" y="526.517229"/> + <use xlink:href="#glyph7-3" x="236.361518" y="526.517229"/> + <use xlink:href="#glyph7-12" x="239.995566" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="245.102875" y="526.517229"/> + <use xlink:href="#glyph7-2" x="249.948272" y="526.517229"/> + <use xlink:href="#glyph7-6" x="255.404799" y="526.517229"/> + <use xlink:href="#glyph7-12" x="259.649977" y="526.517229"/> + <use xlink:href="#glyph7-3" x="262.683807" y="526.517229"/> + <use xlink:href="#glyph7-8" x="266.317854" y="526.517229"/> + <use xlink:href="#glyph7-19" x="271.16325" y="526.517229"/> + <use xlink:href="#glyph7-12" x="276.008646" y="526.517229"/> + <use xlink:href="#glyph7-15" x="279.042476" y="526.517229"/> + <use xlink:href="#glyph7-4" x="282.076305" y="526.517229"/> + <use xlink:href="#glyph7-18" x="287.532832" y="526.517229"/> + <use xlink:href="#glyph7-6" x="292.98936" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-39" x="299.308018" y="526.517229"/> + <use xlink:href="#glyph7-4" x="302.942066" y="526.517229"/> + <use xlink:href="#glyph7-3" x="308.398593" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="314.106121" y="526.517229"/> + <use xlink:href="#glyph7-3" x="318.951517" y="526.517229"/> + <use xlink:href="#glyph7-4" x="322.585564" y="526.517229"/> + <use xlink:href="#glyph7-6" x="328.042092" y="526.517229"/> + <use xlink:href="#glyph7-6" x="332.28727" y="526.517229"/> + <use xlink:href="#glyph7-16" x="336.532448" y="526.517229"/> + <use xlink:href="#glyph7-17" x="340.166496" y="526.517229"/> + <use xlink:href="#glyph7-3" x="345.623023" y="526.517229"/> + <use xlink:href="#glyph7-15" x="349.25707" y="526.517229"/> + <use xlink:href="#glyph7-18" x="352.290899" y="526.517229"/> + <use xlink:href="#glyph7-19" x="357.747427" y="526.517229"/> + <use xlink:href="#glyph7-15" x="362.592823" y="526.517229"/> + <use xlink:href="#glyph7-17" x="365.626652" y="526.517229"/> + <use xlink:href="#glyph7-8" x="371.08318" y="526.517229"/> + <use xlink:href="#glyph7-10" x="375.928576" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="381.024973" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="385.881282" y="526.517229"/> + <use xlink:href="#glyph7-13" x="391.33781" y="526.517229"/> + <use xlink:href="#glyph7-13" x="399.828166" y="526.517229"/> + <use xlink:href="#glyph7-14" x="408.318523" y="526.517229"/> + <use xlink:href="#glyph7-18" x="413.77505" y="526.517229"/> + <use xlink:href="#glyph7-15" x="419.231578" y="526.517229"/> + <use xlink:href="#glyph7-19" x="422.265407" y="526.517229"/> + <use xlink:href="#glyph7-8" x="427.110803" y="526.517229"/> + <use xlink:href="#glyph7-12" x="431.9562" y="526.517229"/> + <use xlink:href="#glyph7-15" x="434.990029" y="526.517229"/> + <use xlink:href="#glyph7-4" x="438.023858" y="526.517229"/> + <use xlink:href="#glyph7-18" x="443.480385" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-42" x="450.99948" y="526.517229"/> + <use xlink:href="#glyph7-1" x="454.633527" y="526.517229"/> + <use xlink:href="#glyph7-25" x="459.478924" y="526.517229"/> + <use xlink:href="#glyph7-20" x="462.207187" y="526.517229"/> + <use xlink:href="#glyph7-25" x="467.663715" y="526.517229"/> + <use xlink:href="#glyph7-29" x="470.391979" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph8-0" x="475.18286" y="526.517229"/> + <use xlink:href="#glyph8-1" x="480.912213" y="526.517229"/> + <use xlink:href="#glyph8-2" x="486.641567" y="526.517229"/> + <use xlink:href="#glyph8-3" x="492.370921" y="526.517229"/> + <use xlink:href="#glyph8-4" x="498.100275" y="526.517229"/> + <use xlink:href="#glyph8-5" x="503.829628" y="526.517229"/> + <use xlink:href="#glyph8-2" x="509.558982" y="526.517229"/> + <use xlink:href="#glyph8-2" x="515.288336" y="526.517229"/> + <use xlink:href="#glyph8-6" x="521.01769" y="526.517229"/> + <use xlink:href="#glyph8-7" x="526.747044" y="526.517229"/> + <use xlink:href="#glyph8-5" x="532.476397" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-43" x="538.190497" y="526.517229"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="72" y="540.06102"/> + <use xlink:href="#glyph7-18" x="76.845396" y="540.06102"/> + <use xlink:href="#glyph7-11" x="82.301924" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="90.759541" y="540.06102"/> + <use xlink:href="#glyph7-3" x="96.216069" y="540.06102"/> + <use xlink:href="#glyph7-4" x="99.850116" y="540.06102"/> + <use xlink:href="#glyph7-12" x="105.306643" y="540.06102"/> + <use xlink:href="#glyph7-1" x="108.340472" y="540.06102"/> + <use xlink:href="#glyph7-19" x="113.185869" y="540.06102"/> + <use xlink:href="#glyph7-12" x="118.031265" y="540.06102"/> + <use xlink:href="#glyph7-15" x="121.065094" y="540.06102"/> + <use xlink:href="#glyph7-4" x="124.098924" y="540.06102"/> + <use xlink:href="#glyph7-18" x="129.555451" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-42" x="138.023982" y="540.06102"/> + <use xlink:href="#glyph7-39" x="141.658029" y="540.06102"/> + <use xlink:href="#glyph7-4" x="145.292076" y="540.06102"/> + <use xlink:href="#glyph7-3" x="150.748603" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-39" x="157.394654" y="540.06102"/> + <use xlink:href="#glyph7-3" x="161.028701" y="540.06102"/> + <use xlink:href="#glyph7-8" x="164.662748" y="540.06102"/> + <use xlink:href="#glyph7-13" x="169.508145" y="540.06102"/> + <use xlink:href="#glyph7-1" x="177.998501" y="540.06102"/> + <use xlink:href="#glyph7-6" x="182.843898" y="540.06102"/> + <use xlink:href="#glyph7-43" x="187.089076" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="193.735126" y="540.06102"/> + <use xlink:href="#glyph7-4" x="196.768956" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="205.226573" y="540.06102"/> + <use xlink:href="#glyph7-1" x="213.105799" y="540.06102"/> + <use xlink:href="#glyph7-2" x="217.951195" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="226.419725" y="540.06102"/> + <use xlink:href="#glyph7-3" x="231.876253" y="540.06102"/> + <use xlink:href="#glyph7-4" x="235.5103" y="540.06102"/> + <use xlink:href="#glyph7-20" x="240.966827" y="540.06102"/> + <use xlink:href="#glyph7-3" x="246.423355" y="540.06102"/> + <use xlink:href="#glyph7-8" x="250.057402" y="540.06102"/> + <use xlink:href="#glyph7-13" x="254.902798" y="540.06102"/> + <use xlink:href="#glyph7-13" x="263.393155" y="540.06102"/> + <use xlink:href="#glyph7-1" x="271.883512" y="540.06102"/> + <use xlink:href="#glyph7-3" x="276.728908" y="540.06102"/> + <use xlink:href="#glyph7-6" x="280.362955" y="540.06102"/> + <use xlink:href="#glyph7-25" x="284.608134" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-44" x="291.55975" y="540.06102"/> + <use xlink:href="#glyph7-1" x="299.438975" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="304.022458" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="309.31529" y="540.06102"/> + <use xlink:href="#glyph7-3" x="314.160686" y="540.06102"/> + <use xlink:href="#glyph7-12" x="317.794733" y="540.06102"/> + <use xlink:href="#glyph7-7" x="320.828562" y="540.06102"/> + <use xlink:href="#glyph7-1" x="326.28509" y="540.06102"/> + <use xlink:href="#glyph7-10" x="331.130486" y="540.06102"/> + <use xlink:href="#glyph7-1" x="334.164315" y="540.06102"/> + <use xlink:href="#glyph7-6" x="339.009712" y="540.06102"/> + <use xlink:href="#glyph7-6" x="343.25489" y="540.06102"/> + <use xlink:href="#glyph7-29" x="347.500068" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-18" x="353.305814" y="540.06102"/> + <use xlink:href="#glyph7-4" x="358.762341" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="367.219958" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-40" x="371.912572" y="540.06102"/> + <use xlink:href="#glyph7-15" x="377.369099" y="540.06102"/> + <use xlink:href="#glyph7-6" x="380.402929" y="540.06102"/> + <use xlink:href="#glyph7-12" x="384.648107" y="540.06102"/> + <use xlink:href="#glyph7-15" x="387.681936" y="540.06102"/> + <use xlink:href="#glyph7-18" x="390.715765" y="540.06102"/> + <use xlink:href="#glyph7-20" x="396.172293" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="404.62991" y="540.06102"/> + <use xlink:href="#glyph7-3" x="410.086438" y="540.06102"/> + <use xlink:href="#glyph7-4" x="413.720485" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="418.915099" y="540.06102"/> + <use xlink:href="#glyph7-6" x="426.794325" y="540.06102"/> + <use xlink:href="#glyph7-1" x="431.039503" y="540.06102"/> + <use xlink:href="#glyph7-3" x="435.884899" y="540.06102"/> + <use xlink:href="#glyph7-6" x="439.518946" y="540.06102"/> + <use xlink:href="#glyph7-29" x="443.764125" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="449.558957" y="540.06102"/> + <use xlink:href="#glyph7-18" x="452.592786" y="540.06102"/> + <use xlink:href="#glyph7-19" x="458.049314" y="540.06102"/> + <use xlink:href="#glyph7-10" x="462.89471" y="540.06102"/> + <use xlink:href="#glyph7-14" x="465.928539" y="540.06102"/> + <use xlink:href="#glyph7-11" x="471.385067" y="540.06102"/> + <use xlink:href="#glyph7-15" x="476.841594" y="540.06102"/> + <use xlink:href="#glyph7-18" x="479.875423" y="540.06102"/> + <use xlink:href="#glyph7-20" x="485.331951" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-18" x="493.800481" y="540.06102"/> + <use xlink:href="#glyph7-1" x="499.257008" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="503.840491" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="514.720807" y="540.06102"/> + <use xlink:href="#glyph7-3" x="519.566203" y="540.06102"/> + <use xlink:href="#glyph7-19" x="523.200251" y="540.06102"/> + <use xlink:href="#glyph7-7" x="528.045647" y="540.06102"/> + <use xlink:href="#glyph7-15" x="533.502174" y="540.06102"/> + <use xlink:href="#glyph7-16" x="536.536004" y="540.06102"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="72" y="553.614814"/> + <use xlink:href="#glyph7-1" x="75.033829" y="553.614814"/> + <use xlink:href="#glyph7-19" x="79.879226" y="553.614814"/> + <use xlink:href="#glyph7-12" x="84.724622" y="553.614814"/> + <use xlink:href="#glyph7-14" x="87.758451" y="553.614814"/> + <use xlink:href="#glyph7-3" x="93.214979" y="553.614814"/> + <use xlink:href="#glyph7-1" x="96.849026" y="553.614814"/> + <use xlink:href="#glyph7-6" x="101.694422" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-10" x="108.58056" y="553.614814"/> + <use xlink:href="#glyph7-15" x="111.614389" y="553.614814"/> + <use xlink:href="#glyph7-33" x="114.648218" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="120.006528" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-34" x="127.492884" y="553.614814"/> + <use xlink:href="#glyph7-35" x="131.126931" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-36" x="140.44668" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="148.544166" y="553.614814"/> + <use xlink:href="#glyph7-31" x="152.178214" y="553.614814"/> + <use xlink:href="#glyph7-22" x="157.634741" y="553.614814"/> + <use xlink:href="#glyph7-24" x="163.091268" y="553.614814"/> + <use xlink:href="#glyph7-29" x="166.725316" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-45" x="172.127278" y="553.614814"/> + <use xlink:href="#glyph7-4" x="180.006503" y="553.614814"/> + <use xlink:href="#glyph7-4" x="185.463031" y="553.614814"/> + <use xlink:href="#glyph7-20" x="190.919558" y="553.614814"/> + <use xlink:href="#glyph7-10" x="196.376086" y="553.614814"/> + <use xlink:href="#glyph7-1" x="199.409915" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-46" x="206.89627" y="553.614814"/> + <use xlink:href="#glyph7-7" x="214.175278" y="553.614814"/> + <use xlink:href="#glyph7-3" x="219.631805" y="553.614814"/> + <use xlink:href="#glyph7-4" x="223.265853" y="553.614814"/> + <use xlink:href="#glyph7-13" x="228.72238" y="553.614814"/> + <use xlink:href="#glyph7-1" x="237.212737" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="244.710005" y="553.614814"/> + <use xlink:href="#glyph7-22" x="248.344053" y="553.614814"/> + <use xlink:href="#glyph7-22" x="253.80058" y="553.614814"/> + <use xlink:href="#glyph7-24" x="259.257107" y="553.614814"/> + <use xlink:href="#glyph7-29" x="262.891155" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="268.282204" y="553.614814"/> + <use xlink:href="#glyph7-18" x="273.1276" y="553.614814"/> + <use xlink:href="#glyph7-11" x="278.584127" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="286.692527" y="553.614814"/> + <use xlink:href="#glyph7-47" x="294.571753" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="303.291283" y="553.614814"/> + <use xlink:href="#glyph7-31" x="306.925331" y="553.614814"/> + <use xlink:href="#glyph7-37" x="312.381858" y="553.614814"/> + <use xlink:href="#glyph7-24" x="317.838385" y="553.614814"/> + <use xlink:href="#glyph7-29" x="321.472433" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-7" x="326.863482" y="553.614814"/> + <use xlink:href="#glyph7-8" x="332.320009" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="336.958057" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="342.250889" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="349.726332" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-13" x="357.2236" y="553.614814"/> + <use xlink:href="#glyph7-14" x="365.713957" y="553.614814"/> + <use xlink:href="#glyph7-10" x="371.170484" y="553.614814"/> + <use xlink:href="#glyph7-12" x="374.204314" y="553.614814"/> + <use xlink:href="#glyph7-15" x="377.238143" y="553.614814"/> + <use xlink:href="#glyph7-16" x="380.271972" y="553.614814"/> + <use xlink:href="#glyph7-17" x="383.906019" y="553.614814"/> + <use xlink:href="#glyph7-3" x="389.362547" y="553.614814"/> + <use xlink:href="#glyph7-15" x="392.996594" y="553.614814"/> + <use xlink:href="#glyph7-18" x="396.030423" y="553.614814"/> + <use xlink:href="#glyph7-19" x="401.486951" y="553.614814"/> + <use xlink:href="#glyph7-15" x="406.332347" y="553.614814"/> + <use xlink:href="#glyph7-17" x="409.366176" y="553.614814"/> + <use xlink:href="#glyph7-8" x="414.822703" y="553.614814"/> + <use xlink:href="#glyph7-10" x="419.6681" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="425.353801" y="553.614814"/> + <use xlink:href="#glyph7-26" x="433.233027" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="441.952558" y="553.614814"/> + <use xlink:href="#glyph7-4" x="446.797954" y="553.614814"/> + <use xlink:href="#glyph7-18" x="452.254481" y="553.614814"/> + <use xlink:href="#glyph7-6" x="457.711009" y="553.614814"/> + <use xlink:href="#glyph7-12" x="461.956187" y="553.614814"/> + <use xlink:href="#glyph7-3" x="464.990016" y="553.614814"/> + <use xlink:href="#glyph7-14" x="468.624064" y="553.614814"/> + <use xlink:href="#glyph7-19" x="474.080591" y="553.614814"/> + <use xlink:href="#glyph7-12" x="478.925987" y="553.614814"/> + <use xlink:href="#glyph7-15" x="481.959817" y="553.614814"/> + <use xlink:href="#glyph7-4" x="484.993646" y="553.614814"/> + <use xlink:href="#glyph7-18" x="490.450173" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="498.54766" y="553.614814"/> + <use xlink:href="#glyph7-7" x="501.581489" y="553.614814"/> + <use xlink:href="#glyph7-8" x="507.038017" y="553.614814"/> + <use xlink:href="#glyph7-12" x="511.883413" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-20" x="517.569114" y="553.614814"/> + <use xlink:href="#glyph7-15" x="523.025642" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="525.797558" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="531.090389" y="553.614814"/> + <use xlink:href="#glyph7-6" x="535.935786" y="553.614814"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="72" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="80.435791" y="567.168608"/> + <use xlink:href="#glyph7-3" x="85.892319" y="567.168608"/> + <use xlink:href="#glyph7-4" x="89.526366" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="94.72098" y="567.168608"/> + <use xlink:href="#glyph7-6" x="102.600206" y="567.168608"/> + <use xlink:href="#glyph7-1" x="106.845384" y="567.168608"/> + <use xlink:href="#glyph7-3" x="111.69078" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-16" x="115.106566" y="567.168608"/> + <use xlink:href="#glyph7-2" x="118.740614" y="567.168608"/> + <use xlink:href="#glyph7-8" x="124.197141" y="567.168608"/> + <use xlink:href="#glyph7-6" x="129.042537" y="567.168608"/> + <use xlink:href="#glyph7-1" x="133.287716" y="567.168608"/> + <use xlink:href="#glyph7-11" x="138.133112" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="147.180035" y="567.168608"/> + <use xlink:href="#glyph7-26" x="155.05926" y="567.168608"/> + <use xlink:href="#glyph7-29" x="161.126919" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="167.45649" y="567.168608"/> + <use xlink:href="#glyph7-27" x="170.49032" y="567.168608"/> + <use xlink:href="#glyph7-17" x="175.946847" y="567.168608"/> + <use xlink:href="#glyph7-15" x="181.403374" y="567.168608"/> + <use xlink:href="#glyph7-19" x="184.437204" y="567.168608"/> + <use xlink:href="#glyph7-8" x="189.2826" y="567.168608"/> + <use xlink:href="#glyph7-10" x="194.127996" y="567.168608"/> + <use xlink:href="#glyph7-10" x="197.161826" y="567.168608"/> + <use xlink:href="#glyph7-27" x="200.195655" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="209.25349" y="567.168608"/> + <use xlink:href="#glyph7-8" x="214.098887" y="567.168608"/> + <use xlink:href="#glyph7-10" x="218.944283" y="567.168608"/> + <use xlink:href="#glyph7-10" x="221.978112" y="567.168608"/> + <use xlink:href="#glyph7-1" x="225.011941" y="567.168608"/> + <use xlink:href="#glyph7-11" x="229.857338" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-48" x="238.90426" y="567.168608"/> + <use xlink:href="#glyph7-3" x="246.183268" y="567.168608"/> + <use xlink:href="#glyph7-4" x="249.817315" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="255.011929" y="567.168608"/> + <use xlink:href="#glyph7-6" x="262.891155" y="567.168608"/> + <use xlink:href="#glyph7-1" x="267.136333" y="567.168608"/> + <use xlink:href="#glyph7-3" x="271.981729" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-49" x="279.206171" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="286.823484" y="567.168608"/> + <use xlink:href="#glyph7-3" x="291.66888" y="567.168608"/> + <use xlink:href="#glyph7-18" x="295.302927" y="567.168608"/> + <use xlink:href="#glyph7-1" x="300.759455" y="567.168608"/> + <use xlink:href="#glyph7-10" x="305.604851" y="567.168608"/> + <use xlink:href="#glyph7-29" x="308.63868" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="315.1756" y="567.168608"/> + <use xlink:href="#glyph7-7" x="318.209429" y="567.168608"/> + <use xlink:href="#glyph7-1" x="323.665957" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph9-0" x="332.012783" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph9-1" x="336.650831" y="567.168608"/> + <use xlink:href="#glyph9-2" x="341.496228" y="567.168608"/> + <use xlink:href="#glyph9-3" x="346.341624" y="567.168608"/> + <use xlink:href="#glyph9-4" x="349.375453" y="567.168608"/> + <use xlink:href="#glyph9-5" x="354.831981" y="567.168608"/> + <use xlink:href="#glyph9-6" x="359.077159" y="567.168608"/> + <use xlink:href="#glyph9-7" x="362.110988" y="567.168608"/> + <use xlink:href="#glyph9-0" x="366.956385" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="375.374921" y="567.168608"/> + <use xlink:href="#glyph7-4" x="380.220318" y="567.168608"/> + <use xlink:href="#glyph7-18" x="385.676845" y="567.168608"/> + <use xlink:href="#glyph7-12" x="391.133372" y="567.168608"/> + <use xlink:href="#glyph7-3" x="394.167202" y="567.168608"/> + <use xlink:href="#glyph7-4" x="397.801249" y="567.168608"/> + <use xlink:href="#glyph7-10" x="403.257776" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="409.882" y="567.168608"/> + <use xlink:href="#glyph7-4" x="412.91583" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-13" x="421.973665" y="567.168608"/> + <use xlink:href="#glyph7-8" x="430.464022" y="567.168608"/> + <use xlink:href="#glyph7-18" x="435.309418" y="567.168608"/> + <use xlink:href="#glyph7-8" x="440.765946" y="567.168608"/> + <use xlink:href="#glyph7-20" x="445.611342" y="567.168608"/> + <use xlink:href="#glyph7-1" x="451.067869" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="459.514574" y="567.168608"/> + <use xlink:href="#glyph7-7" x="462.548403" y="567.168608"/> + <use xlink:href="#glyph7-1" x="468.00493" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="476.451635" y="567.168608"/> + <use xlink:href="#glyph7-3" x="481.908162" y="567.168608"/> + <use xlink:href="#glyph7-4" x="485.542209" y="567.168608"/> + <use xlink:href="#glyph7-12" x="490.998737" y="567.168608"/> + <use xlink:href="#glyph7-1" x="494.032566" y="567.168608"/> + <use xlink:href="#glyph7-19" x="498.877962" y="567.168608"/> + <use xlink:href="#glyph7-12" x="503.723359" y="567.168608"/> + <use xlink:href="#glyph7-15" x="506.757188" y="567.168608"/> + <use xlink:href="#glyph7-4" x="509.791017" y="567.168608"/> + <use xlink:href="#glyph7-18" x="515.247545" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="524.294467" y="567.168608"/> + <use xlink:href="#glyph7-18" x="529.139863" y="567.168608"/> + <use xlink:href="#glyph7-11" x="534.596391" y="567.168608"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-39" x="72" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="75.53583" y="580.7124"/> + <use xlink:href="#glyph7-15" x="80.381226" y="580.7124"/> + <use xlink:href="#glyph7-3" x="83.415055" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-16" x="86.830841" y="580.7124"/> + <use xlink:href="#glyph7-6" x="90.464889" y="580.7124"/> + <use xlink:href="#glyph7-7" x="94.710067" y="580.7124"/> + <use xlink:href="#glyph7-8" x="100.166594" y="580.7124"/> + <use xlink:href="#glyph7-3" x="105.011991" y="580.7124"/> + <use xlink:href="#glyph7-15" x="108.646038" y="580.7124"/> + <use xlink:href="#glyph7-18" x="111.679867" y="580.7124"/> + <use xlink:href="#glyph7-20" x="117.136395" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="125.310273" y="580.7124"/> + <use xlink:href="#glyph7-39" x="130.7668" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="137.129111" y="580.7124"/> + <use xlink:href="#glyph7-10" x="141.974507" y="580.7124"/> + <use xlink:href="#glyph7-10" x="145.008337" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="150.77043" y="580.7124"/> + <use xlink:href="#glyph7-27" x="155.015608" y="580.7124"/> + <use xlink:href="#glyph7-6" x="160.472135" y="580.7124"/> + <use xlink:href="#glyph7-12" x="164.717314" y="580.7124"/> + <use xlink:href="#glyph7-1" x="167.751143" y="580.7124"/> + <use xlink:href="#glyph7-13" x="172.596539" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-3" x="183.815159" y="580.7124"/> + <use xlink:href="#glyph7-1" x="187.449207" y="580.7124"/> + <use xlink:href="#glyph7-6" x="192.294603" y="580.7124"/> + <use xlink:href="#glyph7-4" x="196.539781" y="580.7124"/> + <use xlink:href="#glyph7-14" x="201.996309" y="580.7124"/> + <use xlink:href="#glyph7-3" x="207.452836" y="580.7124"/> + <use xlink:href="#glyph7-19" x="211.086883" y="580.7124"/> + <use xlink:href="#glyph7-1" x="215.93228" y="580.7124"/> + <use xlink:href="#glyph7-6" x="220.777676" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="227.740205" y="580.7124"/> + <use xlink:href="#glyph7-13" x="232.585601" y="580.7124"/> + <use xlink:href="#glyph7-4" x="241.075958" y="580.7124"/> + <use xlink:href="#glyph7-18" x="246.532485" y="580.7124"/> + <use xlink:href="#glyph7-20" x="251.989013" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="260.173804" y="580.7124"/> + <use xlink:href="#glyph7-3" x="265.630331" y="580.7124"/> + <use xlink:href="#glyph7-4" x="269.264379" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="274.458993" y="580.7124"/> + <use xlink:href="#glyph7-6" x="282.338218" y="580.7124"/> + <use xlink:href="#glyph7-1" x="286.583397" y="580.7124"/> + <use xlink:href="#glyph7-3" x="291.428793" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="297.780191" y="580.7124"/> + <use xlink:href="#glyph7-3" x="303.236718" y="580.7124"/> + <use xlink:href="#glyph7-15" x="306.870765" y="580.7124"/> + <use xlink:href="#glyph7-18" x="309.904595" y="580.7124"/> + <use xlink:href="#glyph7-19" x="315.361122" y="580.7124"/> + <use xlink:href="#glyph7-15" x="320.206518" y="580.7124"/> + <use xlink:href="#glyph7-17" x="323.240348" y="580.7124"/> + <use xlink:href="#glyph7-8" x="328.696875" y="580.7124"/> + <use xlink:href="#glyph7-10" x="333.542271" y="580.7124"/> + <use xlink:href="#glyph7-6" x="336.576101" y="580.7124"/> + <use xlink:href="#glyph7-25" x="340.821279" y="580.7124"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-34" x="88.93474" y="594.266194"/> + <use xlink:href="#glyph7-18" x="92.568788" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="101.855797" y="594.266194"/> + <use xlink:href="#glyph7-7" x="104.889627" y="594.266194"/> + <use xlink:href="#glyph7-15" x="110.346154" y="594.266194"/> + <use xlink:href="#glyph7-6" x="113.379983" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="121.466557" y="594.266194"/> + <use xlink:href="#glyph7-8" x="126.923084" y="594.266194"/> + <use xlink:href="#glyph7-17" x="131.76848" y="594.266194"/> + <use xlink:href="#glyph7-1" x="137.225008" y="594.266194"/> + <use xlink:href="#glyph7-3" x="142.070404" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-29" x="145.278842" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="152.110415" y="594.266194"/> + <use xlink:href="#glyph7-1" x="159.98964" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="168.665519" y="594.266194"/> + <use xlink:href="#glyph7-3" x="174.122046" y="594.266194"/> + <use xlink:href="#glyph7-1" x="177.756093" y="594.266194"/> + <use xlink:href="#glyph7-6" x="182.60149" y="594.266194"/> + <use xlink:href="#glyph7-1" x="186.846668" y="594.266194"/> + <use xlink:href="#glyph7-18" x="191.692064" y="594.266194"/> + <use xlink:href="#glyph7-12" x="197.148592" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="204.023816" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-13" x="212.699695" y="594.266194"/> + <use xlink:href="#glyph7-14" x="221.190052" y="594.266194"/> + <use xlink:href="#glyph7-10" x="226.646579" y="594.266194"/> + <use xlink:href="#glyph7-12" x="229.680408" y="594.266194"/> + <use xlink:href="#glyph7-15" x="232.714237" y="594.266194"/> + <use xlink:href="#glyph7-16" x="235.748067" y="594.266194"/> + <use xlink:href="#glyph7-17" x="239.382114" y="594.266194"/> + <use xlink:href="#glyph7-3" x="244.838641" y="594.266194"/> + <use xlink:href="#glyph7-15" x="248.472689" y="594.266194"/> + <use xlink:href="#glyph7-18" x="251.506518" y="594.266194"/> + <use xlink:href="#glyph7-19" x="256.963045" y="594.266194"/> + <use xlink:href="#glyph7-15" x="261.808441" y="594.266194"/> + <use xlink:href="#glyph7-17" x="264.842271" y="594.266194"/> + <use xlink:href="#glyph7-8" x="270.298798" y="594.266194"/> + <use xlink:href="#glyph7-10" x="275.144194" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="282.019419" y="594.266194"/> + <use xlink:href="#glyph7-26" x="289.898645" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="299.796785" y="594.266194"/> + <use xlink:href="#glyph7-4" x="304.642182" y="594.266194"/> + <use xlink:href="#glyph7-18" x="310.098709" y="594.266194"/> + <use xlink:href="#glyph7-6" x="315.555236" y="594.266194"/> + <use xlink:href="#glyph7-12" x="319.800415" y="594.266194"/> + <use xlink:href="#glyph7-3" x="322.834244" y="594.266194"/> + <use xlink:href="#glyph7-14" x="326.468291" y="594.266194"/> + <use xlink:href="#glyph7-19" x="331.924819" y="594.266194"/> + <use xlink:href="#glyph7-12" x="336.770215" y="594.266194"/> + <use xlink:href="#glyph7-15" x="339.804044" y="594.266194"/> + <use xlink:href="#glyph7-4" x="342.837873" y="594.266194"/> + <use xlink:href="#glyph7-18" x="348.294401" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="357.592323" y="594.266194"/> + <use xlink:href="#glyph7-39" x="363.048851" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="370.51338" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="379.200172" y="594.266194"/> + <use xlink:href="#glyph7-1" x="383.44535" y="594.266194"/> + <use xlink:href="#glyph7-19" x="388.290747" y="594.266194"/> + <use xlink:href="#glyph7-14" x="393.136143" y="594.266194"/> + <use xlink:href="#glyph7-3" x="398.59267" y="594.266194"/> + <use xlink:href="#glyph7-1" x="402.226718" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="410.913509" y="594.266194"/> + <use xlink:href="#glyph7-1" x="418.792735" y="594.266194"/> + <use xlink:href="#glyph7-2" x="423.638131" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="432.925141" y="594.266194"/> + <use xlink:href="#glyph7-3" x="438.381668" y="594.266194"/> + <use xlink:href="#glyph7-4" x="442.015715" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="447.210329" y="594.266194"/> + <use xlink:href="#glyph7-6" x="455.089555" y="594.266194"/> + <use xlink:href="#glyph7-1" x="459.334733" y="594.266194"/> + <use xlink:href="#glyph7-3" x="464.18013" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-29" x="467.377655" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="474.209227" y="594.266194"/> + <use xlink:href="#glyph7-8" x="479.054623" y="594.266194"/> + <use xlink:href="#glyph7-10" x="483.90002" y="594.266194"/> + <use xlink:href="#glyph7-10" x="486.933849" y="594.266194"/> + <use xlink:href="#glyph7-1" x="489.967678" y="594.266194"/> + <use xlink:href="#glyph7-11" x="494.813075" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-45" x="504.110997" y="594.266194"/> + <use xlink:href="#glyph7-8" x="511.990223" y="594.266194"/> + <use xlink:href="#glyph7-50" x="516.835619" y="594.266194"/> + <use xlink:href="#glyph7-1" x="521.681015" y="594.266194"/> + <use xlink:href="#glyph7-10" x="526.526412" y="594.266194"/> + <use xlink:href="#glyph7-10" x="529.560241" y="594.266194"/> + <use xlink:href="#glyph7-1" x="532.59407" y="594.266194"/> + <use xlink:href="#glyph7-25" x="537.439467" y="594.266194"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-45" x="72" y="607.809985"/> + <use xlink:href="#glyph7-8" x="79.879226" y="607.809985"/> + <use xlink:href="#glyph7-50" x="84.724622" y="607.809985"/> + <use xlink:href="#glyph7-1" x="89.570018" y="607.809985"/> + <use xlink:href="#glyph7-10" x="94.415415" y="607.809985"/> + <use xlink:href="#glyph7-10" x="97.449244" y="607.809985"/> + <use xlink:href="#glyph7-1" x="100.483073" y="607.809985"/> + <use xlink:href="#glyph7-51" x="105.328469" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="108.373212" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-48" x="115.783176" y="607.809985"/> + <use xlink:href="#glyph7-3" x="123.062183" y="607.809985"/> + <use xlink:href="#glyph7-4" x="126.696231" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="131.890845" y="607.809985"/> + <use xlink:href="#glyph7-6" x="139.77007" y="607.809985"/> + <use xlink:href="#glyph7-1" x="144.015249" y="607.809985"/> + <use xlink:href="#glyph7-3" x="148.860645" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-49" x="155.659478" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="163.27679" y="607.809985"/> + <use xlink:href="#glyph7-3" x="168.122187" y="607.809985"/> + <use xlink:href="#glyph7-18" x="171.756234" y="607.809985"/> + <use xlink:href="#glyph7-1" x="177.212761" y="607.809985"/> + <use xlink:href="#glyph7-10" x="182.058158" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph9-0" x="188.21253" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph9-1" x="192.850579" y="607.809985"/> + <use xlink:href="#glyph9-2" x="197.695975" y="607.809985"/> + <use xlink:href="#glyph9-3" x="202.541371" y="607.809985"/> + <use xlink:href="#glyph9-4" x="205.575201" y="607.809985"/> + <use xlink:href="#glyph9-5" x="211.031728" y="607.809985"/> + <use xlink:href="#glyph9-6" x="215.276906" y="607.809985"/> + <use xlink:href="#glyph9-7" x="218.310736" y="607.809985"/> + <use xlink:href="#glyph9-0" x="223.156132" y="607.809985"/> + <use xlink:href="#glyph9-3" x="228.001528" y="607.809985"/> + <use xlink:href="#glyph9-8" x="231.035357" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="239.026754" y="607.809985"/> + <use xlink:href="#glyph7-3" x="244.483282" y="607.809985"/> + <use xlink:href="#glyph7-4" x="248.117329" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="253.421074" y="607.809985"/> + <use xlink:href="#glyph7-15" x="258.877601" y="607.809985"/> + <use xlink:href="#glyph7-11" x="261.91143" y="607.809985"/> + <use xlink:href="#glyph7-1" x="267.367958" y="607.809985"/> + <use xlink:href="#glyph7-6" x="272.213354" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="279.623318" y="607.809985"/> + <use xlink:href="#glyph7-3" x="284.468715" y="607.809985"/> + <use xlink:href="#glyph7-4" x="288.102762" y="607.809985"/> + <use xlink:href="#glyph7-6" x="293.559289" y="607.809985"/> + <use xlink:href="#glyph7-6" x="297.804468" y="607.809985"/> + <use xlink:href="#glyph7-16" x="302.049646" y="607.809985"/> + <use xlink:href="#glyph7-17" x="305.683693" y="607.809985"/> + <use xlink:href="#glyph7-3" x="311.140221" y="607.809985"/> + <use xlink:href="#glyph7-15" x="314.774268" y="607.809985"/> + <use xlink:href="#glyph7-18" x="317.808097" y="607.809985"/> + <use xlink:href="#glyph7-19" x="323.264624" y="607.809985"/> + <use xlink:href="#glyph7-15" x="328.110021" y="607.809985"/> + <use xlink:href="#glyph7-17" x="331.14385" y="607.809985"/> + <use xlink:href="#glyph7-8" x="336.600377" y="607.809985"/> + <use xlink:href="#glyph7-10" x="341.445774" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="347.655302" y="607.809985"/> + <use xlink:href="#glyph7-3" x="353.111829" y="607.809985"/> + <use xlink:href="#glyph7-4" x="356.745877" y="607.809985"/> + <use xlink:href="#glyph7-12" x="362.202404" y="607.809985"/> + <use xlink:href="#glyph7-1" x="365.236233" y="607.809985"/> + <use xlink:href="#glyph7-19" x="370.081629" y="607.809985"/> + <use xlink:href="#glyph7-12" x="374.927026" y="607.809985"/> + <use xlink:href="#glyph7-15" x="377.960855" y="607.809985"/> + <use xlink:href="#glyph7-4" x="380.994684" y="607.809985"/> + <use xlink:href="#glyph7-18" x="386.451212" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="395.083438" y="607.809985"/> + <use xlink:href="#glyph7-18" x="399.928834" y="607.809985"/> + <use xlink:href="#glyph7-11" x="405.385362" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-39" x="414.017588" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="417.553418" y="607.809985"/> + <use xlink:href="#glyph7-15" x="422.398814" y="607.809985"/> + <use xlink:href="#glyph7-3" x="425.432643" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="432.231477" y="607.809985"/> + <use xlink:href="#glyph7-7" x="436.476655" y="607.809985"/> + <use xlink:href="#glyph7-8" x="441.933182" y="607.809985"/> + <use xlink:href="#glyph7-3" x="446.778579" y="607.809985"/> + <use xlink:href="#glyph7-15" x="450.412626" y="607.809985"/> + <use xlink:href="#glyph7-18" x="453.446455" y="607.809985"/> + <use xlink:href="#glyph7-20" x="458.902983" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="467.535209" y="607.809985"/> + <use xlink:href="#glyph7-39" x="472.991736" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph9-9" x="479.714128" y="607.809985"/> + <use xlink:href="#glyph9-3" x="485.170655" y="607.809985"/> + <use xlink:href="#glyph9-3" x="488.204485" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="494.408241" y="607.809985"/> + <use xlink:href="#glyph7-27" x="498.65342" y="607.809985"/> + <use xlink:href="#glyph7-6" x="504.109947" y="607.809985"/> + <use xlink:href="#glyph7-12" x="508.355125" y="607.809985"/> + <use xlink:href="#glyph7-1" x="511.388954" y="607.809985"/> + <use xlink:href="#glyph7-13" x="516.234351" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-3" x="527.900406" y="607.809985"/> + <use xlink:href="#glyph7-1" x="531.534454" y="607.809985"/> + <use xlink:href="#glyph7-16" x="536.37985" y="607.809985"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="72" y="621.363779"/> + <use xlink:href="#glyph7-4" x="76.245178" y="621.363779"/> + <use xlink:href="#glyph7-14" x="81.701706" y="621.363779"/> + <use xlink:href="#glyph7-3" x="87.158233" y="621.363779"/> + <use xlink:href="#glyph7-19" x="90.79228" y="621.363779"/> + <use xlink:href="#glyph7-1" x="95.637677" y="621.363779"/> + <use xlink:href="#glyph7-6" x="100.483073" y="621.363779"/> + <use xlink:href="#glyph7-25" x="104.728251" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-34" x="110.828649" y="621.363779"/> + <use xlink:href="#glyph7-18" x="114.462696" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="122.647487" y="621.363779"/> + <use xlink:href="#glyph7-7" x="125.681317" y="621.363779"/> + <use xlink:href="#glyph7-15" x="131.137844" y="621.363779"/> + <use xlink:href="#glyph7-6" x="134.171673" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="141.145115" y="621.363779"/> + <use xlink:href="#glyph7-8" x="146.601643" y="621.363779"/> + <use xlink:href="#glyph7-17" x="151.447039" y="621.363779"/> + <use xlink:href="#glyph7-1" x="156.903566" y="621.363779"/> + <use xlink:href="#glyph7-3" x="161.748963" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-29" x="164.968314" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="170.403015" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="178.293154" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-39" x="185.866814" y="621.363779"/> + <use xlink:href="#glyph7-4" x="189.500861" y="621.363779"/> + <use xlink:href="#glyph7-19" x="194.957388" y="621.363779"/> + <use xlink:href="#glyph7-14" x="199.802785" y="621.363779"/> + <use xlink:href="#glyph7-6" x="205.259312" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="212.221841" y="621.363779"/> + <use xlink:href="#glyph7-18" x="217.678369" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-3" x="225.86316" y="621.363779"/> + <use xlink:href="#glyph7-1" x="229.497207" y="621.363779"/> + <use xlink:href="#glyph7-6" x="234.342603" y="621.363779"/> + <use xlink:href="#glyph7-4" x="238.587782" y="621.363779"/> + <use xlink:href="#glyph7-14" x="244.044309" y="621.363779"/> + <use xlink:href="#glyph7-3" x="249.500836" y="621.363779"/> + <use xlink:href="#glyph7-19" x="253.134884" y="621.363779"/> + <use xlink:href="#glyph7-1" x="257.98028" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="265.55394" y="621.363779"/> + <use xlink:href="#glyph7-3" x="271.010467" y="621.363779"/> + <use xlink:href="#glyph7-4" x="274.644515" y="621.363779"/> + <use xlink:href="#glyph7-12" x="280.101042" y="621.363779"/> + <use xlink:href="#glyph7-1" x="283.134871" y="621.363779"/> + <use xlink:href="#glyph7-19" x="287.980268" y="621.363779"/> + <use xlink:href="#glyph7-12" x="292.825664" y="621.363779"/> + <use xlink:href="#glyph7-15" x="295.859493" y="621.363779"/> + <use xlink:href="#glyph7-4" x="298.893322" y="621.363779"/> + <use xlink:href="#glyph7-18" x="304.34985" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="312.534641" y="621.363779"/> + <use xlink:href="#glyph7-18" x="317.991168" y="621.363779"/> + <use xlink:href="#glyph7-10" x="323.447696" y="621.363779"/> + <use xlink:href="#glyph7-27" x="326.481525" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-25" x="331.239617" y="621.363779"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-48" x="88.93474" y="634.90757"/> + <use xlink:href="#glyph7-3" x="96.213748" y="634.90757"/> + <use xlink:href="#glyph7-4" x="99.847795" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="105.042409" y="634.90757"/> + <use xlink:href="#glyph7-6" x="112.921635" y="634.90757"/> + <use xlink:href="#glyph7-1" x="117.166813" y="634.90757"/> + <use xlink:href="#glyph7-3" x="122.012209" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-49" x="128.494564" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="136.111876" y="634.90757"/> + <use xlink:href="#glyph7-3" x="140.957273" y="634.90757"/> + <use xlink:href="#glyph7-18" x="144.59132" y="634.90757"/> + <use xlink:href="#glyph7-1" x="150.047847" y="634.90757"/> + <use xlink:href="#glyph7-10" x="154.893244" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-3" x="160.77538" y="634.90757"/> + <use xlink:href="#glyph7-14" x="164.409427" y="634.90757"/> + <use xlink:href="#glyph7-18" x="169.865955" y="634.90757"/> + <use xlink:href="#glyph7-6" x="175.322482" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="182.426881" y="634.90757"/> + <use xlink:href="#glyph7-18" x="185.46071" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="193.787371" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="201.491988" y="634.90757"/> + <use xlink:href="#glyph7-1" x="205.737166" y="634.90757"/> + <use xlink:href="#glyph7-17" x="210.582562" y="634.90757"/> + <use xlink:href="#glyph7-8" x="216.03909" y="634.90757"/> + <use xlink:href="#glyph7-3" x="220.884486" y="634.90757"/> + <use xlink:href="#glyph7-8" x="224.518533" y="634.90757"/> + <use xlink:href="#glyph7-12" x="229.36393" y="634.90757"/> + <use xlink:href="#glyph7-1" x="232.397759" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="240.102375" y="634.90757"/> + <use xlink:href="#glyph7-26" x="247.981601" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="256.90848" y="634.90757"/> + <use xlink:href="#glyph7-3" x="262.365007" y="634.90757"/> + <use xlink:href="#glyph7-4" x="265.999055" y="634.90757"/> + <use xlink:href="#glyph7-19" x="271.455582" y="634.90757"/> + <use xlink:href="#glyph7-1" x="276.300978" y="634.90757"/> + <use xlink:href="#glyph7-6" x="281.146375" y="634.90757"/> + <use xlink:href="#glyph7-6" x="285.391553" y="634.90757"/> + <use xlink:href="#glyph7-29" x="289.636731" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-11" x="295.267868" y="634.90757"/> + <use xlink:href="#glyph7-15" x="300.724395" y="634.90757"/> + <use xlink:href="#glyph7-3" x="303.758224" y="634.90757"/> + <use xlink:href="#glyph7-1" x="307.392271" y="634.90757"/> + <use xlink:href="#glyph7-19" x="312.237668" y="634.90757"/> + <use xlink:href="#glyph7-12" x="317.083064" y="634.90757"/> + <use xlink:href="#glyph7-10" x="320.116893" y="634.90757"/> + <use xlink:href="#glyph7-27" x="323.150723" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="331.46647" y="634.90757"/> + <use xlink:href="#glyph7-18" x="334.5003" y="634.90757"/> + <use xlink:href="#glyph7-12" x="339.956827" y="634.90757"/> + <use xlink:href="#glyph7-1" x="342.990656" y="634.90757"/> + <use xlink:href="#glyph7-3" x="347.836052" y="634.90757"/> + <use xlink:href="#glyph7-8" x="351.4701" y="634.90757"/> + <use xlink:href="#glyph7-19" x="356.315496" y="634.90757"/> + <use xlink:href="#glyph7-12" x="361.160892" y="634.90757"/> + <use xlink:href="#glyph7-6" x="364.194722" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="371.29912" y="634.90757"/> + <use xlink:href="#glyph7-15" x="379.178346" y="634.90757"/> + <use xlink:href="#glyph7-12" x="382.212175" y="634.90757"/> + <use xlink:href="#glyph7-7" x="385.246004" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="393.561752" y="634.90757"/> + <use xlink:href="#glyph7-7" x="396.595581" y="634.90757"/> + <use xlink:href="#glyph7-1" x="402.052109" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-14" x="409.767638" y="634.90757"/> + <use xlink:href="#glyph7-18" x="415.224166" y="634.90757"/> + <use xlink:href="#glyph7-11" x="420.680693" y="634.90757"/> + <use xlink:href="#glyph7-1" x="426.137221" y="634.90757"/> + <use xlink:href="#glyph7-3" x="430.982617" y="634.90757"/> + <use xlink:href="#glyph7-10" x="434.616664" y="634.90757"/> + <use xlink:href="#glyph7-27" x="437.650493" y="634.90757"/> + <use xlink:href="#glyph7-15" x="443.107021" y="634.90757"/> + <use xlink:href="#glyph7-18" x="446.14085" y="634.90757"/> + <use xlink:href="#glyph7-20" x="451.597378" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="459.913125" y="634.90757"/> + <use xlink:href="#glyph7-26" x="467.792351" y="634.90757"/> + <use xlink:href="#glyph7-29" x="473.860009" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="479.447493" y="634.90757"/> + <use xlink:href="#glyph7-18" x="484.29289" y="634.90757"/> + <use xlink:href="#glyph7-11" x="489.749417" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="498.065165" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-40" x="502.757778" y="634.90757"/> + <use xlink:href="#glyph7-17" x="508.214306" y="634.90757"/> + <use xlink:href="#glyph7-4" x="513.670833" y="634.90757"/> + <use xlink:href="#glyph7-6" x="519.127361" y="634.90757"/> + <use xlink:href="#glyph7-1" x="523.372539" y="634.90757"/> + <use xlink:href="#glyph7-6" x="528.217935" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="535.322334" y="634.90757"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="72" y="648.461364"/> + <use xlink:href="#glyph7-1" x="76.245178" y="648.461364"/> + <use xlink:href="#glyph7-12" x="81.090575" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="86.830841" y="648.461364"/> + <use xlink:href="#glyph7-39" x="92.287369" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="98.638767" y="648.461364"/> + <use xlink:href="#glyph7-27" x="102.883945" y="648.461364"/> + <use xlink:href="#glyph7-6" x="108.340472" y="648.461364"/> + <use xlink:href="#glyph7-12" x="112.585651" y="648.461364"/> + <use xlink:href="#glyph7-1" x="115.61948" y="648.461364"/> + <use xlink:href="#glyph7-13" x="120.464876" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="131.672584" y="648.461364"/> + <use xlink:href="#glyph7-8" x="136.51798" y="648.461364"/> + <use xlink:href="#glyph7-10" x="141.363376" y="648.461364"/> + <use xlink:href="#glyph7-10" x="144.397206" y="648.461364"/> + <use xlink:href="#glyph7-6" x="147.431035" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-39" x="154.382651" y="648.461364"/> + <use xlink:href="#glyph7-4" x="158.016698" y="648.461364"/> + <use xlink:href="#glyph7-3" x="163.473225" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="169.824623" y="648.461364"/> + <use xlink:href="#glyph7-3" x="175.281151" y="648.461364"/> + <use xlink:href="#glyph7-4" x="178.915198" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="184.109812" y="648.461364"/> + <use xlink:href="#glyph7-6" x="191.989038" y="648.461364"/> + <use xlink:href="#glyph7-1" x="196.234216" y="648.461364"/> + <use xlink:href="#glyph7-3" x="201.079612" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="207.420097" y="648.461364"/> + <use xlink:href="#glyph7-3" x="212.876624" y="648.461364"/> + <use xlink:href="#glyph7-15" x="216.510672" y="648.461364"/> + <use xlink:href="#glyph7-18" x="219.544501" y="648.461364"/> + <use xlink:href="#glyph7-19" x="225.001028" y="648.461364"/> + <use xlink:href="#glyph7-15" x="229.846425" y="648.461364"/> + <use xlink:href="#glyph7-17" x="232.880254" y="648.461364"/> + <use xlink:href="#glyph7-8" x="238.336781" y="648.461364"/> + <use xlink:href="#glyph7-10" x="243.182178" y="648.461364"/> + <use xlink:href="#glyph7-6" x="246.216007" y="648.461364"/> + <use xlink:href="#glyph7-25" x="250.461185" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-0" x="256.561583" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="266.001375" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-11" x="273.553209" y="648.461364"/> + <use xlink:href="#glyph7-3" x="279.009737" y="648.461364"/> + <use xlink:href="#glyph7-8" x="282.643784" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="287.336397" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="297.92206" y="648.461364"/> + <use xlink:href="#glyph7-7" x="300.95589" y="648.461364"/> + <use xlink:href="#glyph7-1" x="306.412417" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="313.975164" y="648.461364"/> + <use xlink:href="#glyph7-6" x="317.008993" y="648.461364"/> + <use xlink:href="#glyph7-4" x="321.254172" y="648.461364"/> + <use xlink:href="#glyph7-10" x="326.710699" y="648.461364"/> + <use xlink:href="#glyph7-8" x="329.744528" y="648.461364"/> + <use xlink:href="#glyph7-12" x="334.589925" y="648.461364"/> + <use xlink:href="#glyph7-15" x="337.623754" y="648.461364"/> + <use xlink:href="#glyph7-4" x="340.657583" y="648.461364"/> + <use xlink:href="#glyph7-18" x="346.11411" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="354.277075" y="648.461364"/> + <use xlink:href="#glyph7-4" x="359.733603" y="648.461364"/> + <use xlink:href="#glyph7-14" x="365.19013" y="648.461364"/> + <use xlink:href="#glyph7-18" x="370.646658" y="648.461364"/> + <use xlink:href="#glyph7-11" x="376.103185" y="648.461364"/> + <use xlink:href="#glyph7-8" x="381.559712" y="648.461364"/> + <use xlink:href="#glyph7-3" x="386.405109" y="648.461364"/> + <use xlink:href="#glyph7-27" x="390.039156" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="398.213034" y="648.461364"/> + <use xlink:href="#glyph7-19" x="403.05843" y="648.461364"/> + <use xlink:href="#glyph7-3" x="407.903827" y="648.461364"/> + <use xlink:href="#glyph7-4" x="411.537874" y="648.461364"/> + <use xlink:href="#glyph7-6" x="416.994401" y="648.461364"/> + <use xlink:href="#glyph7-6" x="421.23958" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="428.202109" y="648.461364"/> + <use xlink:href="#glyph7-7" x="431.235938" y="648.461364"/> + <use xlink:href="#glyph7-1" x="436.692465" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="444.255212" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-40" x="448.947826" y="648.461364"/> + <use xlink:href="#glyph7-15" x="454.404353" y="648.461364"/> + <use xlink:href="#glyph7-6" x="457.438182" y="648.461364"/> + <use xlink:href="#glyph7-12" x="461.683361" y="648.461364"/> + <use xlink:href="#glyph7-15" x="464.71719" y="648.461364"/> + <use xlink:href="#glyph7-18" x="467.751019" y="648.461364"/> + <use xlink:href="#glyph7-20" x="473.207547" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="481.359599" y="648.461364"/> + <use xlink:href="#glyph7-3" x="486.816126" y="648.461364"/> + <use xlink:href="#glyph7-4" x="490.450173" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="495.644787" y="648.461364"/> + <use xlink:href="#glyph7-6" x="503.524013" y="648.461364"/> + <use xlink:href="#glyph7-1" x="507.769191" y="648.461364"/> + <use xlink:href="#glyph7-3" x="512.614588" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="518.955072" y="648.461364"/> + <use xlink:href="#glyph7-3" x="524.4116" y="648.461364"/> + <use xlink:href="#glyph7-15" x="528.045647" y="648.461364"/> + <use xlink:href="#glyph7-18" x="531.079476" y="648.461364"/> + <use xlink:href="#glyph7-16" x="536.536004" y="648.461364"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="72" y="662.005155"/> + <use xlink:href="#glyph7-15" x="76.845396" y="662.005155"/> + <use xlink:href="#glyph7-17" x="79.879226" y="662.005155"/> + <use xlink:href="#glyph7-8" x="85.335753" y="662.005155"/> + <use xlink:href="#glyph7-10" x="90.181149" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph10-0" x="93.205936" y="658.054049"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-11" x="100.527986" y="662.005155"/> + <use xlink:href="#glyph7-1" x="105.984513" y="662.005155"/> + <use xlink:href="#glyph7-52" x="110.829909" y="662.005155"/> + <use xlink:href="#glyph7-18" x="116.897568" y="662.005155"/> + <use xlink:href="#glyph7-1" x="122.354095" y="662.005155"/> + <use xlink:href="#glyph7-11" x="127.199492" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="135.493413" y="662.005155"/> + <use xlink:href="#glyph7-27" x="140.949941" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="149.254775" y="662.005155"/> + <use xlink:href="#glyph7-7" x="152.288605" y="662.005155"/> + <use xlink:href="#glyph7-1" x="157.745132" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="165.427922" y="662.005155"/> + <use xlink:href="#glyph7-8" x="169.673101" y="662.005155"/> + <use xlink:href="#glyph7-13" x="174.518497" y="662.005155"/> + <use xlink:href="#glyph7-1" x="183.008854" y="662.005155"/> + <use xlink:href="#glyph7-16" x="187.85425" y="662.005155"/> + <use xlink:href="#glyph7-4" x="191.488297" y="662.005155"/> + <use xlink:href="#glyph7-3" x="196.944825" y="662.005155"/> + <use xlink:href="#glyph7-15" x="200.578872" y="662.005155"/> + <use xlink:href="#glyph7-20" x="203.612701" y="662.005155"/> + <use xlink:href="#glyph7-15" x="209.069229" y="662.005155"/> + <use xlink:href="#glyph7-18" x="212.103058" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="220.407893" y="662.005155"/> + <use xlink:href="#glyph7-4" x="225.86442" y="662.005155"/> + <use xlink:href="#glyph7-10" x="231.320947" y="662.005155"/> + <use xlink:href="#glyph7-15" x="234.354777" y="662.005155"/> + <use xlink:href="#glyph7-19" x="237.388606" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-27" x="242.081219" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-42" x="250.375141" y="662.005155"/> + <use xlink:href="#glyph7-26" x="254.009188" y="662.005155"/> + <use xlink:href="#glyph7-28" x="260.076847" y="662.005155"/> + <use xlink:href="#glyph7-47" x="267.956072" y="662.005155"/> + <use xlink:href="#glyph7-43" x="274.023731" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-21" x="280.495172" y="662.005155"/> + <use xlink:href="#glyph7-22" x="284.12922" y="662.005155"/> + <use xlink:href="#glyph7-41" x="289.585747" y="662.005155"/> + <use xlink:href="#glyph7-24" x="295.042274" y="662.005155"/> + <use xlink:href="#glyph7-29" x="298.676322" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-18" x="304.274719" y="662.005155"/> + <use xlink:href="#glyph7-8" x="309.731246" y="662.005155"/> + <use xlink:href="#glyph7-13" x="314.576642" y="662.005155"/> + <use xlink:href="#glyph7-1" x="323.066999" y="662.005155"/> + <use xlink:href="#glyph7-10" x="327.912395" y="662.005155"/> + <use xlink:href="#glyph7-27" x="330.946225" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-29" x="335.704317" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="341.291801" y="662.005155"/> + <use xlink:href="#glyph7-7" x="344.32563" y="662.005155"/> + <use xlink:href="#glyph7-1" x="349.782157" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="357.475861" y="662.005155"/> + <use xlink:href="#glyph7-3" x="360.50969" y="662.005155"/> + <use xlink:href="#glyph7-15" x="364.143737" y="662.005155"/> + <use xlink:href="#glyph7-17" x="367.177567" y="662.005155"/> + <use xlink:href="#glyph7-10" x="372.634094" y="662.005155"/> + <use xlink:href="#glyph7-1" x="375.667923" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-4" x="383.350714" y="662.005155"/> + <use xlink:href="#glyph7-39" x="388.807241" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph11-0" x="395.190468" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="403.672842" y="662.005155"/> + <use xlink:href="#glyph7-3" x="409.12937" y="662.005155"/> + <use xlink:href="#glyph7-4" x="412.763417" y="662.005155"/> + <use xlink:href="#glyph7-12" x="418.219944" y="662.005155"/> + <use xlink:href="#glyph7-4" x="421.253774" y="662.005155"/> + <use xlink:href="#glyph7-19" x="426.710301" y="662.005155"/> + <use xlink:href="#glyph7-4" x="431.555697" y="662.005155"/> + <use xlink:href="#glyph7-10" x="437.012225" y="662.005155"/> + <use xlink:href="#glyph7-29" x="440.046054" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-11" x="445.633538" y="662.005155"/> + <use xlink:href="#glyph7-4" x="451.090066" y="662.005155"/> + <use xlink:href="#glyph7-13" x="456.546593" y="662.005155"/> + <use xlink:href="#glyph7-8" x="465.03695" y="662.005155"/> + <use xlink:href="#glyph7-15" x="469.882346" y="662.005155"/> + <use xlink:href="#glyph7-18" x="472.916175" y="662.005155"/> + <use xlink:href="#glyph7-16" x="478.372703" y="662.005155"/> + <use xlink:href="#glyph7-18" x="482.00675" y="662.005155"/> + <use xlink:href="#glyph7-8" x="487.463277" y="662.005155"/> + <use xlink:href="#glyph7-13" x="492.308673" y="662.005155"/> + <use xlink:href="#glyph7-1" x="500.79903" y="662.005155"/> + <use xlink:href="#glyph7-29" x="505.644426" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="511.253737" y="662.005155"/> + <use xlink:href="#glyph7-4" x="516.710264" y="662.005155"/> + <use xlink:href="#glyph7-3" x="522.166791" y="662.005155"/> + <use xlink:href="#glyph7-12" x="525.800839" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph11-1" x="528.787865" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-29" x="537.270239" y="662.005155"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-14" x="72" y="675.558949"/> + <use xlink:href="#glyph7-6" x="77.456527" y="675.558949"/> + <use xlink:href="#glyph7-15" x="81.701706" y="675.558949"/> + <use xlink:href="#glyph7-18" x="84.735535" y="675.558949"/> + <use xlink:href="#glyph7-20" x="90.192062" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="98.551462" y="675.558949"/> + <use xlink:href="#glyph7-8" x="102.796641" y="675.558949"/> + <use xlink:href="#glyph7-18" x="107.642037" y="675.558949"/> + <use xlink:href="#glyph7-11" x="113.098564" y="675.558949"/> + <use xlink:href="#glyph7-2" x="118.555092" y="675.558949"/> + <use xlink:href="#glyph7-4" x="124.011619" y="675.558949"/> + <use xlink:href="#glyph7-40" x="129.468147" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="134.771891" y="675.558949"/> + <use xlink:href="#glyph7-11" x="139.617288" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-28" x="147.976688" y="675.558949"/> + <use xlink:href="#glyph7-26" x="155.855913" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="164.826444" y="675.558949"/> + <use xlink:href="#glyph7-3" x="170.282972" y="675.558949"/> + <use xlink:href="#glyph7-4" x="173.917019" y="675.558949"/> + <use xlink:href="#glyph7-19" x="179.373546" y="675.558949"/> + <use xlink:href="#glyph7-1" x="184.218943" y="675.558949"/> + <use xlink:href="#glyph7-6" x="189.064339" y="675.558949"/> + <use xlink:href="#glyph7-6" x="193.309517" y="675.558949"/> + <use xlink:href="#glyph7-1" x="197.554695" y="675.558949"/> + <use xlink:href="#glyph7-6" x="202.400092" y="675.558949"/> + <use xlink:href="#glyph7-25" x="206.64527" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-53" x="213.302234" y="675.558949"/> + <use xlink:href="#glyph7-7" x="219.97011" y="675.558949"/> + <use xlink:href="#glyph7-1" x="225.426637" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="233.185819" y="675.558949"/> + <use xlink:href="#glyph7-3" x="238.642347" y="675.558949"/> + <use xlink:href="#glyph7-4" x="242.276394" y="675.558949"/> + <use xlink:href="#glyph7-19" x="247.732921" y="675.558949"/> + <use xlink:href="#glyph7-1" x="252.578318" y="675.558949"/> + <use xlink:href="#glyph7-6" x="257.423714" y="675.558949"/> + <use xlink:href="#glyph7-6" x="261.668892" y="675.558949"/> + <use xlink:href="#glyph7-1" x="265.914071" y="675.558949"/> + <use xlink:href="#glyph7-6" x="270.759467" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="277.907518" y="675.558949"/> + <use xlink:href="#glyph7-3" x="282.752914" y="675.558949"/> + <use xlink:href="#glyph7-1" x="286.386962" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="294.146144" y="675.558949"/> + <use xlink:href="#glyph7-8" x="298.391322" y="675.558949"/> + <use xlink:href="#glyph7-18" x="303.236718" y="675.558949"/> + <use xlink:href="#glyph7-11" x="308.693246" y="675.558949"/> + <use xlink:href="#glyph7-2" x="314.149773" y="675.558949"/> + <use xlink:href="#glyph7-4" x="319.6063" y="675.558949"/> + <use xlink:href="#glyph7-40" x="325.062828" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="330.366572" y="675.558949"/> + <use xlink:href="#glyph7-11" x="335.211969" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="343.571369" y="675.558949"/> + <use xlink:href="#glyph7-4" x="347.816547" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="356.175947" y="675.558949"/> + <use xlink:href="#glyph7-7" x="359.209776" y="675.558949"/> + <use xlink:href="#glyph7-8" x="364.666304" y="675.558949"/> + <use xlink:href="#glyph7-12" x="369.5117" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="375.459315" y="675.558949"/> + <use xlink:href="#glyph7-7" x="378.493144" y="675.558949"/> + <use xlink:href="#glyph7-1" x="383.949671" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-27" x="388.642285" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="396.990772" y="675.558949"/> + <use xlink:href="#glyph7-8" x="401.836168" y="675.558949"/> + <use xlink:href="#glyph7-18" x="406.681565" y="675.558949"/> + <use xlink:href="#glyph7-18" x="412.138092" y="675.558949"/> + <use xlink:href="#glyph7-4" x="417.594619" y="675.558949"/> + <use xlink:href="#glyph7-12" x="423.051147" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="428.998762" y="675.558949"/> + <use xlink:href="#glyph7-18" x="432.032591" y="675.558949"/> + <use xlink:href="#glyph7-12" x="437.489118" y="675.558949"/> + <use xlink:href="#glyph7-1" x="440.522948" y="675.558949"/> + <use xlink:href="#glyph7-3" x="445.368344" y="675.558949"/> + <use xlink:href="#glyph7-8" x="449.002391" y="675.558949"/> + <use xlink:href="#glyph7-19" x="453.847787" y="675.558949"/> + <use xlink:href="#glyph7-12" x="458.693184" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="464.640799" y="675.558949"/> + <use xlink:href="#glyph7-15" x="472.520024" y="675.558949"/> + <use xlink:href="#glyph7-12" x="475.553853" y="675.558949"/> + <use xlink:href="#glyph7-7" x="478.587683" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-12" x="486.947083" y="675.558949"/> + <use xlink:href="#glyph7-7" x="489.980912" y="675.558949"/> + <use xlink:href="#glyph7-1" x="495.437439" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-14" x="503.196621" y="675.558949"/> + <use xlink:href="#glyph7-18" x="508.653149" y="675.558949"/> + <use xlink:href="#glyph7-11" x="514.109676" y="675.558949"/> + <use xlink:href="#glyph7-1" x="519.566203" y="675.558949"/> + <use xlink:href="#glyph7-3" x="524.4116" y="675.558949"/> + <use xlink:href="#glyph7-10" x="528.045647" y="675.558949"/> + <use xlink:href="#glyph7-27" x="531.079476" y="675.558949"/> + <use xlink:href="#glyph7-16" x="536.536004" y="675.558949"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-15" x="72" y="689.10274"/> + <use xlink:href="#glyph7-18" x="75.033829" y="689.10274"/> + <use xlink:href="#glyph7-20" x="80.490357" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="88.784278" y="689.10274"/> + <use xlink:href="#glyph7-27" x="93.029457" y="689.10274"/> + <use xlink:href="#glyph7-6" x="98.485984" y="689.10274"/> + <use xlink:href="#glyph7-12" x="102.731162" y="689.10274"/> + <use xlink:href="#glyph7-1" x="105.764992" y="689.10274"/> + <use xlink:href="#glyph7-13" x="110.610388" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-8" x="121.938139" y="689.10274"/> + <use xlink:href="#glyph7-18" x="126.783535" y="689.10274"/> + <use xlink:href="#glyph7-11" x="132.240062" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-13" x="140.533984" y="689.10274"/> + <use xlink:href="#glyph7-14" x="149.024341" y="689.10274"/> + <use xlink:href="#glyph7-6" x="154.480868" y="689.10274"/> + <use xlink:href="#glyph7-12" x="158.726046" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-14" x="164.59727" y="689.10274"/> + <use xlink:href="#glyph7-6" x="170.053797" y="689.10274"/> + <use xlink:href="#glyph7-1" x="174.298976" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-6" x="181.981766" y="689.10274"/> + <use xlink:href="#glyph7-27" x="186.226945" y="689.10274"/> + <use xlink:href="#glyph7-6" x="191.683472" y="689.10274"/> + <use xlink:href="#glyph7-12" x="195.92865" y="689.10274"/> + <use xlink:href="#glyph7-1" x="198.96248" y="689.10274"/> + <use xlink:href="#glyph7-13" x="203.807876" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="215.135627" y="689.10274"/> + <use xlink:href="#glyph7-8" x="219.981023" y="689.10274"/> + <use xlink:href="#glyph7-10" x="224.826419" y="689.10274"/> + <use xlink:href="#glyph7-10" x="227.860249" y="689.10274"/> + <use xlink:href="#glyph7-6" x="230.894078" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="237.97665" y="689.10274"/> + <use xlink:href="#glyph7-3" x="243.433178" y="689.10274"/> + <use xlink:href="#glyph7-4" x="247.067225" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-9" x="252.37097" y="689.10274"/> + <use xlink:href="#glyph7-15" x="257.827497" y="689.10274"/> + <use xlink:href="#glyph7-11" x="260.861326" y="689.10274"/> + <use xlink:href="#glyph7-1" x="266.317854" y="689.10274"/> + <use xlink:href="#glyph7-11" x="271.16325" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-2" x="279.446259" y="689.10274"/> + <use xlink:href="#glyph7-27" x="284.902786" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-48" x="293.196708" y="689.10274"/> + <use xlink:href="#glyph7-3" x="300.475715" y="689.10274"/> + <use xlink:href="#glyph7-4" x="304.109763" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-5" x="309.304377" y="689.10274"/> + <use xlink:href="#glyph7-6" x="317.183602" y="689.10274"/> + <use xlink:href="#glyph7-1" x="321.428781" y="689.10274"/> + <use xlink:href="#glyph7-3" x="326.274177" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-49" x="332.734705" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-1" x="340.352018" y="689.10274"/> + <use xlink:href="#glyph7-3" x="345.197414" y="689.10274"/> + <use xlink:href="#glyph7-18" x="348.831461" y="689.10274"/> + <use xlink:href="#glyph7-1" x="354.287989" y="689.10274"/> + <use xlink:href="#glyph7-10" x="359.133385" y="689.10274"/> + <use xlink:href="#glyph7-25" x="362.167214" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-47" x="368.605916" y="689.10274"/> + <use xlink:href="#glyph7-3" x="374.673575" y="689.10274"/> + <use xlink:href="#glyph7-15" x="378.307622" y="689.10274"/> + <use xlink:href="#glyph7-18" x="381.341451" y="689.10274"/> + <use xlink:href="#glyph7-19" x="386.797979" y="689.10274"/> + <use xlink:href="#glyph7-15" x="391.643375" y="689.10274"/> + <use xlink:href="#glyph7-17" x="394.677204" y="689.10274"/> + <use xlink:href="#glyph7-8" x="400.133732" y="689.10274"/> + <use xlink:href="#glyph7-10" x="404.979128" y="689.10274"/> + <use xlink:href="#glyph7-6" x="408.012957" y="689.10274"/> + <use xlink:href="#glyph7-51" x="412.258136" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-17" x="418.729577" y="689.10274"/> + <use xlink:href="#glyph7-3" x="424.186105" y="689.10274"/> + <use xlink:href="#glyph7-4" x="427.820152" y="689.10274"/> + <use xlink:href="#glyph7-19" x="433.276679" y="689.10274"/> + <use xlink:href="#glyph7-1" x="438.122075" y="689.10274"/> + <use xlink:href="#glyph7-6" x="442.967472" y="689.10274"/> + <use xlink:href="#glyph7-6" x="447.21265" y="689.10274"/> + <use xlink:href="#glyph7-1" x="451.457828" y="689.10274"/> + <use xlink:href="#glyph7-6" x="456.303225" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="463.385797" y="689.10274"/> + <use xlink:href="#glyph7-8" x="468.231194" y="689.10274"/> + <use xlink:href="#glyph7-18" x="473.07659" y="689.10274"/> +</g> +<g style="fill:rgb(0%,0%,0%);fill-opacity:1;"> + <use xlink:href="#glyph7-19" x="481.370512" y="689.10274"/> + <use xlink:href="#glyph7-4" x="486.215908" y="689.10274"/> + <use xlink:href="#glyph7-13" x="491.672435" y="689.10274"/> + <use xlink:href="#glyph7-13" x="500.162792" y="689.10274"/> + <use xlink:href="#glyph7-14" x="508.653149" y="689.10274"/> + <use xlink:href="#glyph7-18" x="514.109676" y="689.10274"/> + <use xlink:href="#glyph7-15" x="519.566203" y="689.10274"/> + <use xlink:href="#glyph7-19" x="522.600033" y="689.10274"/> + <use xlink:href="#glyph7-8" x="527.445429" y="689.10274"/> + <use xlink:href="#glyph7-12" x="532.290825" y="689.10274"/> + <use xlink:href="#glyph7-1" x="535.324655" y="689.10274"/> +</g> +<path style="fill:none;stroke-width:0.99972;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(13.729858%,12.159729%,12.548828%);stroke-opacity:1;stroke-miterlimit:10;" d="M 0.49986 -626.324629 L 187.447515 -626.324629 " transform="matrix(1.00028,0,0,-1.00028,72,72)"/> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph12-0" x="84.653542" y="705.507332"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-0" x="88.144519" y="709.318399"/> + <use xlink:href="#glyph13-1" x="91.129035" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-2" x="97.841954" y="709.318399"/> + <use xlink:href="#glyph13-3" x="100.333531" y="709.318399"/> + <use xlink:href="#glyph13-4" x="104.814786" y="709.318399"/> + <use xlink:href="#glyph13-5" x="107.306363" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-6" x="113.033406" y="709.318399"/> + <use xlink:href="#glyph13-7" x="117.514661" y="709.318399"/> + <use xlink:href="#glyph13-6" x="121.494014" y="709.318399"/> + <use xlink:href="#glyph13-8" x="125.975269" y="709.318399"/> + <use xlink:href="#glyph13-9" x="129.954623" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-10" x="132.5896" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-11" x="137.061892" y="709.318399"/> + <use xlink:href="#glyph13-8" x="143.532824" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-12" x="149.752805" y="709.318399"/> + <use xlink:href="#glyph13-5" x="154.234059" y="709.318399"/> + <use xlink:href="#glyph13-8" x="157.720475" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-13" x="163.940456" y="709.318399"/> + <use xlink:href="#glyph13-6" x="167.91981" y="709.318399"/> + <use xlink:href="#glyph13-9" x="172.401064" y="709.318399"/> + <use xlink:href="#glyph13-4" x="175.38558" y="709.318399"/> + <use xlink:href="#glyph13-1" x="177.877157" y="709.318399"/> + <use xlink:href="#glyph13-14" x="182.358412" y="709.318399"/> + <use xlink:href="#glyph13-4" x="186.337766" y="709.318399"/> + <use xlink:href="#glyph13-6" x="188.829343" y="709.318399"/> + <use xlink:href="#glyph13-7" x="193.310597" y="709.318399"/> + <use xlink:href="#glyph13-15" x="197.289951" y="709.318399"/> + <use xlink:href="#glyph13-16" x="199.781529" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-7" x="206.00151" y="709.318399"/> + <use xlink:href="#glyph13-1" x="209.980864" y="709.318399"/> + <use xlink:href="#glyph13-17" x="214.462118" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-13" x="221.184" y="709.318399"/> + <use xlink:href="#glyph13-18" x="225.163354" y="709.318399"/> + <use xlink:href="#glyph13-9" x="229.644608" y="709.318399"/> + <use xlink:href="#glyph13-4" x="232.629124" y="709.318399"/> + <use xlink:href="#glyph13-19" x="235.120701" y="709.318399"/> + <use xlink:href="#glyph13-4" x="239.601955" y="709.318399"/> + <use xlink:href="#glyph13-1" x="242.093533" y="709.318399"/> + <use xlink:href="#glyph13-16" x="246.574787" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-4" x="252.794768" y="709.318399"/> + <use xlink:href="#glyph13-1" x="255.286346" y="709.318399"/> + <use xlink:href="#glyph13-2" x="259.7676" y="709.318399"/> + <use xlink:href="#glyph13-8" x="262.259178" y="709.318399"/> + <use xlink:href="#glyph13-9" x="266.238532" y="709.318399"/> + <use xlink:href="#glyph13-14" x="269.223047" y="709.318399"/> + <use xlink:href="#glyph13-3" x="273.202401" y="709.318399"/> + <use xlink:href="#glyph13-7" x="277.683655" y="709.318399"/> + <use xlink:href="#glyph13-1" x="281.663009" y="709.318399"/> + <use xlink:href="#glyph13-19" x="286.144264" y="709.318399"/> + <use xlink:href="#glyph13-8" x="290.625518" y="709.318399"/> + <use xlink:href="#glyph13-7" x="294.604872" y="709.318399"/> + <use xlink:href="#glyph13-20" x="298.584226" y="709.318399"/> + <use xlink:href="#glyph13-15" x="303.06548" y="709.318399"/> + <use xlink:href="#glyph13-21" x="305.557058" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph13-22" x="309.464712" y="709.318399"/> +</g> +<g style="fill:rgb(13.729858%,12.159729%,12.548828%);fill-opacity:1;"> + <use xlink:href="#glyph7-30" x="303.274739" y="749.889756"/> +</g> +</g> +</svg> diff --git a/dom/svg/crashtests/751515-1.svg b/dom/svg/crashtests/751515-1.svg new file mode 100644 index 0000000000..7485434c91 --- /dev/null +++ b/dom/svg/crashtests/751515-1.svg @@ -0,0 +1,9 @@ +<?xml version="1.0"?> + +<svg xmlns="http://www.w3.org/2000/svg"> +<script> + +document.documentElement.createSVGAngle().convertToSpecifiedUnits(2); + +</script> +</svg> diff --git a/dom/svg/crashtests/761507-1.svg b/dom/svg/crashtests/761507-1.svg new file mode 100644 index 0000000000..172d71ed95 --- /dev/null +++ b/dom/svg/crashtests/761507-1.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> +<![CDATA[ + +function boom() +{ + var r = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var f = r.requiredFeatures; + f.appendItem(1); + document.implementation.createDocument('', '', null).adoptNode(r); + r.getAttribute("requiredFeatures"); +} + +window.addEventListener("load", boom, false); + +]]> +</script> +</svg> diff --git a/dom/svg/crashtests/821955-1.html b/dom/svg/crashtests/821955-1.html new file mode 100644 index 0000000000..f6db39968e --- /dev/null +++ b/dom/svg/crashtests/821955-1.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<script> +document.createElementNS("http://www.w3.org/2000/svg", "polyline").points.appendItem(document.createElementNS("http://www.w3.org/2000/svg", "svg").currentTranslate) +</script> + diff --git a/dom/svg/crashtests/831561.html b/dom/svg/crashtests/831561.html new file mode 100644 index 0000000000..d86a8cb5bd --- /dev/null +++ b/dom/svg/crashtests/831561.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <body> + <script> + var clipPath = document.createElementNS("http://www.w3.org/2000/svg", + "clipPath"); + clipPath["hasExtension"].apply(clipPath,[]); + </script> + </body> +</html> diff --git a/dom/svg/crashtests/842463-1.html b/dom/svg/crashtests/842463-1.html new file mode 100644 index 0000000000..f20db0d6ca --- /dev/null +++ b/dom/svg/crashtests/842463-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + var f = document.createElementNS("http://www.w3.org/2000/svg", "feImage"); + f.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#s"); +} + +</script> +</head> +<body onload="boom();"></body> +</html> + diff --git a/dom/svg/crashtests/847138-1.svg b/dom/svg/crashtests/847138-1.svg new file mode 100644 index 0000000000..8f10f04e9a --- /dev/null +++ b/dom/svg/crashtests/847138-1.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<script> + +var globalE = document.createEvent('SVGZoomEvents'); +globalE.previousTranslate.expando = null; + +</script> +</svg> diff --git a/dom/svg/crashtests/864509.svg b/dom/svg/crashtests/864509.svg new file mode 100644 index 0000000000..e88dd1fcd8 --- /dev/null +++ b/dom/svg/crashtests/864509.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + +<script> +document.documentElement.requiredFeatures.foo = null; +</script> + +</svg> diff --git a/dom/svg/crashtests/898915-1.svg b/dom/svg/crashtests/898915-1.svg new file mode 100644 index 0000000000..6e9b59c36f --- /dev/null +++ b/dom/svg/crashtests/898915-1.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <text> + <animate attributeName="y" by="" /> + </text> +</svg> diff --git a/dom/svg/crashtests/crashtests.list b/dom/svg/crashtests/crashtests.list new file mode 100644 index 0000000000..2347be887c --- /dev/null +++ b/dom/svg/crashtests/crashtests.list @@ -0,0 +1,99 @@ +load 307322-1.svg +load 327705-1.svg +load 336994-1.html +load 344888-1.svg +load 345445-1.svg +load 360836-1.svg +load 369051-1.svg +load 369291-2.svg +load 369568-1.svg +load 374882-1.svg +load 380101-1.svg +load 381777-1.svg +load 383685-1.svg +load 385096.html +load 385554-1.html +load chrome://reftest/content/crashtests/dom/svg/crashtests/385554-2.xhtml +load 388712-1.svg +load 395616-1.html +load 396618-1.html +load 397017-1.html +load 397551-1.svg +load 397704-1.svg +load 398926-both-different.svg +load 398926-both-same.svg +load 398926-fill.svg +load 398926-stroke.svg +load 405639-1.svg +load 406361-1.html +load 409811-1.html +load 410659-1.svg +load 410659-2.svg +load 410659-3.svg +load 413174-1.svg +load 414188-1.svg +load 427325-1.svg +load 428228-1.svg +load 428841-1.svg +load 436418-mpathRoot-1.svg +load 448244-1.svg +load 466576-1.xhtml +load 499879-1.svg +load 535691-1.svg +load 539167-1.svg +load 573316-1.svg +load 579356-1.svg +load 579356-2.svg +load 595608-1.svg +load 601251-1.html +load 601406-1.svg +load 603145-1.svg +load 613899-1.svg +load 613899-2.svg +load 719779-1.svg +load 723441-1.html +load 751515-1.svg +load 761507-1.svg +load 821955-1.html +load 831561.html +load 842463-1.html +load 847138-1.svg +load 864509.svg +load 898915-1.svg +load 1035248-1.svg +load 1035248-2.svg +load 1244898-1.xhtml +load 1250725.html +load 1267272-1.svg +load 1282985-1.svg +# Disabled for now due to it taking a very long time to run - bug 1259356 +#load long-clipPath-reference-chain.svg +load zero-size-image.svg +load 1322286.html +load 1329849-1.svg +load 1329849-2.svg +load 1329849-3.svg +load 1329849-4.svg +load 1329849-5.svg +load 1329849-6.svg +load 1329093-1.html +load 1329093-2.html +load 1343147.svg +load 1347617-1.svg +load 1347617-2.svg +load 1347617-3.svg +load 1402798.html +load 1419250-1.html +load 1420492.html +load 1477853.html +load 1486488.html +load 1493447.html +skip-if(Android||ThreadSanitizer) load 1507961-1.html # times out on Android due to the test size +load 1513603.html +load 1531578-1.html +load test_nested_svg.html +load 1555795.html +load 1560179.html +load 1572904.html +load 1683907.html +pref(layout.css.d-property.enabled,true) pref(dom.svg.pathSeg.enabled,true) load 1715387.html diff --git a/dom/svg/crashtests/invalid-image.svg b/dom/svg/crashtests/invalid-image.svg new file mode 100644 index 0000000000..62d8fe9f6d --- /dev/null +++ b/dom/svg/crashtests/invalid-image.svg @@ -0,0 +1 @@ +X diff --git a/dom/svg/crashtests/long-clipPath-reference-chain.svg b/dom/svg/crashtests/long-clipPath-reference-chain.svg new file mode 100644 index 0000000000..31a587c740 --- /dev/null +++ b/dom/svg/crashtests/long-clipPath-reference-chain.svg @@ -0,0 +1,53 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <title>Test very long clipPath chain - MAY CRASH</title> + <script><![CDATA[ + +// This script creates a very long chain of clipPath elements to test whether +// that causes a stack overflow that crashes the UA. The first clipPath clips +// to a 50x100 rect, the last to a 25x100 rect. If a UA was to apply the +// entire clipPath chain (unlikely) a rect 25x100 would be rendered. +// +// At the time of writing, Firefox would treat the entire chain of clipPaths as +// invalid due to its excessive length, and refuse to render the referencing +// element at all. One alternative would be to ignore the clipPath reference +// and render the referencing element without any clipping. Another +// alternative would be to break the chain and clip the referencing element, +// but only using the first X clipPaths in the chain (in which case a 50x100 +// rect would be rendered). + +var chainLength = 100000; + +var SVG_NS = "http://www.w3.org/2000/svg"; +var template = document.createElementNS(SVG_NS, "clipPath"); +var templatesRect = document.createElementNS(SVG_NS, "rect"); +templatesRect.setAttribute("width", "100"); +templatesRect.setAttribute("height", "100"); +template.appendChild(templatesRect); + +function createClipPath(index) { + var cp = template.cloneNode(true); + cp.id = "c" + index; + cp.setAttribute("clip-path", "url(#c" + (index + 1) + ")"); + return cp; +} + +var de = document.documentElement; + +for (var i = chainLength; i > 0; --i) { + var cp = createClipPath(i); + + if (i == chainLength) { + cp.firstChild.setAttribute("width", "25"); + } + else if (i == 1) { + cp.firstChild.setAttribute("width", "50"); + } + + de.appendChild(cp); +} + + ]]></script> + <rect width="100%" height="100%" fill="lime"/> + <!-- We don't expect the following element to render at all --> + <rect width="500" height="500" clip-path="url(#c1)"/> +</svg> diff --git a/dom/svg/crashtests/test_nested_svg.html b/dom/svg/crashtests/test_nested_svg.html new file mode 100644 index 0000000000..588b78cbce --- /dev/null +++ b/dom/svg/crashtests/test_nested_svg.html @@ -0,0 +1,63 @@ +<!-- if not handled properly, this file will cause 100% CPU for exponentially long time --> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> +<svg> diff --git a/dom/svg/crashtests/zero-size-image.svg b/dom/svg/crashtests/zero-size-image.svg new file mode 100644 index 0000000000..20d40b2be8 --- /dev/null +++ b/dom/svg/crashtests/zero-size-image.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Test that we don't fail assertions with zero-size <image> elements. --> + <image xlink:href="data:image/svg+xml,%3Csvg%3E%3C%2Fsvg%3E"/> + <image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAAAXNSR0IArs4c6QAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII%3D"/> +</svg> diff --git a/dom/svg/moz.build b/dom/svg/moz.build new file mode 100644 index 0000000000..e294483d36 --- /dev/null +++ b/dom/svg/moz.build @@ -0,0 +1,271 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "SVG") + +MOCHITEST_MANIFESTS += ["test/mochitest.ini"] + +EXPORTS.mozilla += [ + "SVGAnimatedClass.h", + "SVGAnimatedClassOrString.h", + "SVGAttrValueWrapper.h", + "SVGContentUtils.h", + "SVGFragmentIdentifier.h", + "SVGPreserveAspectRatio.h", + "SVGStringList.h", + "SVGTagList.h", +] + +EXPORTS.mozilla.dom += [ + "DOMSVGAnimatedNumber.h", + "SVGAElement.h", + "SVGAnimatedLength.h", + "SVGAnimatedNumber.h", + "SVGAnimatedPreserveAspectRatio.h", + "SVGAnimatedRect.h", + "SVGAnimatedString.h", + "SVGAnimatedTransformList.h", + "SVGAnimateElement.h", + "SVGAnimateMotionElement.h", + "SVGAnimateTransformElement.h", + "SVGAnimationElement.h", + "SVGCircleElement.h", + "SVGClipPathElement.h", + "SVGComponentTransferFunctionElement.h", + "SVGDefsElement.h", + "SVGDescElement.h", + "SVGDocument.h", + "SVGElement.h", + "SVGElementFactory.h", + "SVGEllipseElement.h", + "SVGFEBlendElement.h", + "SVGFEColorMatrixElement.h", + "SVGFEComponentTransferElement.h", + "SVGFECompositeElement.h", + "SVGFEConvolveMatrixElement.h", + "SVGFEDiffuseLightingElement.h", + "SVGFEDisplacementMapElement.h", + "SVGFEDistantLightElement.h", + "SVGFEDropShadowElement.h", + "SVGFEFloodElement.h", + "SVGFEGaussianBlurElement.h", + "SVGFEImageElement.h", + "SVGFEMergeElement.h", + "SVGFEMergeNodeElement.h", + "SVGFEMorphologyElement.h", + "SVGFEOffsetElement.h", + "SVGFEPointLightElement.h", + "SVGFESpecularLightingElement.h", + "SVGFESpotLightElement.h", + "SVGFETileElement.h", + "SVGFETurbulenceElement.h", + "SVGFilterElement.h", + "SVGFilters.h", + "SVGForeignObjectElement.h", + "SVGGElement.h", + "SVGGeometryElement.h", + "SVGGradientElement.h", + "SVGGraphicsElement.h", + "SVGImageElement.h", + "SVGLineElement.h", + "SVGMarkerElement.h", + "SVGMaskElement.h", + "SVGMatrix.h", + "SVGMetadataElement.h", + "SVGMPathElement.h", + "SVGPathData.h", + "SVGPathElement.h", + "SVGPatternElement.h", + "SVGPolygonElement.h", + "SVGPolylineElement.h", + "SVGRect.h", + "SVGRectElement.h", + "SVGScriptElement.h", + "SVGSetElement.h", + "SVGStopElement.h", + "SVGStyleElement.h", + "SVGSVGElement.h", + "SVGSwitchElement.h", + "SVGSymbolElement.h", + "SVGTests.h", + "SVGTextContentElement.h", + "SVGTextElement.h", + "SVGTextPathElement.h", + "SVGTextPositioningElement.h", + "SVGTitleElement.h", + "SVGTransform.h", + "SVGTransformableElement.h", + "SVGTransformList.h", + "SVGTSpanElement.h", + "SVGUseElement.h", + "SVGViewElement.h", + "SVGViewportElement.h", +] + +UNIFIED_SOURCES += [ + "DOMSVGAngle.cpp", + "DOMSVGAnimatedAngle.cpp", + "DOMSVGAnimatedBoolean.cpp", + "DOMSVGAnimatedEnumeration.cpp", + "DOMSVGAnimatedInteger.cpp", + "DOMSVGAnimatedLength.cpp", + "DOMSVGAnimatedLengthList.cpp", + "DOMSVGAnimatedNumber.cpp", + "DOMSVGAnimatedNumberList.cpp", + "DOMSVGAnimatedString.cpp", + "DOMSVGAnimatedTransformList.cpp", + "DOMSVGLength.cpp", + "DOMSVGLengthList.cpp", + "DOMSVGNumber.cpp", + "DOMSVGNumberList.cpp", + "DOMSVGPathSeg.cpp", + "DOMSVGPathSegList.cpp", + "DOMSVGPoint.cpp", + "DOMSVGPointList.cpp", + "DOMSVGStringList.cpp", + "DOMSVGTransform.cpp", + "DOMSVGTransformList.cpp", + "SVGAElement.cpp", + "SVGAnimatedBoolean.cpp", + "SVGAnimatedClass.cpp", + "SVGAnimatedClassOrString.cpp", + "SVGAnimatedEnumeration.cpp", + "SVGAnimatedInteger.cpp", + "SVGAnimatedIntegerPair.cpp", + "SVGAnimatedLength.cpp", + "SVGAnimatedLengthList.cpp", + "SVGAnimatedNumber.cpp", + "SVGAnimatedNumberList.cpp", + "SVGAnimatedNumberPair.cpp", + "SVGAnimatedOrient.cpp", + "SVGAnimatedPathSegList.cpp", + "SVGAnimatedPointList.cpp", + "SVGAnimatedPreserveAspectRatio.cpp", + "SVGAnimatedRect.cpp", + "SVGAnimatedString.cpp", + "SVGAnimatedTransformList.cpp", + "SVGAnimatedViewBox.cpp", + "SVGAnimateElement.cpp", + "SVGAnimateMotionElement.cpp", + "SVGAnimateTransformElement.cpp", + "SVGAnimationElement.cpp", + "SVGAttrValueWrapper.cpp", + "SVGCircleElement.cpp", + "SVGClipPathElement.cpp", + "SVGContentUtils.cpp", + "SVGDataParser.cpp", + "SVGDefsElement.cpp", + "SVGDescElement.cpp", + "SVGDocument.cpp", + "SVGElement.cpp", + "SVGElementFactory.cpp", + "SVGEllipseElement.cpp", + "SVGFEBlendElement.cpp", + "SVGFEColorMatrixElement.cpp", + "SVGFEComponentTransferElement.cpp", + "SVGFECompositeElement.cpp", + "SVGFEConvolveMatrixElement.cpp", + "SVGFEDiffuseLightingElement.cpp", + "SVGFEDisplacementMapElement.cpp", + "SVGFEDistantLightElement.cpp", + "SVGFEDropShadowElement.cpp", + "SVGFEFloodElement.cpp", + "SVGFEGaussianBlurElement.cpp", + "SVGFEImageElement.cpp", + "SVGFEMergeElement.cpp", + "SVGFEMergeNodeElement.cpp", + "SVGFEMorphologyElement.cpp", + "SVGFEOffsetElement.cpp", + "SVGFEPointLightElement.cpp", + "SVGFESpecularLightingElement.cpp", + "SVGFESpotLightElement.cpp", + "SVGFETileElement.cpp", + "SVGFETurbulenceElement.cpp", + "SVGFilterElement.cpp", + "SVGFilters.cpp", + "SVGForeignObjectElement.cpp", + "SVGFragmentIdentifier.cpp", + "SVGGElement.cpp", + "SVGGeometryElement.cpp", + "SVGGeometryProperty.cpp", + "SVGGradientElement.cpp", + "SVGGraphicsElement.cpp", + "SVGImageElement.cpp", + "SVGIntegerPairSMILType.cpp", + "SVGLength.cpp", + "SVGLengthList.cpp", + "SVGLengthListSMILType.cpp", + "SVGLineElement.cpp", + "SVGMarkerElement.cpp", + "SVGMaskElement.cpp", + "SVGMatrix.cpp", + "SVGMetadataElement.cpp", + "SVGMotionSMILAnimationFunction.cpp", + "SVGMotionSMILAttr.cpp", + "SVGMotionSMILPathUtils.cpp", + "SVGMotionSMILType.cpp", + "SVGMPathElement.cpp", + "SVGNumberList.cpp", + "SVGNumberListSMILType.cpp", + "SVGNumberPairSMILType.cpp", + "SVGOrientSMILType.cpp", + "SVGPathData.cpp", + "SVGPathDataParser.cpp", + "SVGPathElement.cpp", + "SVGPathSegListSMILType.cpp", + "SVGPathSegUtils.cpp", + "SVGPatternElement.cpp", + "SVGPointList.cpp", + "SVGPointListSMILType.cpp", + "SVGPolyElement.cpp", + "SVGPolygonElement.cpp", + "SVGPolylineElement.cpp", + "SVGPreserveAspectRatio.cpp", + "SVGRect.cpp", + "SVGRectElement.cpp", + "SVGScriptElement.cpp", + "SVGSetElement.cpp", + "SVGStopElement.cpp", + "SVGStringList.cpp", + "SVGStyleElement.cpp", + "SVGSVGElement.cpp", + "SVGSwitchElement.cpp", + "SVGSymbolElement.cpp", + "SVGTests.cpp", + "SVGTextContentElement.cpp", + "SVGTextElement.cpp", + "SVGTextPathElement.cpp", + "SVGTextPositioningElement.cpp", + "SVGTitleElement.cpp", + "SVGTransform.cpp", + "SVGTransformableElement.cpp", + "SVGTransformList.cpp", + "SVGTransformListParser.cpp", + "SVGTransformListSMILType.cpp", + "SVGTSpanElement.cpp", + "SVGUseElement.cpp", + "SVGViewBoxSMILType.cpp", + "SVGViewElement.cpp", + "SVGViewportElement.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "/dom", + "/dom/base", + "/dom/html", + "/dom/smil", + "/dom/svg", + "/dom/xml", + "/layout/base", + "/layout/generic", + "/layout/style", + "/layout/svg", + "/layout/xul", +] diff --git a/dom/svg/test/MutationEventChecker.js b/dom/svg/test/MutationEventChecker.js new file mode 100644 index 0000000000..12afa600d8 --- /dev/null +++ b/dom/svg/test/MutationEventChecker.js @@ -0,0 +1,278 @@ +// Helper class to check DOM MutationEvents +// +// Usage: +// +// * Create a new event checker: +// var eventChecker = new MutationEventChecker; +// * Set the attribute to watch +// eventChecker.watchAttr(<DOM element>, "<attribute name>"); +// * Set the events to expect (0..n) +// eventChecker.expect("add", "modify"); +// OR +// eventChecker.expect("add modify"); +// OR +// eventChecker.expect(MutationEvent.ADDITION, MutationEvent.MODIFICATION); +// +// An empty string or empty set of arguments is also fine as a way of checking +// that all expected events have been received and indicating no events are +// expected from the following code, e.g. +// +// eventChecker.expect(""); +// // changes that are not expected to generate events +// eventChecker.expect("modify"); +// // change that is expected to generate an event +// ... +// +// * Either finish listening or set the next attribute to watch +// eventChecker.finish(); +// eventChecker.watchAttr(element, "nextAttribute"); +// +// In either case a check is performed that all expected events have been +// received. +// +// * Event checking can be temporarily disabled with ignoreEvents(). The next +// call to expect() will cause it to resume. + +function MutationEventChecker() { + this.expectedEvents = []; + + this.watchAttr = function(element, attr) { + if (this.attr) { + this.finish(); + } + + this.expectedEvents = []; + this.element = element; + this.attr = attr; + this.oldValue = element.getAttribute(attr); + this.giveUp = false; + this.ignore = false; + + this.element.addEventListener("DOMAttrModified", this._listener); + }; + + this.expect = function() { + if (this.giveUp) { + return; + } + + ok( + !this.expectedEvents.length, + "Expecting new events for " + + this.attr + + " but the following previously expected events have still not been " + + "received: " + + this._stillExpecting() + ); + if (this.expectedEvents.length) { + this.giveUp = true; + return; + } + + this.ignore = false; + + if (!arguments.length || (arguments.length == 1 && arguments[0] == "")) { + return; + } + + // Turn arguments object into an array + var args = Array.prototype.slice.call(arguments); + // Check for whitespace separated keywords + if ( + args.length == 1 && + typeof args[0] === "string" && + args[0].indexOf(" ") > 0 + ) { + args = args[0].split(" "); + } + // Convert strings to event Ids + this.expectedEvents = args.map(this._argToEventId); + }; + + // Temporarily disable event checking + this.ignoreEvents = function() { + // Check all events have been received + ok( + this.giveUp || !this.expectedEvents.length, + "Going to ignore subsequent events on " + + this.attr + + " attribute, but we're still expecting the following events: " + + this._stillExpecting() + ); + + this.ignore = true; + }; + + this.finish = function() { + // Check all events have been received + ok( + this.giveUp || !this.expectedEvents.length, + "Finishing listening to " + + this.attr + + " attribute, but we're still expecting the following events: " + + this._stillExpecting() + ); + + this.element.removeEventListener("DOMAttrModified", this._listener); + this.attr = ""; + }; + + this._receiveEvent = function(e) { + if (this.giveUp || this.ignore) { + this.oldValue = e.newValue; + return; + } + + // Make sure we're expecting something at all + if (!this.expectedEvents.length) { + ok( + false, + "Unexpected " + + this._eventToName(e.attrChange) + + " event when none expected on " + + this.attr + + " attribute." + ); + return; + } + + var expectedEvent = this.expectedEvents.shift(); + + // Make sure we got the event we expected + if (e.attrChange != expectedEvent) { + ok( + false, + "Unexpected " + + this._eventToName(e.attrChange) + + " on " + + this.attr + + " attribute. Expected " + + this._eventToName(expectedEvent) + + " (followed by: " + + this._stillExpecting() + + ")" + ); + // If we get events out of sequence, it doesn't make sense to do any + // further testing since we don't really know what to expect + this.giveUp = true; + return; + } + + // Common param checking + is( + e.target, + this.element, + "Unexpected node for mutation event on " + this.attr + " attribute" + ); + is(e.attrName, this.attr, "Unexpected attribute name for mutation event"); + + // Don't bother testing e.relatedNode since Attr nodes are on the way + // out anyway (but then, so are mutation events...) + + // Event-specific checking + if (e.attrChange == MutationEvent.MODIFICATION) { + ok( + this.element.hasAttribute(this.attr), + "Attribute not set after modification" + ); + is( + e.prevValue, + this.oldValue, + "Unexpected old value for modification to " + this.attr + " attribute" + ); + isnot( + e.newValue, + this.oldValue, + "Unexpected new value for modification to " + this.attr + " attribute" + ); + } else if (e.attrChange == MutationEvent.REMOVAL) { + ok(!this.element.hasAttribute(this.attr), "Attribute set after removal"); + is( + e.prevValue, + this.oldValue, + "Unexpected old value for removal of " + this.attr + " attribute" + ); + // DOM 3 Events doesn't say what value newValue will be for a removal + // event but generally empty strings are used for other events when an + // attribute isn't relevant + ok( + e.newValue === "", + "Unexpected new value for removal of " + this.attr + " attribute" + ); + } else if (e.attrChange == MutationEvent.ADDITION) { + ok( + this.element.hasAttribute(this.attr), + "Attribute not set after addition" + ); + // DOM 3 Events doesn't say what value prevValue will be for an addition + // event but generally empty strings are used for other events when an + // attribute isn't relevant + ok( + e.prevValue === "", + "Unexpected old value for addition of " + this.attr + " attribute" + ); + ok( + typeof e.newValue == "string" && e.newValue !== "", + "Unexpected new value for addition of " + this.attr + " attribute" + ); + } else { + ok(false, "Unexpected mutation event type: " + e.attrChange); + this.giveUp = true; + } + this.oldValue = e.newValue; + }; + this._listener = this._receiveEvent.bind(this); + + this._stillExpecting = function() { + if (!this.expectedEvents.length) { + return "(nothing)"; + } + var eventNames = []; + for (var i = 0; i < this.expectedEvents.length; i++) { + eventNames.push(this._eventToName(this.expectedEvents[i])); + } + return eventNames.join(", "); + }; + + this._eventToName = function(evtId) { + switch (evtId) { + case MutationEvent.MODIFICATION: + return "modification"; + case MutationEvent.ADDITION: + return "addition"; + case MutationEvent.REMOVAL: + return "removal"; + } + return "Unknown MutationEvent Type"; + }; + + this._argToEventId = function(arg) { + if (typeof arg === "number") { + return arg; + } + + if (typeof arg !== "string") { + ok(false, "Unexpected event type: " + arg); + return 0; + } + + switch (arg.toLowerCase()) { + case "mod": + case "modify": + case "modification": + return MutationEvent.MODIFICATION; + + case "add": + case "addition": + return MutationEvent.ADDITION; + + case "removal": + case "remove": + return MutationEvent.REMOVAL; + + default: + ok(false, "Unexpected event name: " + arg); + return 0; + } + }; +} diff --git a/dom/svg/test/a_href_destination.svg b/dom/svg/test/a_href_destination.svg new file mode 100644 index 0000000000..43e4c812f4 --- /dev/null +++ b/dom/svg/test/a_href_destination.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <rect width="100%" height="100%" fill="green"/> +</svg> diff --git a/dom/svg/test/a_href_helper_01.svg b/dom/svg/test/a_href_helper_01.svg new file mode 100644 index 0000000000..8f33cea404 --- /dev/null +++ b/dom/svg/test/a_href_helper_01.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <a id="a" xlink:href="a_href_destination.svg"> + <rect width="100%" height="100%"/> + </a> +</svg> diff --git a/dom/svg/test/a_href_helper_02_03.svg b/dom/svg/test/a_href_helper_02_03.svg new file mode 100644 index 0000000000..af4b7e2736 --- /dev/null +++ b/dom/svg/test/a_href_helper_02_03.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <a id="a" xlink:href="initial.svg"> + <rect width="100%" height="100%"/> + </a> +</svg> diff --git a/dom/svg/test/a_href_helper_04.svg b/dom/svg/test/a_href_helper_04.svg new file mode 100644 index 0000000000..50aca28898 --- /dev/null +++ b/dom/svg/test/a_href_helper_04.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <a id="a" xlink:href="initial.svg"> + <set attributeName="xlink:href" to="a_href_destination.svg"/> + <rect width="100%" height="100%"/> + </a> +</svg> diff --git a/dom/svg/test/a_href_helper_05.svg b/dom/svg/test/a_href_helper_05.svg new file mode 100644 index 0000000000..8600960b8d --- /dev/null +++ b/dom/svg/test/a_href_helper_05.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <a id="a" href="a_href_destination.svg" xlink:href="a_href_fake_destination.svg"> + <rect width="100%" height="100%"/> + </a> +</svg> diff --git a/dom/svg/test/a_href_helper_06.svg b/dom/svg/test/a_href_helper_06.svg new file mode 100644 index 0000000000..9414c39892 --- /dev/null +++ b/dom/svg/test/a_href_helper_06.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <a id="a" href="initial.svg"> + <rect width="100%" height="100%"/> + </a> +</svg> diff --git a/dom/svg/test/a_href_helper_07.svg b/dom/svg/test/a_href_helper_07.svg new file mode 100644 index 0000000000..9fe1d23f7f --- /dev/null +++ b/dom/svg/test/a_href_helper_07.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <a id="a" href="initial.svg"> + <set attributeName="href" to="a_href_destination.svg"/> + <rect width="100%" height="100%"/> + </a> +</svg> diff --git a/dom/svg/test/animated-svg-image-helper.html b/dom/svg/test/animated-svg-image-helper.html new file mode 100644 index 0000000000..94af2098bc --- /dev/null +++ b/dom/svg/test/animated-svg-image-helper.html @@ -0,0 +1,3 @@ +<html> + <img src="animated-svg-image-helper.svg"> +</html> diff --git a/dom/svg/test/animated-svg-image-helper.svg b/dom/svg/test/animated-svg-image-helper.svg new file mode 100644 index 0000000000..5f6f564e92 --- /dev/null +++ b/dom/svg/test/animated-svg-image-helper.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg"> + <set attributeName="font-size" to="50"/> +</svg> diff --git a/dom/svg/test/bbox-helper.svg b/dom/svg/test/bbox-helper.svg new file mode 100644 index 0000000000..40aa01cb2a --- /dev/null +++ b/dom/svg/test/bbox-helper.svg @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg"> + <g transform="scale(0.5)"> + <foreignObject id="fO" x="10" y="10" width="100" height="100"/> + <image id="i" x="10" y="10" width="100" height="100"/> + </g> + <text id="b" x="100" y="100">abcdef</text> + <text id="a" x="20" y="30">a</text> + <text id="y" x="20" y="40">y</text> + <text id="tspantext1"> + <tspan id="tspan1" x="100" y="100">abcdef</tspan> + </text> + <text id="tspantext2" x="100" y="100">ABCEDF<tspan id="tspan2">ABCEDF</tspan></text> + <text id="text" x="20" y="60">text</text> + <!-- ‎ is the same as the HTML ‎ --> + <text id="lrmText" x="20" y="60">‎text</text> + <g id="v"> + <circle cx="100" cy="50" r="5"/> + <path d="M 100,100 L 100,200"/> + </g> + <g id="h"> + <circle cx="200" cy="50" r="5"/> + <path d="M 200,100 L 300,100"/> + </g> + <g id="e"> + <!-- empty container should not affect parent's bbox --> + <g/> + <!-- neither should a path, --> + <path/> + <!-- a polygon --> + <polygon/> + <!-- or an empty text element --> + <text x="185" y="25"/> + <circle cx="100" cy="100" r="5"/> + <g/> + <circle cx="100" cy="100" r="5"/> + <g/> + </g> + <use x="100" y="100" id="use_v" href="#v"/> + <use x="100" y="100" id="use_h" href="#h"/> + <use x="100" y="100" id="use_e" href="#e"/> +</svg> diff --git a/dom/svg/test/bounds-helper.svg b/dom/svg/test/bounds-helper.svg new file mode 100644 index 0000000000..4d1ef9c3c3 --- /dev/null +++ b/dom/svg/test/bounds-helper.svg @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="750" + xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> + <style type="text/css"> +text { font: 20px monospace; } +#css-trans-rect-2 { transform: scaleX(2) } + </style> + +<g id="g"> + <svg id="svg1" x="10" y="10" width="25" height="30"/> + <svg id="svg2" width="1" height="1" overflow="visible"> + <rect width="2" height="2" fill="yellow"/> + </svg> + <svg id="svg3" width="1" height="1" overflow="hidden"> + <rect width="2" height="2" fill="yellow"/> + </svg> + <symbol> + <rect id="use-test" width="50" height="10"/> + </symbol> + <use id="use1" href="#use-test" x="100" y="50" fill="yellow"/> + <text id="text1" x="25" y="25">abc</text> + <text id="text1a" x="85" y="25" stroke="black" stroke-width="4">abc</text> + <rect id="rect1" x="50" y="50" width="50" height="50" fill="green"/> + <rect id="rect1a" x="50" y="50" width="50" height="50" fill="none" stroke-width="4" stroke="yellow"/> + <text id="text2" x="125" y="25">abc</text> + <text id="text2a" x="185" y="25" stroke="black" stroke-width="10">abc</text> + <g transform="rotate(45 175 75)"> + <rect id="rect2" x="150" y="50" width="50" height="50" fill="yellow"/> + <rect id="rect2a" x="150" y="50" width="50" height="50" fill="none" stroke-width="4" stroke="blue"/> + <text id="text3" x="150" y="50" text-anchor="middle">abc</text> + </g> + <g transform="scale(2)"> + <rect id="rect3" x="25" y="80" width="50" height="50" fill="green"/> + <rect id="rect3a" x="25" y="80" width="50" height="50" fill="none" stroke-width="4" stroke="blue"/> + <rect id="rect3b" vector-effect="non-scaling-stroke" x="100" y="100" width="25" height="25" fill="orange" stroke-width="4" stroke="yellow"/> + <image id="i" x="10" y="10" width="100" height="100"/> + </g> + <g transform="scale(2) rotate(45 175 75)"> + <rect id="rect4" x="150" y="50" width="50" height="50" fill="yellow"/> + <rect id="rect4a" x="150" y="50" width="50" height="50" fill="none" stroke-width="4" stroke="blue"/> + </g> + <text id="text4" x="185" y="25"/> + <g id="g2"> + <rect x="100" y="100" width="50" height="50" fill="pink"/> + <text x="200" y="200"/> + </g> + <circle id="nonScalingStrokedCircle1" cx="0" cy="0" r="10" + transform="translate(45 130) scale(3 -2)" + fill="none" stroke="gray" stroke-width="10" + vector-effect="non-scaling-stroke"/> + <ellipse id="nonScalingStrokedEllipse1" cx="20" cy="-10" rx="5" ry="5" + transform="matrix(0 3 -2 0 0 0)" + fill="none" stroke="steelblue" stroke-width="10" + vector-effect="non-scaling-stroke" /> + <line id="nonScalingStrokedLine1" x1="120" y1="5" x2="120" y2="10" + transform="scale(2 3)" + stroke-width="10" stroke-linecap="round" stroke="orange" + vector-effect="non-scaling-stroke" /> + <line id="nonScalingStrokedLine2" x1="130" y1="5" x2="140" y2="5" + transform="rotate(45 260 15) scale(2 3)" + stroke-width="10" stroke-linecap="square" stroke="crimson" + vector-effect="non-scaling-stroke" /> + <line id="nonScalingStrokedLine3" x1="140" y1="5" x2="150" y2="5" + transform="rotate(45 280 15) scale(2 3)" + stroke-width="10" stroke-linecap="butt" stroke="indigo" + vector-effect="non-scaling-stroke" /> + + <marker id="marker1" markerWidth="100" markerHeight="100" + refX="0" refY="50" markerUnits="userSpaceOnUse"> + <line x1="0" y1="50" x2="50" y2="100" stroke="aqua" stroke-width="20" + transform="rotate(-45 0 50)" /> + </marker> + <line id="shapeWithMarker1" x1="160" y1="130" x2="170" y2="130" + stroke="black" stroke-width="3" marker-end="url(#marker1)"/> + + <line id="rotatedLine1" x1="160" y1="150" x2="180" y2="170" + stroke="darkmagenta" stroke-width="10" + transform="rotate(-45 160 150)" /> + + <rect id="css-trans-rect-2" x="5" y="5" width="6" height="6"></rect> +</g> +</svg> diff --git a/dom/svg/test/dataTypes-helper.svg b/dom/svg/test/dataTypes-helper.svg new file mode 100644 index 0000000000..e2a17e31c3 --- /dev/null +++ b/dom/svg/test/dataTypes-helper.svg @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="750"> + <defs> + <filter id="filter"> + <!-- <boolean> (preserveAlpha) --> + <!-- <enum> (edgeMode) --> + <!-- <number> (divisor) --> + <!-- <integer> (targetX) --> + <!-- <integer-optional-integer> (order) --> + <!-- <string> (result) --> + <feConvolveMatrix id="convolve"/> + <!-- <number-optional-number> (stdDeviation) --> + <feGaussianBlur id="blur"/> + </filter> + <!-- <angle> (orient) --> + <!-- <length> (markerWidth) --> + <!-- <preserveAspectRatio> (preserveAspectRatio) --> + <marker id="marker"/> + </defs> +</svg> diff --git a/dom/svg/test/fragments-helper.svg b/dom/svg/test/fragments-helper.svg new file mode 100644 index 0000000000..c7fa8fca7a --- /dev/null +++ b/dom/svg/test/fragments-helper.svg @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg"> + <view id="view" viewBox="0 200 100 100" preserveAspectRatio="none"/> +</svg> diff --git a/dom/svg/test/getBBox-method-helper.svg b/dom/svg/test/getBBox-method-helper.svg new file mode 100644 index 0000000000..8172b2d864 --- /dev/null +++ b/dom/svg/test/getBBox-method-helper.svg @@ -0,0 +1,304 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + viewBox="0 0 500 500" width="500px" height="500px"> + <defs> + <clipPath id="rect01" clip-rule="evenodd" clipPathUnits="objectBoundingBox"> + <rect x="0" y="0" width="0.5" height="1.0"/> + </clipPath> + <clipPath id="rect02" clip-rule="evenodd" clipPathUnits="objectBoundingBox"> + <rect x="0.5" y="0" width="0.5" height="1.0"/> + </clipPath> + <clipPath id="rect03" clip-rule="evenodd" clipPathUnits="objectBoundingBox"> + <rect x="0.5" y="0" width="0.5" height="1.0"/> + </clipPath> + <clipPath id="rect04" clip-rule="evenodd" clipPathUnits="objectBoundingBox"> + <rect x="0" y="0" width="0.5" height="1.0"/> + </clipPath> + <clipPath id="rect05" clip-rule="evenodd"> + <rect x="0" y="60" width="10px" height="23px"/> + </clipPath> + <clipPath id="rect06" clip-rule="evenodd"> + <rect x="10" y="60" width="10px" height="23px"/> + </clipPath> + <clipPath id="rect4" clip-rule="evenodd"> + <rect x="200" y="200" width="200" height="200"/> + </clipPath> + <clipPath id="rect-none" clip-rule="evenodd"> + </clipPath> + <clipPath id="rect5" clip-rule="evenodd"> + <rect x="0" y="0" width="100" height="100"/> + </clipPath> + <clipPath id="rect6" clip-rule="evenodd"> + <rect x="150" y="0" width="100" height="100"/> + </clipPath> + <clipPath id="rect7" clip-rule="evenodd"> + <rect x="0" y="100" width="100" height="100"/> + </clipPath> + <clipPath id="rect8" clip-rule="evenodd"> + <rect x="10" y="10" width="180" height="180"/> + </clipPath> + <clipPath id="rect9" clip-rule="evenodd"> + <rect x="100" y="100" width="200" height="200"/> + </clipPath> + + <clipPath id="circle1" clip-rule="evenodd"> + <circle cx="203" cy="203" r="150"/> + </clipPath> + <clipPath id="circle2" clip-rule="evenodd" clipPathUnits="objectBoundingBox"> + <circle cx="0.5" cy="0.5" r="0.25"/> + </clipPath> + <clipPath id="circle3" clip-rule="evenodd"> + <circle cx="100" cy="100" r="50"/> + <circle cx="300" cy="300" r="50"/> + </clipPath> + + <clipPath id="circle4" clip-rule="evenodd"> + <circle cx="50" cy="50" r="50"/> + </clipPath> + <clipPath id="circle5" clip-rule="evenodd"> + <circle cx="150" cy="50" r="50"/> + </clipPath> + <clipPath id="circle6" clip-rule="evenodd"> + <circle cx="50" cy="200" r="50"/> + </clipPath> + <clipPath id="circle7" clip-rule="evenodd" clipPathUnits="objectBoundingBox"> + <circle cx="0.5" cy="0.5" r="0.5"/> + </clipPath> + + <clipPath id="circle8" clip-rule="evenodd" clipPathUnits="userSpaceOnUse"> + <circle cx="110" cy="20" r="90"/> + </clipPath> + + <clipPath id="circle9" clip-rule="evenodd" clipPathUnits="userSpaceOnUse"> + <circle cx="290" cy="20" r="90"/> + </clipPath> + + <clipPath id="circle10" clip-rule="evenodd" clipPathUnits="userSpaceOnUse"> + <circle cx="110" cy="200" r="90"/> + </clipPath> + + <clipPath id="circle11" clip-rule="evenodd"> + <circle cx="0" cy="0" r="150"/> + </clipPath> + + <clipPath id="star" clip-rule="evenodd"> + <path d="M400,25 L619,703 43,283 757,283 181,703 z" /> + </clipPath> + + <marker id="m_atr" markerUnits="strokeWidth" markerWidth="3" markerHeight="3" viewBox="0 0 10 10" refX="5" refY="5"> + <polygon points="0,0 5,5 0,10 10,5" fill="red"/> + </marker> + + <switch> + <rect id="rect-10" x="20" y="20" width="180" height="180" fill="blue" stroke="cyan" stroke-width="8"/> + <rect id="rect-11" x="200" y="20" width="180" height="180" fill="lightgreen" stroke="none" /> + <rect id="rect-12" x="20" y="200" width="180" height="180" fill="darkcyan" stroke="none" /> + </switch> + + <clipPath id="clipCircle1"> + <circle id="c1" cx="100" cy="100" r="50"/> + </clipPath> + + <clipPath id="clipCircle2"> + <circle id="c2" cx="150" cy="150" r="50"/> + </clipPath> + + <clipPath id="clipPath1"> + <path id="p1" d="M10 10l100 0 0 100 -100 0ZM50 50l40 0 0 40 -40 0Z" clip-rule="evenodd"/> + </clipPath> + + <!-- "If a valid 'clip-path' reference is placed on one of the children of a 'clipPath' element, + then the given child element is clipped by the referenced clipping path before OR'ing the + silhouette of the child element with the silhouettes of the other child elements." --> + + <clipPath id="clipRects1"> + <rect x="50" y="30" width="25" height="100"/> + <rect x="25" y="50" width="10" height="10" clip-path="url(#clipTwoCircles)"/> + </clipPath> + + <!-- Test use in a clipPath --> + <clipPath id="clipTwoCircles"> + <use xlink:href="#c1"/> + <use xlink:href="#c2"/> + </clipPath> + + <clipPath id="clipInClip1"> + <use xlink:href="#c2" clip-path="url(#clipCircle1)"/> + <use xlink:href="#p1"/> + </clipPath> + + <clipPath id="clipOnClip1" clip-path="url(#clipCircle1)"> + <use xlink:href="#c2"/> + <use xlink:href="#p1"/> + </clipPath> + + </defs> + + <!-- text --> + <text id="text1" font-size="20px" font-familiy="monospace" fill="red" x="0" y="50" clip-path="url('#rect01')">99</text> + <text id="text2" font-size="20px" font-familiy="monospace" fill="blue" x="100" y="120" clip-path="url('#rect02')">99</text> + <text id="text3" font-size="20px" font-familiy="monospace" clip-path="url('#rect03')" x="0" y="120"> + <tspan x="0" y="50" fill="red">99</tspan> + </text> + <text id="text4" font-size="20px" font-familiy="monospace" clip-path="url('#rect04')" x="0" y="120"> + <tspan x="100" y="120" fill="blue">99</tspan> + </text> + <text id="text5" font-size="20px" font-familiy="monospace" fill="red" x="0" y="80" clip-path="url('#rect05')">99</text> + <text id="text6" font-size="20px" font-familiy="monospace" fill="blue" x="0" y="80" clip-path="url('#rect06')">99</text> + + <!-- image --> + <image id="image1" x="150" y="150" width="200" height="200" preserveApectRatio="none" clip="rect(200,300,300,200)" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image2" x="2" y="2" width="200" height="200" clip-path="url('#circle1')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image3" x="205" y="2" width="200" height="200" clip-path="url('#circle1')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image4" x="2" y="205" width="200" height="200" clip-path="url('#circle1')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image5" x="205" y="205" width="200" height="200" clip-path="url('#circle1')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image6" x="2" y="2" width="200" height="200" clip-path="url('#circle2')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image7" x="205" y="2" width="200" height="200" clip-path="url('#circle2')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image8" x="2" y="205" width="200" height="200" clip-path="url('#circle2')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image9" x="205" y="205" width="200" height="200" clip-path="url('#circle2')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image10" x="0" y="0" width="400" height="400" clip-path="url('#rect4')" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image11" x="0" y="0" width="400" height="400" clip-path="url('#rect-none')" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image12" x="25" y="43" width="768" height="768" clip-path="url('#star')" preserveApectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image13" x="0" y="0" width="400" height="400" clip-path="url('#circle3')" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <image id="image14" x="0" y="0" width="400" height="400" clip-path="url('#m_atr')" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAKXRFWHRDcmVhdGlvbiBUaW1lAJCFIDMwIDQgMjAxNCAwOToyODo0MyArMDkwMH73aTcAAAAHdElNRQfeBB4AHhEfV06+AAAACXBIWXMAAB7BAAAewQHDaVRTAAAABGdBTUEAALGPC/xhBQAAABlJREFUeNpjZPj/n4EUwESS6lENoxqGlAYASU8CHux7qQ4AAAAASUVORK5CYII="/> + + <!-- path --> + <path id="path1" d="M10,50 L25,100 H110 V50 Q60,0 10,50" stroke="black" stroke-width="8" stroke-miterlimit="1" stroke-linejoin="miter" fill="lightcyan" marker-mid="url(#m_atr)"/> + <path id="path2" d="M160,50 L175,100 H260 V50 Q210,0 160,50" stroke="black" stroke-width="8" stroke-linejoin="bevel" fill="lightcyan" marker-mid="url(#m_atr)"/> + <path id="path3" d="M10,150 L25,200 H110 V150 Q60,100 10,150" stroke="black" stroke-width="8" stroke-linejoin="round" fill="lightcyan" marker-mid="url(#m_atr)"/> + + + <path id="path4" d="M10,50 L25,100 H110 V50 Q60,0 10,50" stroke="black" stroke-width="8" stroke-miterlimit="1" stroke-linejoin="miter" fill="lightcyan" + marker-mid="url(#m_atr)" clip-path="url(#circle4)"/> + + <path id="path5" d="M160,50 L175,100 H260 V50 Q210,0 160,50" stroke="black" stroke-width="8" stroke-linejoin="bevel" fill="lightcyan" + marker-mid="url(#m_atr)" clip-path="url(#circle5)"/> + + <path id="path6" d="M10,150 L25,200 H110 V150 Q60,100 10,150" stroke="black" stroke-width="8" stroke-linejoin="round" fill="lightcyan" + marker-mid="url(#m_atr)" clip-path="url(#circle6)"/> + + <path id="path7" d="M10,50 L25,100 H110 V50 Q60,0 10,50" + stroke="black" stroke-width="8" stroke-miterlimit="1" stroke-linejoin="miter" fill="lightcyan" + clip-path="url('#rect5')" marker-mid="url(#m_atr)"/> + + <path id="path8" d="M160,50 L175,100 H260 V50 Q210,0 160,50" + stroke="black" stroke-width="8" stroke-linejoin="bevel" fill="lightcyan" + clip-path="url('#rect6')" marker-mid="url(#m_atr)"/> + + <path id="path9" d="M10,150 L25,200 H110 V150 Q60,100 10,150" + stroke="black" stroke-width="8" stroke-linejoin="round" fill="lightcyan" + clip-path="url('#rect7')" marker-mid="url(#m_atr)"/> + + <path id="path10" d="M10,50 L25,100 H110 V50 Q60,0 10,50" + stroke="black" stroke-width="8" stroke-miterlimit="1" stroke-linejoin="miter" fill="lightcyan" + clip-path="url('#circle7')" marker-mid="url(#m_atr)"/> + + <path id="path11" d="M160,50 L175,100 H260 V50 Q210,0 160,50" + stroke="black" stroke-width="8" stroke-linejoin="bevel" fill="lightcyan" + clip-path="url('#circle7')" marker-mid="url(#m_atr)"/> + + <path id="path12" d="M10,150 L25,200 H110 V150 Q60,100 10,150" + stroke="black" stroke-width="8" stroke-linejoin="round" fill="lightcyan" + clip-path="url('#circle7')" marker-mid="url(#m_atr)"/> + + <path id="path13" d="M50,0 C 130,0 50,0 100,50 + C 100,130 100,50 50,100 + C -30,100 50,100 0,50 + C 0,-30 0,50 50,0Z" /> + + <!-- use --> + <use id="use1" xlink:href="#rect-10" x="50" y="50" clip-path="url('#circle8')"/> + <use id="use2" xlink:href="#rect-11" x="50" y="50" clip-path="url('#circle9')"/> + <use id="use3" xlink:href="#rect-12" x="50" y="50" clip-path="url('#circle10')"/> + + <use id="use4" xlink:href="#rect-10" x="2" y="2" width="200" height="200" clip-path="url('#circle11')"/> + <use id="use5" xlink:href="#rect-10" x="205" y="2" width="200" height="200" clip-path="url('#circle11')"/> + <use id="use6" xlink:href="#rect-10" x="2" y="205" width="200" height="200" clip-path="url('#circle11')"/> + <use id="use7" xlink:href="#rect-10" x="205" y="205" width="200" height="200" clip-path="url('#circle11')"/> + + <use id="use8" xlink:href="#rect-10" x="50" y="50" clip-path="url('#m_atr')"/> + + <!-- foreignObject --> + <foreignObject id="fo1" x="2" y="2" width="200" height="200" clip-path="url('#circle1')" clip="rect(2,102,102,2)"> + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + <foreignObject id="fo2" x="205" y="2" width="200" height="200" clip-path="url('#circle1')" > + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + <foreignObject id="fo3" x="2" y="205" width="200" height="200" clip-path="url('#circle1')" > + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + <foreignObject id="fo4" x="205" y="205" width="200" height="200" clip-path="url('#circle1')" clip="rect(2,102,102,2)"> + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + + <foreignObject id="fo5" x="250" y="250" width="200" height="200" clip-path="url('#rect8')"> + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + + <foreignObject id="fo6" x="0" y="0" width="200" height="200" clip-path="url('#rect9')"> + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + + <foreignObject id="fo7" x="0" y="0" width="200" height="200" clip-path="url('#rect8')"> + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + + <foreignObject id="fo8" x="0" y="0" width="200" height="200" clip-path="url('#m_atr')"> + <div xmlns="http://www.w3.org/1999/xhtml" style="background-color:pink;width:100%;height:100%;"> +There are issues of dynamic loading required for tiling. Accroding to 'postpone' attribute of Resource Priorities, the dynamic loading is controlled by positional relation with bounding box of embedded contents and container's viewport. However, each bounding boxes of embedded contents should be whole earth basically when this method is used. (green part on Example) Tiling is impossible unless this situation is changed. + </div> + </foreignObject> + + <!-- --> + <rect id="rect-1" width="200" height="200" fill="blue" clip-path="url(#clipInClip1)"/> + <rect id="rect-2" width="200" height="200" fill="blue" clip-path="url(#clipRects1)"/> + <rect id="rect-3" width="300" height="300" fill="blue" clip-path="url(#clipOnClip1)"/> + + <g clip-path="url(#clipCircle1)" id="g1"> + <use xlink:href="#c2" fill="red"/> + <use xlink:href="#p1" fill="red" fill-rule="evenodd"/> + </g> + +</svg> diff --git a/dom/svg/test/getCTM-helper.svg b/dom/svg/test/getCTM-helper.svg new file mode 100644 index 0000000000..835efc5067 --- /dev/null +++ b/dom/svg/test/getCTM-helper.svg @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100" height="100" viewBox="-11 -22 100 100"> + <g transform="translate(3, 4)"> + <svg x="1" y="2" width="888" height="999"> + <g> + <svg x="30" y="40" width="100" height="100"> + <g id="buggy"/> + </svg> + + <defs> + <symbol id="sym" width="100" height="100"> + <rect id="symbolRect" width="0" height="0" + transform="translate(70, 80)"/> + </symbol> + </defs> + <svg id="inner" x="30" y="40" width="100" height="100"> + <g id="g1"/> + </svg> + <svg id="inner-2" viewBox="0 0 10 10" width="-10" height="10"> + <g id="g5"/> + </svg> + <foreignObject id="fO" x="30" y="40" width="100" height="100" transform="translate(1, 1)"> + <!-- current layout implementation ignores x="50" and y="60". + thus, I made getCTM and getScreenCTM do the same. --> + <svg id="outer" x="50" y="60" width="100" height="100"> + <g id="g2" transform="translate(600, 700)"/> + </svg> + </foreignObject> + <foreignObject x="30" y="40" width="100" height="100" transform="translate(1, 1)"> + <html xmlns="http://www.w3.org/1999/xhtml" style="width: 100%; height: 100%"> + <svg xmlns="http://www.w3.org/2000/svg" id="outer2" + width="100" height="100" viewBox="100 100 200 200"/> + </html> + </foreignObject> + <!-- something invalid --> + <foreignObject> + <g id="g3"/> + </foreignObject> + <image> + <g id="g4"/> + </image> + <use xlink:href="#sym" id="use"/> + </g> + </svg> + </g> +</svg> diff --git a/dom/svg/test/getSubStringLength-helper.svg b/dom/svg/test/getSubStringLength-helper.svg new file mode 100644 index 0000000000..6c80d9d46b --- /dev/null +++ b/dom/svg/test/getSubStringLength-helper.svg @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="750"> + <style type="text/css"> +.t { font: 20px monospace; } + </style> + <text id="text" x="5" class="t" y="25">abc</text> +</svg> diff --git a/dom/svg/test/matrixUtils.js b/dom/svg/test/matrixUtils.js new file mode 100644 index 0000000000..ed9b8297b9 --- /dev/null +++ b/dom/svg/test/matrixUtils.js @@ -0,0 +1,78 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Utilities for testing SVG matrices + */ + +function createMatrix(a, b, c, d, e, f) { + var svg = document.getElementsByTagName("svg")[0]; + var m = svg.createSVGMatrix(); + m.a = a; + m.b = b; + m.c = c; + m.d = d; + m.e = e; + m.f = f; + return m; +} + +// Lightweight dummy Matrix class for representing arrays that get passed in +function MatrixFromArray(a) { + this.a = a[0]; + this.b = a[1]; + this.c = a[2]; + this.d = a[3]; + this.e = a[4]; + this.f = a[5]; +} + +function cmpMatrix(a, b, msg) { + if (a.constructor === Array) { + a = new MatrixFromArray(a); + } + if (b.constructor === Array) { + b = new MatrixFromArray(b); + } + + ok( + a.a == b.a && + a.b == b.b && + a.c == b.c && + a.d == b.d && + a.e == b.e && + a.f == b.f, + msg + " - got " + formatMatrix(a) + ", expected " + formatMatrix(b) + ); +} + +function roughCmpMatrix(a, b, msg) { + if (a.constructor === Array) { + a = new MatrixFromArray(a); + } + if (b.constructor === Array) { + b = new MatrixFromArray(b); + } + + const tolerance = 1 / 65535; + ok( + Math.abs(b.a - a.a) < tolerance && + Math.abs(b.b - a.b) < tolerance && + Math.abs(b.c - a.c) < tolerance && + Math.abs(b.d - a.d) < tolerance && + Math.abs(b.e - a.e) < tolerance && + Math.abs(b.f - a.f) < tolerance, + msg + " - got " + formatMatrix(a) + ", expected " + formatMatrix(b) + ); +} + +function formatMatrix(m) { + if (m.constructor != Array) { + return "(" + [m.a, m.b, m.c, m.d, m.e, m.f].join(", ") + ")"; + } + + return "(" + m.join(", ") + ")"; +} diff --git a/dom/svg/test/mochitest.ini b/dom/svg/test/mochitest.ini new file mode 100644 index 0000000000..7fec193463 --- /dev/null +++ b/dom/svg/test/mochitest.ini @@ -0,0 +1,114 @@ +[DEFAULT] +prefs = + dom.svg.pathSeg.enabled=true + layout.css.d-property.enabled=true +support-files = + MutationEventChecker.js + a_href_destination.svg + a_href_helper_01.svg + a_href_helper_02_03.svg + a_href_helper_04.svg + a_href_helper_05.svg + a_href_helper_06.svg + a_href_helper_07.svg + animated-svg-image-helper.html + animated-svg-image-helper.svg + bbox-helper.svg + bounds-helper.svg + dataTypes-helper.svg + fragments-helper.svg + getBBox-method-helper.svg + getCTM-helper.svg + getSubStringLength-helper.svg + matrixUtils.js + object-delayed-intrinsic-size.sjs + pointer-events.js + scientific-helper.svg + selectSubString-helper.svg + switch-helper.svg + text-helper-scaled.svg + text-helper-selection.svg + text-helper.svg + viewport-helper.svg + +[test_a_href_01.xhtml] +[test_a_href_02.xhtml] +[test_animLengthObjectIdentity.xhtml] +[test_animLengthReadonly.xhtml] +[test_animLengthUnits.xhtml] +[test_bbox-with-invalid-viewBox.xhtml] +[test_bbox.xhtml] +[test_bbox-changes.xhtml] +[test_bounds.html] +[test_bug872812.html] +[test_bug1426594.html] +[test_getBBox-method.html] +[test_dataTypes.html] +[test_dataTypesModEvents.html] +[test_fragments.html] +[test_getCTM.html] +[test_getElementById.xhtml] +[test_getPathSegListAtLength_with_d_property.html] +[test_getSubStringLength.xhtml] +[test_getTotalLength.xhtml] +[test_hit-testing-and-viewbox.xhtml] +[test_lang.xhtml] +skip-if = true # disabled-for-intermittent-failures--bug-701060 +[test_length.xhtml] +[test_lengthParsing.html] +[test_markerOrient.xhtml] +[test_nonAnimStrings.xhtml] +[test_non-scaling-stroke.html] +[test_object-delayed-intrinsic-size.html] +[test_onerror.xhtml] +[test_onload.xhtml] +[test_onload2.xhtml] +[test_pairParsing.html] +[test_pathAnimInterpolation.xhtml] +skip-if = true # We need to polyfill the SVG DOM for path data +[test_pointAtLength.xhtml] +[test_pointer-events-1a.xhtml] +[test_pointer-events-1b.xhtml] +[test_pointer-events-2.xhtml] +[test_pointer-events-3.xhtml] +[test_pointer-events-4.xhtml] +[test_pointer-events-6.xhtml] +[test_pointer-events-7.xhtml] +[test_scientific.html] +[test_selectSubString.xhtml] +[test_style_sheet.html] +[test_stroke-hit-testing.xhtml] +[test_stroke-linecap-hit-testing.xhtml] +[test_SVGLengthList-2.xhtml] +[test_SVGLengthList.xhtml] +[test_SVGMatrix.xhtml] +[test_SVG_namespace_ids.html] +[test_SVGNumberList.xhtml] +[test_SVGPointList.xhtml] +[test_SVGStringList.xhtml] +[test_SVGStyleElement.xhtml] +[test_SVGTransformListAddition.xhtml] +[test_SVGTransformList.xhtml] +[test_SVGxxxListIndexing.xhtml] +[test_SVGxxxList.xhtml] +[test_switch.xhtml] +[test_tabindex.html] +[test_tearoff_with_cc.html] +support-files = tearoff_with_cc_helper.html +[test_text_2.html] +[test_text_dirty.html] +[test_text.html] +[test_text_lengthAdjust.html] +[test_text_scaled.html] +[test_text_selection.html] +[test_text_update.html] +[test_transform.xhtml] +[test_transformParsing.html] +[test_use_with_hsts.html] +support-files = use-with-hsts-helper.html use-with-hsts-helper.html^headers^ +scheme = https +[test_valueAsString.xhtml] +[test_valueLeaks.xhtml] +[test_viewBox.html] +[test_viewport.html] + diff --git a/dom/svg/test/object-delayed-intrinsic-size.sjs b/dom/svg/test/object-delayed-intrinsic-size.sjs new file mode 100644 index 0000000000..6510f91eac --- /dev/null +++ b/dom/svg/test/object-delayed-intrinsic-size.sjs @@ -0,0 +1,24 @@ +var timer = null; + +function handleRequest(request, response) { + response.processAsync(); + + response.setStatusLine(null, 200, "OK"); + response.setHeader("Content-Type", "image/svg+xml", false); + + // We need some body output or else gecko will not do an initial reflow + // while waiting for the rest of the document to load: + response.bodyOutputStream.write("\n", 1); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback( + function() { + var body = + "<svg xmlns='http://www.w3.org/2000/svg' width='70' height='0'></svg>"; + response.bodyOutputStream.write(body, body.length); + response.finish(); + }, + 1000 /* milliseconds */, + Ci.nsITimer.TYPE_ONE_SHOT + ); +} diff --git a/dom/svg/test/pointer-events.js b/dom/svg/test/pointer-events.js new file mode 100644 index 0000000000..e65fd3d390 --- /dev/null +++ b/dom/svg/test/pointer-events.js @@ -0,0 +1,328 @@ +SimpleTest.waitForExplicitFinish(); + +var pointer_events_values = [ + "auto", + "visiblePainted", + "visibleFill", + "visibleStroke", + "visible", + "painted", + "fill", + "stroke", + "all", + "none", +]; + +var paint_values = ["blue", "transparent", "none"]; + +var opacity_values = ["1", "0.5", "0"]; + +var visibility_values = ["visible", "hidden", "collapse"]; + +/** + * List of attributes and various values for which we want to test permutations + * when hit testing a pointer event that is over an element's fill area, + * stroke area, or both (where they overlap). + * + * We're using an array of objects so that we have control over the order in + * which permutations are tested. + * + * TODO: test the effect of clipping, masking, filters, markers, etc. + */ +var hit_test_inputs = { + fill: [ + { name: "pointer-events", values: pointer_events_values }, + { name: "fill", values: paint_values }, + { name: "fill-opacity", values: opacity_values }, + { name: "opacity", values: opacity_values }, + { name: "visibility", values: visibility_values }, + ], + stroke: [ + { name: "pointer-events", values: pointer_events_values }, + { name: "stroke", values: paint_values }, + { name: "stroke-opacity", values: opacity_values }, + { name: "opacity", values: opacity_values }, + { name: "visibility", values: visibility_values }, + ], + both: [ + { name: "pointer-events", values: pointer_events_values }, + { name: "fill", values: paint_values }, + { name: "fill-opacity", values: opacity_values }, + { name: "stroke", values: paint_values }, + { name: "stroke-opacity", values: opacity_values }, + { name: "opacity", values: opacity_values }, + { name: "visibility", values: visibility_values }, + ], +}; + +/** + * The following object contains a list of 'pointer-events' property values, + * each with an object detailing the conditions under which the fill and stroke + * of a graphical object will intercept pointer events for the given value. If + * the object contains a 'fill-intercepts-iff' property then the fill is + * expected to intercept pointer events for that value of 'pointer-events' if + * and only if the conditions listed in the 'fill-intercepts-iff' object are + * met. If there are no conditions in the 'fill-intercepts-iff' object then the + * fill should always intercept pointer events. However, if the + * 'fill-intercepts-iff' property is not set at all then it indicates that the + * fill should never intercept pointer events. The same rules apply for + * 'stroke-intercepts-iff'. + * + * If an attribute name in the conditions list is followed by the "!" + * character then the requirement for a hit is that its value is NOT any + * of the values listed in the given array. + */ +var hit_conditions = { + auto: { + "fill-intercepts-iff": { + visibility: ["visible"], + "fill!": ["none"], + }, + "stroke-intercepts-iff": { + visibility: ["visible"], + "stroke!": ["none"], + }, + }, + visiblePainted: { + "fill-intercepts-iff": { + visibility: ["visible"], + "fill!": ["none"], + }, + "stroke-intercepts-iff": { + visibility: ["visible"], + "stroke!": ["none"], + }, + }, + visibleFill: { + "fill-intercepts-iff": { + visibility: ["visible"], + }, + // stroke never intercepts pointer events + }, + visibleStroke: { + // fill never intercepts pointer events + "stroke-intercepts-iff": { + visibility: ["visible"], + }, + }, + visible: { + "fill-intercepts-iff": { + visibility: ["visible"], + }, + "stroke-intercepts-iff": { + visibility: ["visible"], + }, + }, + painted: { + "fill-intercepts-iff": { + "fill!": ["none"], + }, + "stroke-intercepts-iff": { + "stroke!": ["none"], + }, + }, + fill: { + "fill-intercepts-iff": { + // fill always intercepts pointer events + }, + // stroke never intercepts pointer events + }, + stroke: { + // fill never intercepts pointer events + "stroke-intercepts-iff": { + // stroke always intercepts pointer events + }, + }, + all: { + "fill-intercepts-iff": { + // fill always intercepts pointer events + }, + "stroke-intercepts-iff": { + // stroke always intercepts pointer events + }, + }, + none: { + // neither fill nor stroke intercept pointer events + }, +}; + +// bit flags +var POINT_OVER_FILL = 0x1; +var POINT_OVER_STROKE = 0x2; + +/** + * Examine the element's attribute values and, based on the area(s) of the + * element that the pointer event is over (fill and/or stroke areas), return + * true if the element is expected to intercept the event, otherwise false. + */ +function hit_expected( + element, + over /* bit flags indicating which area(s) of the element the pointer is over */ +) { + function expect_hit(target) { + var intercepts_iff = + hit_conditions[element.getAttribute("pointer-events")][ + target + "-intercepts-iff" + ]; + + if (!intercepts_iff) { + return false; // never intercepts events + } + + for (var attr in intercepts_iff) { + var vals = intercepts_iff[attr]; // must get this before we adjust 'attr' + var invert = false; + if (attr.substr(-1) == "!") { + invert = true; + attr = attr.substr(0, attr.length - 1); + } + var match = vals.indexOf(element.getAttribute(attr)) > -1; + if (invert) { + match = !match; + } + if (!match) { + return false; + } + } + + return true; + } + + return ( + ((over & POINT_OVER_FILL) != 0 && expect_hit("fill")) || + ((over & POINT_OVER_STROKE) != 0 && expect_hit("stroke")) + ); +} + +function for_all_permutations(inputs, callback) { + var current_permutation = arguments[2] || {}; + var index = arguments[3] || 0; + + if (index < inputs.length) { + var name = inputs[index].name; + var values = inputs[index].values; + for (var i = 0; i < values.length; ++i) { + current_permutation[name] = values[i]; + for_all_permutations(inputs, callback, current_permutation, index + 1); + } + return; + } + + callback(current_permutation); +} + +function make_log_msg(over, tag, attributes) { + var target; + if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) { + target = "fill and stroke"; + } else if (over == POINT_OVER_FILL) { + target = "fill"; + } else if (over == POINT_OVER_STROKE) { + target = "stroke"; + } else { + throw new Error("unexpected bit combination in 'over'"); + } + var msg = + "Check if events are intercepted at a point over the " + + target + + " on <" + + tag + + "> for"; + for (var attr in attributes) { + msg += " " + attr + "=" + attributes[attr]; + } + return msg; +} + +var dx, dy; // offset of <svg> element from pointer coordinates origin + +function test_element( + id, + x, + y, + over /* bit flags indicating which area(s) of the element the pointer is over */ +) { + var element = document.getElementById(id); + var tag = element.tagName; + + function test_permutation(attributes) { + for (var attr in attributes) { + element.setAttribute(attr, attributes[attr]); + } + var hits = document.elementFromPoint(dx + x, dy + y) == element; + var msg = make_log_msg(over, tag, attributes); + + is(hits, hit_expected(element, over), msg); + } + + var inputs; + if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) { + inputs = hit_test_inputs.both; + } else if (over == POINT_OVER_FILL) { + inputs = hit_test_inputs.fill; + } else if (over == POINT_OVER_STROKE) { + inputs = hit_test_inputs.stroke; + } else { + throw new Error("unexpected bit combination in 'over'"); + } + + for_all_permutations(inputs, test_permutation); + + // To reduce the chance of bogus results in subsequent tests: + element.setAttribute("fill", "none"); + element.setAttribute("stroke", "none"); +} + +function run_tests(subtest) { + var div = document.getElementById("div"); + dx = div.offsetLeft; + dy = div.offsetTop; + + // Run the test with only a subset of pointer-events values, to avoid + // running over the mochitest time limit. The subtest argument indicates + // whether to use the first half of the pointer-events values (0) + // or the second half (1). + var partition = Math.floor(pointer_events_values.length / 2); + switch (subtest) { + case 0: + pointer_events_values.splice(partition); + break; + case 1: + pointer_events_values.splice(0, partition); + break; + case 2: + throw new Error("unexpected subtest number"); + } + + test_element("rect", 30, 30, POINT_OVER_FILL); + test_element("rect", 5, 5, POINT_OVER_STROKE); + + // The SVG 1.1 spec essentially says that, for text, hit testing is done + // against the character cells of the text, and not the fill and stroke as + // you might expect for a normal graphics element like <path>. See the + // paragraph starting "For text elements..." in this section: + // + // http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty + // + // This requirement essentially means that for the purposes of hit testing + // the fill and stroke areas are the same area - the character cell. (At + // least until we support having any fill or stroke that lies outside the + // character cells intercept events like Opera does - see below.) Thus, for + // text, when a pointer event is over a character cell it is essentially over + // both the fill and stroke at the same time. That's the reason we pass both + // the POINT_OVER_FILL and POINT_OVER_STROKE bits in test_element's 'over' + // argument below. It's also the reason why we only test one point in the + // text rather than having separate tests for fill and stroke. + // + // For hit testing of text, Opera essentially treats fill and stroke like it + // would on any normal element, but it adds the character cells of glyhs to + // both the glyphs' fill AND stroke. I think this is what we should do too. + // It's compatible with the letter of the SVG 1.1 rules, and it allows any + // parts of a glyph that are outside the glyph's character cells to also + // intercept events in the normal way. When we make that change we'll be able + // to add separate fill and stroke tests for text below. + + test_element("text", 210, 30, POINT_OVER_FILL | POINT_OVER_STROKE); + + SimpleTest.finish(); +} diff --git a/dom/svg/test/scientific-helper.svg b/dom/svg/test/scientific-helper.svg new file mode 100644 index 0000000000..8ac2227e62 --- /dev/null +++ b/dom/svg/test/scientific-helper.svg @@ -0,0 +1,5 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="750"> + <rect id="rect" stroke-width="10"/> + <text id="text">x</text> +</svg> diff --git a/dom/svg/test/selectSubString-helper.svg b/dom/svg/test/selectSubString-helper.svg new file mode 100644 index 0000000000..6c80d9d46b --- /dev/null +++ b/dom/svg/test/selectSubString-helper.svg @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="750"> + <style type="text/css"> +.t { font: 20px monospace; } + </style> + <text id="text" x="5" class="t" y="25">abc</text> +</svg> diff --git a/dom/svg/test/switch-helper.svg b/dom/svg/test/switch-helper.svg new file mode 100644 index 0000000000..842296efa7 --- /dev/null +++ b/dom/svg/test/switch-helper.svg @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ +--> +<svg version="1.1" xmlns="http://www.w3.org/2000/svg"> + <switch id="s"> + <rect systemLanguage="fr" id="first" x="75" y="100" width="70" height="70" fill="yellow"/> + <rect id="second" x="75" y="100" width="50" height="50" fill="lime"/> + <rect id="third" x="75" y="100" width="80" height="80" fill="red"/> + </switch> +</svg> diff --git a/dom/svg/test/tearoff_with_cc_helper.html b/dom/svg/test/tearoff_with_cc_helper.html new file mode 100644 index 0000000000..6d63e939fe --- /dev/null +++ b/dom/svg/test/tearoff_with_cc_helper.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<body onload="go()"> + <svg id="outerSvg" width="50%" height="50%" + style="border: 1px solid black"> + </svg> + <script type="application/javascript"> + /* I'm not sure what exactly was required to trigger bug 1288228's crash, + * but it involved tweaking a length's specified units and cycle-collecting + * and reloading (in some combination). So, we'll tweak the units and + * cycle-collect a few times, and message the outer page to reload us + * after we've made the first tweak. + */ + const maxTweaks = 5; + let remainingTweaks = maxTweaks; + + var savedBaseVal = document.getElementById("outerSvg").width.baseVal; + function go() { + window.parent.SpecialPowers.DOMWindowUtils.cycleCollect(); + tweak(); + } + + function tweak() { + console.log("tweaked"); + savedBaseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX); + savedBaseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PERCENTAGE); + if (remainingTweaks == maxTweaks) { + window.parent.postMessage("ping", "*"); // only do this on first tweak + } + if (--remainingTweaks) { + setTimeout(tweak, 0); + } + } +</script> +</body> +</html> diff --git a/dom/svg/test/test_SVGLengthList-2.xhtml b/dom/svg/test/test_SVGLengthList-2.xhtml new file mode 100644 index 0000000000..0d8b3a8737 --- /dev/null +++ b/dom/svg/test/test_SVGLengthList-2.xhtml @@ -0,0 +1,64 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=630760 +--> +<head> + <title>Tests specific to SVGLengthList</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug 630760</a> +<p id="display"></p> +<div id="content" style="display:none;"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <text id="text"> + <set attributeName="x" to="10 20 30 40" begin="0" dur="indefinite"/> + </text> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run_tests() { + var svg = document.getElementById("svg"); + svg.pauseAnimations(); + + // Check that the animVal list for 'x' on <text> gives the correct number of + // items when examined for the FIRST time DURING animation: + + var text = document.getElementById("text"); + var list = text.x.animVal; + + is(list.numberOfItems, 4, "Checking numberOfItems"); + + // Check that items at an index larger than 255 (max value for PRUint8) are + // returning the correct values: + + var item; + list = text.x.baseVal; + for (var i = 0; i < 256; ++i) { + item = svg.createSVGLength(); + item.value = 1; + list.appendItem(item); + } + item = svg.createSVGLength(); + item.value = 2; + list.appendItem(item); + + is(list.getItem(0).value, 1, "Check value of first item"); + is(list.getItem(256).value, 2, "Check value of item at index > 255"); + + SimpleTest.finish(); +} + +window.addEventListener("load", run_tests); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGLengthList.xhtml b/dom/svg/test/test_SVGLengthList.xhtml new file mode 100644 index 0000000000..87b5c40d6b --- /dev/null +++ b/dom/svg/test/test_SVGLengthList.xhtml @@ -0,0 +1,158 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=515116 +--> +<head> + <title>Tests specific to SVGLengthList</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="MutationEventChecker.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug 515116</a> +<p id="display"></p> +<div id="content" style="display:none;"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <text id="text" x="10cm 20cm 30mc"/> + <rect id="rect" x="40" y="50"/> + <text id="text2" x="60"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/* +This file runs a series of SVGLengthList specific tests. Generic SVGXxxList +tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized +to other list types belongs there. +*/ + +function run_tests() { + document.getElementById("svg").pauseAnimations(); + + var text = document.getElementById("text"); + var lengths = text.x.baseVal; + + is(lengths.numberOfItems, 0, "Checking numberOfItems"); + + // Test mutation events + // --- Initialization + var eventChecker = new MutationEventChecker; + eventChecker.watchAttr(text, "x"); + eventChecker.expect("modify"); + text.textContent = "abc"; + text.setAttribute("x", "10 20 30"); + is(lengths.numberOfItems, 3, "Checking numberOfItems"); + // -- Actual changes + eventChecker.expect("modify modify modify modify modify"); + lengths[0].value = 8; + lengths[0].valueInSpecifiedUnits = 9; + lengths[0].valueAsString = "10"; + lengths[0].convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_CM); + lengths[0].newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_MM, 11); + // -- Redundant changes + eventChecker.expect("modify"); + lengths[0].valueAsString = "10"; + eventChecker.expect(""); + lengths[0].value = 10; + lengths[0].valueInSpecifiedUnits = 10; + lengths[0].valueAsString = "10"; + lengths[0].convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_NUMBER); + lengths[0].newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_NUMBER, 10); + // -- Invalid attribute + eventChecker.expect("modify"); + text.setAttribute("x", ",20"); + is(lengths.numberOfItems, 0, "Checking that parsing stops at invalid token"); + // -- Attribute removal + eventChecker.expect("remove"); + text.removeAttribute("x"); + // -- Non-existent attribute removal + eventChecker.expect(""); + text.removeAttribute("x"); + text.removeAttributeNS(null, "x"); + eventChecker.finish(); + + // Test that the addition of an owned SVGLength to an SVGLengthList creates a + // copy of the SVGLength, and an unowned SVGLength does not make a copy + var text2 = document.getElementById("text2"); + var rect = document.getElementById("rect"); + var subtests = [ + function initialize(aItem) { + text.removeAttribute("x"); + return lengths.initialize(aItem); + }, + function insertItemBefore(aItem) { + text.removeAttribute("x"); + return lengths.insertItemBefore(aItem, 0); + }, + function replaceItem(aItem) { + text.setAttribute("x", "10"); + return lengths.replaceItem(aItem, 0); + }, + function appendItem(aItem) { + text.removeAttribute("x"); + return lengths.appendItem(aItem); + }, + ]; + subtests.forEach(function(aFunction) { + // -- Adding an unowned SVGLength + var name = aFunction.name; + var existingItem = document.getElementById("svg").createSVGLength(); + var newItem = aFunction(existingItem); + is(newItem, lengths.getItem(0), name + " return value is correct when passed an unowned object"); + is(newItem, existingItem, name + " did not make a copy when passed an unowned object"); + }); + subtests.forEach(function(aFunction) { + // -- Adding an SVGLength that is a baseVal + var name = aFunction.name; + var existingItem = rect.x.baseVal; + var newItem = aFunction(existingItem); + is(newItem, lengths.getItem(0), name + " return value is correct when passed a baseVal"); + isnot(newItem, existingItem, name + " made a copy when passed a baseVal"); + is(newItem.value, existingItem.value, name + " made a copy with the right values when passed a baseVal"); + is(rect.x.baseVal, existingItem, name + " left the original object alone when passed a baseVal"); + }); + subtests.forEach(function(aFunction) { + // -- Adding an SVGLength that is an animVal + var name = aFunction.name; + var existingItem = rect.x.animVal; + var newItem = aFunction(existingItem); + is(newItem, lengths.getItem(0), name + " return value is correct when passed an animVal"); + isnot(newItem, existingItem, name + " made a copy when passed an animVal"); + is(newItem.value, existingItem.value, name + " made a copy with the right values when passed an animVal"); + is(rect.x.animVal, existingItem, name + " left the original object alone when passed an animVal"); + }); + subtests.forEach(function(aFunction) { + // -- Adding an SVGLength that is in a baseVal list + var name = aFunction.name; + var existingItem = text2.x.baseVal.getItem(0); + var newItem = aFunction(existingItem); + is(newItem, lengths.getItem(0), name + " return value is correct when passed a baseVal list item"); + isnot(newItem, existingItem, name + " made a copy when passed a baseVal list item"); + is(newItem.value, existingItem.value, name + " made a copy with the right values when passed a baseVal list item"); + is(text2.x.baseVal.getItem(0), existingItem, name + " left the original object alone when passed a baseVal list item"); + }); + subtests.forEach(function(aFunction) { + // -- Adding an SVGLength that is in a animVal list + var name = aFunction.name; + var existingItem = text2.x.animVal.getItem(0); + var newItem = aFunction(existingItem); + is(newItem, lengths.getItem(0), name + " return value is correct when passed a animVal list item"); + isnot(newItem, existingItem, name + " made a copy when passed a animVal list item"); + is(newItem.value, existingItem.value, name + " made a copy with the right values when passed a animVal list item"); + is(text2.x.animVal.getItem(0), existingItem, name + " left the original object alone when passed a animVal list item"); + }); + + SimpleTest.finish(); +} + +window.addEventListener("load", run_tests); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGMatrix.xhtml b/dom/svg/test/test_SVGMatrix.xhtml new file mode 100644 index 0000000000..9f5b50b555 --- /dev/null +++ b/dom/svg/test/test_SVGMatrix.xhtml @@ -0,0 +1,180 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test SVGMatrix behavior</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="matrixUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="1" id="svg"> + <g id="g" transform="translate(10, 20)"/> + </svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function main() { + var tests = + [ testCreateMatrix, + testMultiply, + testInverse, + testTranslate, + testScale, + testScaleNonUniform, + testRotate, + testRotateFromVector, + testFlipX, + testFlipY, + testSkewX, + testSkewY, + ]; + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + SimpleTest.finish(); +} + +function testCreateMatrix() { + var svg = $("svg"); + var m = svg.createSVGMatrix(); + + // Should be initialised to identity + cmpMatrix(m, [1, 0, 0, 1, 0, 0], + "createMatrix should produce identity matrix"); + + // Should return a new object each time; + ok(m != svg.createSVGMatrix(), + "Got identical objects when creating new matrix"); +} + +// SVGMatrix multiply(in SVGMatrix secondMatrix); +function testMultiply() { + // This is the example from SVG 1.1 section 7.5 + var m1 = createMatrix(1, 0, 0, 1, 50, 90); + var m2 = createMatrix(0.707, -0.707, 0.707, 0.707, 0, 0); + var m3 = createMatrix(1, 0, 0, 1, 130, 160); + var result = m1.multiply(m2).multiply(m3); + roughCmpMatrix(result, [0.707, -0.707, 0.707, 0.707, 255.03, 111.21], + "Unexpected result after multiplying matrices"); + + // Check orig matrices are unchanged + cmpMatrix(m1, [1, 0, 0, 1, 50, 90], "Matrix changed after multiplication"); + roughCmpMatrix(m2, [0.707, -0.707, 0.707, 0.707, 0, 0], + "Matrix changed after multiplication"); + cmpMatrix(m3, [1, 0, 0, 1, 130, 160], "Matrix changed after multiplication"); +} + +// SVGMatrix inverse() raises(SVGException); +function testInverse() { + // Test inversion + var m = createMatrix(2, 0, 0, 4, 110, -50); + roughCmpMatrix(m.inverse(), [0.5, 0, 0, 0.25, -55, 12.5], + "Unexpected result after inverting matrix"); + + // Test non-invertable + m = createMatrix(0, 0, 1, 0, 0, 0); + try { + m.inverse(); + ok(false, "Failed to throw exception when inverting singular matrix"); + } catch (e) { + is(e.name, "InvalidStateError", + "Got unexpected exception " + e + ", expected InvalidStateError"); + } +} + +// SVGMatrix translate(in float x, in float y); +function testTranslate() { + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.translate(100, -50), [2, 0, 0, 1, 320, 50], + "Unexpected result after translate"); +} + +// SVGMatrix scale(in float scaleFactor); +function testScale() { + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.scale(0.5), [1, 0, 0, 0.5, 120, 100], + "Unexpected result after scale"); +} + +// SVGMatrix scaleNonUniform(in float scaleFactorX, in float scaleFactorY); +function testScaleNonUniform() { + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.scaleNonUniform(0.5, -3), [1, 0, 0, -3, 120, 100], + "Unexpected result after scaleNonUniform"); +} + +// SVGMatrix rotate(in float angle); +function testRotate() { + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.rotate(45), + [2 * Math.cos(Math.PI / 4), Math.sin(Math.PI / 4), + 2 * -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4), + 120, 100], + "Unexpected result after rotate"); +} + +// SVGMatrix rotateFromVector(in float x, in float y) raises(SVGException); +function testRotateFromVector() { + var m = createMatrix(2, 0, 0, 1, 120, 100); + // Make a 150 degree angle + var result = m.rotateFromVector(-2, 1.1547); + roughCmpMatrix(result, + [2 * Math.cos(5 * Math.PI / 6), Math.sin(5 * Math.PI / 6), + 2 * -Math.sin(5 * Math.PI / 6), Math.cos(5 * Math.PI / 6), + 120, 100], + "Unexpected result after rotateFromVector"); + + // Test bad input (1) + try { + m.rotateFromVector(1, 0); + ok(false, "Failed to throw exception with zero coord for rotateFromVector"); + } catch (e) { + is(e.name, "InvalidAccessError", + "Got unexpected exception " + e + ", expected TypeError"); + } + + // Test bad input (2) + try { + m.rotateFromVector(0, 1); + ok(false, "Failed to throw exception with zero coord for rotateFromVector"); + } catch (e) { } +} + +// SVGMatrix flipX(); +function testFlipX() { + var m = createMatrix(1, 2, 3, 4, 5, 6); + cmpMatrix(m.flipX(), [-1, -2, 3, 4, 5, 6], "Unexpected result after flipX"); +} + +// SVGMatrix flipY(); +function testFlipY() { + var m = createMatrix(1, 2, 3, 4, 5, 6); + cmpMatrix(m.flipY(), [1, 2, -3, -4, 5, 6], "Unexpected result after flipY"); +} + +// SVGMatrix skewX(in float angle); +function testSkewX() { + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.skewX(30), [2, 0, 2 * Math.tan(Math.PI / 6), 1, 120, 100], + "Unexpected result after skewX"); +} + +// SVGMatrix skewY(in float angle); +function testSkewY() { + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.skewY(30), [2, Math.tan(Math.PI / 6), 0, 1, 120, 100], + "Unexpected result after skewY"); +} + +window.addEventListener("load", main); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGNumberList.xhtml b/dom/svg/test/test_SVGNumberList.xhtml new file mode 100644 index 0000000000..15198b5783 --- /dev/null +++ b/dom/svg/test/test_SVGNumberList.xhtml @@ -0,0 +1,74 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=629200 +--> +<head> + <title>Tests specific to SVGNumberList</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="MutationEventChecker.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=629200">Mozilla Bug 629200</a> +<p id="display"></p> +<div id="content" style="display:none;"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <text id="text" rotate="10 20 30">abc</text> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/* +This file runs a series of SVGNumberList specific tests. Generic SVGXxxList +tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized +to other list types belongs there. +*/ + +function run_tests() { + document.getElementById("svg").pauseAnimations(); + + var text = document.getElementById("text"); + var numbers = text.rotate.baseVal; + + is(numbers.numberOfItems, 3, "Checking numberOfItems"); + + // Test mutation events + // --- Initialization + var eventChecker = new MutationEventChecker; + eventChecker.watchAttr(text, "rotate"); + // -- Actual changes + eventChecker.expect("modify modify"); + numbers[0].value = 15; + text.setAttribute("rotate", "17 20 30"); + // -- Redundant changes + eventChecker.expect(""); + numbers[0].value = 17; + numbers[1].value = 20; + text.setAttribute("rotate", "17 20 30"); + // -- Invalid attribute + eventChecker.expect("modify"); + text.setAttribute("rotate", ",20"); + is(numbers.numberOfItems, 0, "Checking that parsing stops at invalid token"); + // -- Attribute removal + eventChecker.expect("remove"); + text.removeAttribute("rotate"); + // -- Non-existent attribute removal + eventChecker.expect(""); + text.removeAttribute("rotate"); + text.removeAttributeNS(null, "rotate"); + eventChecker.finish(); + + SimpleTest.finish(); +} + +window.addEventListener("load", run_tests); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGPointList.xhtml b/dom/svg/test/test_SVGPointList.xhtml new file mode 100644 index 0000000000..9a0d661eca --- /dev/null +++ b/dom/svg/test/test_SVGPointList.xhtml @@ -0,0 +1,129 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=629200 +--> +<head> + <title>Tests specific to SVGPointList</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="MutationEventChecker.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=629200">Mozilla Bug 629200</a> +<p id="display"></p> +<div id="content" style="display:none;"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <polyline id="polyline" points="50,375 150,380"/> + <polyline id="polyline2" points="10,20"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/* +This file runs a series of SVGPointList specific tests. Generic SVGXxxList +tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized +to other list types belongs there. +*/ + +function run_tests() { + document.getElementById("svg").pauseAnimations(); + + var polyline = document.getElementById("polyline"); + var points = polyline.points; + + is(points.numberOfItems, 2, "Checking numberOfItems"); + + // Test mutation events + // --- Initialization + var eventChecker = new MutationEventChecker; + eventChecker.watchAttr(polyline, "points"); + // -- Actual changes + eventChecker.expect("modify modify"); + points[0].x = 40; + polyline.setAttribute("points", "30,375 150,380"); + // -- Redundant changes + eventChecker.expect(""); + points[0].x = 30; + points[1].y = 380; + polyline.setAttribute("points", "30,375 150,380"); + // -- Invalid attribute + eventChecker.expect("modify"); + polyline.setAttribute("points", ",30,375"); + is(points.numberOfItems, 0, "Checking that parsing stops at invalid token"); + // -- Attribute removal + eventChecker.expect("remove"); + polyline.removeAttribute("points"); + // -- Non-existent attribute removal + eventChecker.expect(""); + polyline.removeAttribute("points"); + polyline.removeAttributeNS(null, "points"); + eventChecker.finish(); + + // Test that the addition of an owned SVGPoint to an SVGPointList creates a + // copy of the SVGPoint + var polyline2 = document.getElementById("polyline2"); + var subtests = [ + function initialize(aItem) { + polyline.removeAttribute("points"); + return points.initialize(aItem); + }, + function insertItemBefore(aItem) { + polyline.removeAttribute("points"); + return points.insertItemBefore(aItem, 0); + }, + function replaceItem(aItem) { + polyline.setAttribute("points", "10,20"); + return points.replaceItem(aItem, 0); + }, + function appendItem(aItem) { + polyline.removeAttribute("points"); + return points.appendItem(aItem); + }, + ]; + subtests.forEach(function(aFunction) { + // -- Adding SVGSVGElement.currentTranslate, which is the only instance + // of an owned, single SVGPoint + var svg = document.getElementById("svg"); + var name = aFunction.name; + var existingItem = svg.currentTranslate; + var newItem = aFunction(existingItem); + is(newItem, points.getItem(0), name + " return value is correct when passed currentTranslate"); + isnot(newItem, existingItem, name + " made a copy when passed currentTranslate"); + is(newItem.value, existingItem.value, name + " made a copy with the right values when passed currentTranslate"); + is(svg.currentTranslate, existingItem, name + " left the original object alone when passed currentTranslate"); + }); + subtests.forEach(function(aFunction) { + // -- Adding an SVGPoint that is in a baseVal list + var name = aFunction.name; + var existingItem = polyline2.points.getItem(0); + var newItem = aFunction(existingItem); + is(newItem, points.getItem(0), name + " return value is correct when passed a baseVal list item"); + isnot(newItem, existingItem, name + " made a copy when passed a baseVal list item"); + is(newItem.value, existingItem.value, name + " made a copy with the right values when passed a baseVal list item"); + is(polyline2.points.getItem(0), existingItem, name + " left the original object alone when passed a baseVal list item"); + }); + subtests.forEach(function(aFunction) { + // -- Adding an SVGPoint that is in a animVal list + var name = aFunction.name; + var existingItem = polyline2.animatedPoints.getItem(0); + var newItem = aFunction(existingItem); + is(newItem, points.getItem(0), name + " return value is correct when passed a animVal list item"); + isnot(newItem, existingItem, name + " made a copy when passed a animVal list item"); + is(newItem.value, existingItem.value, name + " made a copy with the right values when passed a animVal list item"); + is(polyline2.animatedPoints.getItem(0), existingItem, name + " left the original object alone when passed a animVal list item"); + }); + + SimpleTest.finish(); +} + +window.addEventListener("load", run_tests); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGStringList.xhtml b/dom/svg/test/test_SVGStringList.xhtml new file mode 100644 index 0000000000..faba904376 --- /dev/null +++ b/dom/svg/test/test_SVGStringList.xhtml @@ -0,0 +1,118 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=724993--> +<head> + <title>Tests specific to SVGStringList</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=724993">Mozilla Bug 724993</a> +<p id="display"></p> +<div id="content" style="display:none;"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <g id="g" requiredExtensions="foo bar baz"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/* +This file runs a series of SVGStringList specific tests. Generic SVGXxxList +tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized +to other list types belongs there. +*/ + +function initializeThrowsFor(stringList, value) { + try { + stringList.initialize(value); + } catch (e) { + return true; + } + return false; +} + +function insertItemBeforeThrowsFor(stringList, value) { + try { + stringList.insertItemBefore(value, 0); + } catch (e) { + return true; + } + return false; +} + +function replaceItemThrowsFor(stringList, value) { + try { + stringList.replaceItem(value, 0); + } catch (e) { + return true; + } + return false; +} + +function appendItemThrowsFor(stringList, value) { + try { + stringList.appendItem(value); + } catch (e) { + return true; + } + return false; +} + +function run_tests() { + var g = document.getElementById("g"); + var strings = g.requiredExtensions; + + // sanity check: + is(strings.numberOfItems, 3, "numberOfItems should be 3"); + + + ok(!initializeThrowsFor(strings, null), + "SVGStringList.initialize() should not throw when passed null"); + ok(initializeThrowsFor(strings, ""), + "SVGStringList.initialize() should throw when passed the empty string"); + is(strings.length, 0, "length should be 0"); + + ok(!insertItemBeforeThrowsFor(strings, null), + "SVGStringList.insertItemBefore() should not throw when passed null"); + ok(insertItemBeforeThrowsFor(strings, ""), + "SVGStringList.insertItemBefore() should throw when passed the empty string"); + is(strings.length, 1, "length should be 1"); + + ok(!replaceItemThrowsFor(strings, null), + "SVGStringList.replaceItem() should not throw when passed null"); + ok(replaceItemThrowsFor(strings, ""), + "SVGStringList.replaceItem() should throw when passed the empty string"); + is(strings.length, 1, "length should be 1"); + + ok(!appendItemThrowsFor(strings, null), + "SVGStringList.appendItem() should not throw when passed null"); + ok(appendItemThrowsFor(strings, ""), + "SVGStringList.appendItem() should throw when passed the empty string"); + is(strings.length, 2, "length should be 2"); + + + // more sanity checks: + ok(!initializeThrowsFor(strings, "valid-string"), + "SVGStringList.initialize() should not throw when passed a valid string"); + ok(!insertItemBeforeThrowsFor(strings, "valid-string"), + "SVGStringList.insertItemBefore() should not throw when passed a valid string"); + ok(!replaceItemThrowsFor(strings, "valid-string"), + "SVGStringList.replaceItem() should not throw when passed a valid string"); + ok(!appendItemThrowsFor(strings, "valid-string"), + "SVGStringList.appendItem() should not throw when passed a valid string"); + is(strings.length, 3, "numberOfItems should be 3"); + + SimpleTest.finish(); +} + +window.addEventListener("load", run_tests); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGStyleElement.xhtml b/dom/svg/test/test_SVGStyleElement.xhtml new file mode 100644 index 0000000000..41d449dee5 --- /dev/null +++ b/dom/svg/test/test_SVGStyleElement.xhtml @@ -0,0 +1,33 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=559024 +--> +<head> + <title>Test SVGStyleElement</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=559024">Mozilla Bug 559024</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg xmlns="http://www.w3.org/2000/svg"> +<style></style> +</svg> +</div> +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ +/** Test for Bug 559024 **/ +var el = document.getElementsByTagNameNS("http://www.w3.org/2000/svg", "style")[0]; +el.media = "ssss"; +is(el.media, "ssss"); +is(el.getAttributeNS("", "media"), "ssss"); +el.title = "tttt"; +is(el.title, "tttt"); +is(el.getAttributeNS("", "title"), "tttt"); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGTransformList.xhtml b/dom/svg/test/test_SVGTransformList.xhtml new file mode 100644 index 0000000000..3e1cd7b8bb --- /dev/null +++ b/dom/svg/test/test_SVGTransformList.xhtml @@ -0,0 +1,461 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602759 +--> +<head> + <title>Tests specific to SVGTransformList</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="matrixUtils.js"></script> + <script type="text/javascript" src="MutationEventChecker.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602759"> + Mozilla Bug 602759</a> +<p id="display"></p> +<div id="content" style="display:none;"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100" + onload="this.pauseAnimations();"> + <g id="g"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/* +This file runs a series of SVGTransformList specific tests. Generic SVGXxxList +tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized +to other list types belongs there. +*/ + +function main() { + var g = $("g"); + var tests = + [ testConsolidateMatrix, + testConsolidateMatrixOneElem, + testConsolidateMatrixZeroElem, + testCreateSVGTransformFromMatrix, + testReadOnly, + testOrphan, + testFailedSet, + testMutationEvents, + ]; + for (var i = 0; i < tests.length; i++) { + tests[i](g); + } + SimpleTest.finish(); +} + +function testConsolidateMatrix(g) { + // This is the example from SVG 1.1 section 7.5 + g.setAttribute("transform", + "translate(50 90) rotate(-45) translate(130 160)"); + var list = g.transform.baseVal; + is(list.numberOfItems, 3, "Unexpected length of unconsolidated list"); + + // Sanity check -- take ref to first item in list and validate it + var first_item = list.getItem(0); + is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE, + "Unexpected type of first item in list"); + cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90], + "Unexpected value for first item in list"); + + // Consolidate + var consolidated = list.consolidate(); + is(list.numberOfItems, 1, "Unexpected length of consolidated list"); + ok(consolidated === list.getItem(0), + "Consolidate return value should be first item in list, not a copy"); + is(consolidated.type, SVGTransform.SVG_TRANSFORM_MATRIX, + "Consolidated transform not of type matrix"); + const angle = -Math.PI / 4; + roughCmpMatrix(consolidated.matrix, + [Math.cos(angle), Math.sin(angle), + -Math.sin(angle), Math.cos(angle), + 130 * Math.cos(angle) - 160 * Math.sin(angle) + 50, + 160 * Math.cos(angle) + 130 * Math.sin(angle) + 90], + "Unexpected result after consolidating matrices"); + + // Check ref to first item in list + // a) should not have changed + is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE, + "Unexpected type of cached first item in list after consolidating"); + cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90], + "Unexpected value for cached first item in list after consolidating"); + // b) should still be usable + first_item.setScale(2, 3); + is(first_item.type, SVGTransform.SVG_TRANSFORM_SCALE, + "Cached first item in list not usable after consolidating"); + + // Check consolidated is live + // a) Changes to 'consolidated' affect list + consolidated.setSkewX(45); + is(list.getItem(0).type, SVGTransform.SVG_TRANSFORM_SKEWX, + "Changing return value from consolidate doesn't affect list"); + // b) Changes to list affect 'consolidated' + list.getItem(0).setRotate(90, 0, 0); + is(consolidated.type, SVGTransform.SVG_TRANSFORM_ROTATE, + "Changing list doesn't affect return value from consolidate"); +} + +function testConsolidateMatrixOneElem(g) { + // Check that even if we only have one item in the list it becomes a matrix + // transform (as per the spec) + g.setAttribute("transform", "translate(50 90)"); + var list = g.transform.baseVal; + is(list.numberOfItems, 1, "Unexpected length of unconsolidated list"); + var first_item = list.getItem(0); + is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE, + "Unexpected type of first item in list"); + cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90], + "Unexpected value for first item in list"); + + // Consolidate + var consolidated = list.consolidate(); + is(list.numberOfItems, 1, "Unexpected length of consolidated list"); + ok(consolidated === list.getItem(0), + "Consolidate return value should be first item in list, not a copy"); + is(consolidated.type, SVGTransform.SVG_TRANSFORM_MATRIX, + "Consolidated transform not of type matrix"); + cmpMatrix(consolidated.matrix, [1, 0, 0, 1, 50, 90], + "Unexpected consolidated matrix value"); +} + +function testConsolidateMatrixZeroElem(g) { + // Check that zero items returns null + g.setAttribute("transform", ""); + var list = g.transform.baseVal; + is(list.numberOfItems, 0, "Unexpected length of unconsolidated list"); + var consolidated = list.consolidate(); + ok(consolidated === null, + "consolidate() should return null for a zero-length transform list"); +} + +function testCreateSVGTransformFromMatrix(g) { + var m = createMatrix(1, 2, 3, 4, 5, 6); + + // "Creates an SVGTransform object which is initialized to transform of type + // SVG_TRANSFORM_MATRIX and whose values are the given matrix. The values from + // the parameter matrix are copied, the matrix parameter is not adopted as + // SVGTransform::matrix." + var list = g.transform.baseVal; + list.clear(); + var t = list.createSVGTransformFromMatrix(m); + + // Check that list hasn't changed + is(list.numberOfItems, 0, + "Transform list changed after calling createSVGTransformFromMatrix"); + + // Check return value + is(t.type, SVGTransform.SVG_TRANSFORM_MATRIX, + "Returned transform not of type matrix"); + cmpMatrix(t.matrix, [1, 2, 3, 4, 5, 6], + "Unexpected returned matrix value"); + + // Check values are copied + ok(t.matrix != m, "Matrix should be copied not adopted"); + m.a = 2; + is(t.matrix.a, 1, + "Changing source matrix should not affect newly created transform"); + + // null should give us an identity matrix + t = list.createSVGTransformFromMatrix(null); + cmpMatrix(t.matrix, [1, 0, 0, 1, 0, 0], + "Unexpected returned matrix value"); + + // Try passing in bad values ("undefined" etc.) + let exception = null; + try { + t = list.createSVGTransformFromMatrix("undefined"); + } catch (e) { exception = e; } + ok(exception, + "Failed to throw for string input to createSVGTransformFromMatrix"); + exception = null; + try { + t = list.createSVGTransformFromMatrix(SVGMatrix(t)); + } catch (e) { exception = e; } + ok(exception, + "Failed to throw for bad input to createSVGTransformFromMatrix"); + exception = null; +} + +function testReadOnly(g) { + var SVG_NS = "http://www.w3.org/2000/svg"; + + // Just some data to work with + g.setAttribute("transform", "translate(50 90)"); + + // baseVal / animVal are readonly attributes + // Create another (empty) transform list + var otherg = document.createElementNS(SVG_NS, "g"); + g.parentNode.appendChild(otherg); + is(g.transform.baseVal.numberOfItems, 1, + "Unexpected number of items in transform list before attempting to set"); + is(otherg.transform.baseVal.numberOfItems, 0, + "Unexpected number of items in source transform list before attempting to" + + " set"); + // Attempt to set the base value and check nothing changes + g.transform.baseVal = otherg.transform.baseVal; + is(g.transform.baseVal.numberOfItems, 1, + "baseVal should be read-only but its value has changed"); + is(otherg.transform.baseVal.numberOfItems, 0, + "baseVal changed after attempting to use it set another value"); + + // Read-only SVGTransformList: + // Standard list methods are covered in test_SVGxxxList.xhtml so here we + // just add tests for SVGTransformList-specific methods + var roList = g.transform.animVal; + // consolidate() + var threw = false; + try { + roList.consolidate(); + } catch (e) { + is(e.name, "NoModificationAllowedError", + "Got unexpected exception " + e + + ", expected NoModificationAllowedError"); + is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Got unexpected exception " + e + + ", expected NO_MODIFICATION_ALLOWED_ERR"); + threw = true; + } + ok(threw, + "Failed to throw exception when calling consolidate on read-only list"); + + // Read-only SVGTransform: + // read-only attributes are tested in test_transform.xhtml. Here we are + // concerned with methods that throw because this *object* is read-only + // (since it belongs to a read-only transform list) + var roTransform = roList.getItem(0); + // setMatrix + threw = false; + try { + var m = createMatrix(1, 2, 3, 4, 5, 6); + roTransform.setMatrix(m); + } catch (e) { + is(e.name, "NoModificationAllowedError", + "Got unexpected exception " + e + + ", expected NoModificationAllowedError"); + is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Got unexpected exception " + e + + ", expected NO_MODIFICATION_ALLOWED_ERR"); + threw = true; + } + ok(threw, "Failed to throw exception when calling setMatrix on read-only" + + " transform"); + // setTranslate + threw = false; + try { + roTransform.setTranslate(2, 3); + } catch (e) { + threw = true; + } + ok(threw, "Failed to throw when calling setTranslate on read-only" + + " transform"); + // setScale + threw = false; + try { + roTransform.setScale(2, 3); + } catch (e) { + threw = true; + } + ok(threw, "Failed to throw when calling setScale on read-only transform"); + // setRotate + threw = false; + try { + roTransform.setRotate(1, 2, 3); + } catch (e) { + threw = true; + } + ok(threw, "Failed to throw when calling setRotate on read-only transform"); + // setSkewX + threw = false; + try { + roTransform.setSkewX(2); + } catch (e) { + threw = true; + } + ok(threw, "Failed to throw when calling setSkewX on read-only transform"); + // setSkewY + threw = false; + try { + roTransform.setSkewY(2); + } catch (e) { + threw = true; + } + ok(threw, "Failed to throw when calling setSkewY on read-only transform"); + + // Read-only SVGMatrix + var roMatrix = roTransform.matrix; + threw = false; + try { + roMatrix.a = 1; + } catch (e) { + is(e.name, "NoModificationAllowedError", + "Got unexpected exception " + e + + ", expected NoModificationAllowedError"); + is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Got unexpected exception " + e + + ", expected NO_MODIFICATION_ALLOWED_ERR"); + threw = true; + } + ok(threw, "Failed to throw exception when modifying read-only matrix"); +} + +function testOrphan(g) { + // Although this isn't defined, if a read-only object becomes orphaned + // (detached from it's parent), then presumably it should become editable + // again. + + // As with the read-only test set a value to test with + g.setAttribute("transform", "translate(50 90)"); + + var roList = g.transform.animVal; + var roTransform = roList.getItem(0); + var roMatrix = roTransform.matrix; + + // Orphan transform list contents by re-setting transform attribute + g.setAttribute("transform", ""); + + // Transform should now be editable + var exception = null; + try { + roTransform.setTranslate(5, 3); + } catch (e) { + exception = e; + } + ok(exception === null, + "Unexpected exception " + exception + " modifying orphaned transform"); + + // So should matrix + exception = null; + try { + roMatrix.a = 1; + } catch (e) { + exception = e; + } + ok(exception === null, + "Unexpected exception " + exception + " modifying orphaned matrix"); +} + +function testFailedSet(g) { + // Check that a parse failure results in the attribute being empty + + // Set initial value + g.setAttribute("transform", "translate(50 90)"); + var list = g.transform.baseVal; + is(list.numberOfItems, 1, "Unexpected initial length of list"); + + // Attempt to set bad value + g.setAttribute("transform", "translate(40 50) scale(a)"); + is(list.numberOfItems, 0, + "Transform list should be empty after setting bad value"); + is(g.transform.animVal.numberOfItems, 0, + "Animated transform list should also be empty after setting bad value"); +} + +function testMutationEvents(g) { + // Check mutation events + + // Set initial value + g.setAttribute("transform", "translate(50 90)"); + var list = g.transform.baseVal; + is(list.numberOfItems, 1, "Unexpected initial length of list"); + var eventChecker = new MutationEventChecker; + eventChecker.watchAttr(g, "transform"); + + // consolidate + // + // Consolidate happens to generate two modification events in our + // implementation--it's not ideal but it's better than none + eventChecker.expect("modify modify modify"); + g.setAttribute("transform", "translate(10 10) translate(10 10)"); + list.consolidate(); + + // In the following, each of the operations is performed twice but only one + // mutation event is expected. This is to check that redundant mutation + // events are not sent. + + // transform.setMatrix + eventChecker.expect("modify"); + var mx = $("svg").createSVGMatrix(); + list[0].setMatrix(mx); + list[0].setMatrix(mx); + [ + {a: 1, m11: 2}, + {a: Infinity}, + {b: 0, m12: -1}, + {c: Infinity, m21: -Infinity}, + {d: 0, m22: NaN}, + {e: 1, m41: 1.00000001}, + {f: 0, m42: Number.MIN_VALUE}, + ].forEach(dict => { + let exception = null; + try { + list[0].setMatrix(dict); + } catch (e) { + exception = e; + } + ok(exception, + "Failed to throw for invalid input to setMatrix"); + }); + + // transform.setTranslate + eventChecker.expect("modify"); + list[0].setTranslate(10, 10); + list[0].setTranslate(10, 10); + + // transform.setScale + eventChecker.expect("modify"); + list[0].setScale(2, 2); + list[0].setScale(2, 2); + + // transform.setRotate + eventChecker.expect("modify"); + list[0].setRotate(45, 1, 2); + list[0].setRotate(45, 1, 2); + + // transform.setSkewX + eventChecker.expect("modify"); + list[0].setSkewX(45); + list[0].setSkewX(45); + + // transform.setSkewY + eventChecker.expect("modify"); + list[0].setSkewY(25); + list[0].setSkewY(25); + + // transform.matrix + eventChecker.expect("modify modify"); + list[0].matrix.a = 1; + list[0].matrix.a = 1; + list[0].matrix.e = 5; + list[0].matrix.e = 5; + + // setAttribute interaction + eventChecker.expect("modify"); + list[0].setMatrix(mx); + eventChecker.expect(""); + g.setAttribute("transform", "matrix(1, 0, 0, 1, 0, 0)"); + list[0].setMatrix(mx); + + // Attribute removal + eventChecker.expect("remove"); + g.removeAttribute("transform"); + + // Non-existent attribute removal + eventChecker.expect(""); + g.removeAttribute("transform"); + g.removeAttributeNS(null, "transform"); + + eventChecker.finish(); +} + +window.addEventListener("load", main); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGTransformListAddition.xhtml b/dom/svg/test/test_SVGTransformListAddition.xhtml new file mode 100644 index 0000000000..b3b908466c --- /dev/null +++ b/dom/svg/test/test_SVGTransformListAddition.xhtml @@ -0,0 +1,185 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602759 +--> +<head> + <title>Tests specific to SVGLengthList addition</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602759"> + Mozilla Bug 602759</a> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100" + onload="this.pauseAnimations();"> + <g id="g"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/* +This file runs a series of tests specific to addition of SVGTransformList in +animation. +*/ + +function AdditionTestCase(desc, baseVal, animSpecs, expectedTransformList) { + this.desc = desc; + this.baseVal = baseVal; + this.animSpecs = animSpecs; + this.expectedTransformList = expectedTransformList; +} + +function Transform(type, angle) { + this.type = type; + this.angle = angle; +} + +function main(g) { + var cases = [ + new AdditionTestCase("Not additive", + "translate(150 50)", + {type: "rotate", from: "0", to: "90"}, + [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)] + ), + new AdditionTestCase("To animation", + "rotate(-90)", + {type: "rotate", to: "90"}, + [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)] + ), + new AdditionTestCase("By animation", + "rotate(-90)", + {type: "rotate", by: "180"}, + [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, -90), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 180)] + ), + new AdditionTestCase("Normal additive: same type", + "rotate(45)", + {type: "rotate", from: "0", to: "45", additive: "sum"}, + [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)] + ), + new AdditionTestCase("Normal additive: different type", + "translate(50)", + {type: "rotate", from: "0", to: "90", additive: "sum"}, + [new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)] + ), + new AdditionTestCase("Stacked additive: same type", + "rotate(-90)", + [{type: "rotate", from: "0", to: "90", additive: "sum"}, + {type: "rotate", from: "0", to: "90", additive: "sum"}], + [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, -90), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)] + ), + new AdditionTestCase("Stacked additive: different types #1", + "translate(50)", + [{type: "rotate", from: "0", to: "45", additive: "sum"}, + {type: "rotate", from: "0", to: "45", additive: "sum"}], + [new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)] + ), + new AdditionTestCase("Stacked additive: different types #2", + "skewX(20) translate(50)", + [{type: "rotate", from: "0", to: "45", additive: "sum"}, + {type: "rotate", from: "0", to: "45", additive: "sum"}], + [new Transform(SVGTransform.SVG_TRANSFORM_SKEWX, 20), + new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)] + ), + new AdditionTestCase("Stacked additive: different types #3", + "skewX(20) translate(50)", + [{type: "rotate", from: "0", to: "45", additive: "sum"}, + {type: "translate", from: "0", to: "30", additive: "sum"}, + {type: "translate", from: "0", to: "-30", additive: "sum"}, + {type: "rotate", from: "0", to: "45", additive: "sum"}], + [new Transform(SVGTransform.SVG_TRANSFORM_SKEWX, 20), + new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45), + new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0), + new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0), + new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)] + ), + new AdditionTestCase("Base value with rotation around a centre", + "rotate(90 50 50)", + {type: "translate", from: "0 0", to: "0 -50", additive: "sum"}, + [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90), + new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0)] + ), + ]; + + for (var i = 0; i < cases.length; i++) { + runAdditionTestCase(cases[i], $("g"), $("svg")); + } + + SimpleTest.finish(); +} + +function runAdditionTestCase(test, elem, svg) { + var anims = createAnims(test.animSpecs); + + elem.setAttribute("transform", test.baseVal); + elem.appendChild(anims); + + svg.setCurrentTime(1); + var expected = test.expectedTransformList; // Array of Transform objects + var actual = elem.transform.animVal; // SVGTransformList + is(actual.numberOfItems, expected.length, + "Unexpected number of transforms"); + + if (actual.numberOfItems == expected.length) { + for (var i = 0; i < actual.numberOfItems; i++) { + var transform = actual.getItem(i); + var testDesc = " for transform " + i + " in '" + test.desc + "' test"; + is(transform.type, expected[i].type, + "Unexpected transform type" + testDesc); + is(transform.angle, expected[i].angle, + "Unexpected transform angle" + testDesc); + } + } + // We assume the only children of elem are the animation elements we've just + // added. + while (elem.firstChild) { + elem.firstChild.remove(); + } +} + +function createAnims(specs) { + if (specs.constructor == Array) { + var frag = document.createDocumentFragment(); + for (var i = 0; i < specs.length; ++i) { + frag.appendChild(createAnim(specs[i])); + } + return frag; + } + + return createAnim(specs); +} + +function createAnim(attrs) { + var SVG_NS = "http://www.w3.org/2000/svg"; + var anim = document.createElementNS(SVG_NS, "animateTransform"); + anim.setAttribute("attributeName", "transform"); + anim.setAttribute("dur", "1s"); + anim.setAttribute("fill", "freeze"); + for (let attr in attrs) { + anim.setAttribute(attr, attrs[attr]); + } + return anim; +} + +window.addEventListener("load", main); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVG_namespace_ids.html b/dom/svg/test/test_SVG_namespace_ids.html new file mode 100644 index 0000000000..43b2684649 --- /dev/null +++ b/dom/svg/test/test_SVG_namespace_ids.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=589640 +--> +<head> + <title>Test for Bug 589640</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + function debug(message) { + document.getElementById("debug").appendChild(document.createTextNode(message + "\n")); + } + + function runTests() { + var svg = document.getElementById("svg1"); + for (var el = 0; el < svg.children.length; el++) { + is(svg.children[el].id, svg.children[el].localName, svg.children[el].localName + " in the SVG namespace has a valid ID"); + debug(svg.children[el].localName + ".id = " + svg.children[el].id); + } + + SimpleTest.finish(); + } + </script> +</head> +<body onload="runTests()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589640">Mozilla Bug 589640</a> +<pre id="debug"></pre> +<!-- NOTE: This test relies on the ids being the same as the element names --> +<svg id="svg1"> + <a id="a" /> + <animate id="animate" /> + <animateColor id="animateColor" /> + <animateMotion id="animateMotion" /> + <animateTransform id="animateTransform" /> + <circle id="circle" /> + <clipPath id="clipPath" /> + <color-profile id="color-profile" /> + <cursor id="cursor" /> + <defs id="defs" /> + <desc id="desc" /> + <ellipse id="ellipse" /> + <feBlend id="feBlend" /> + <feColorMatrix id="feColorMatrix" /> + <feComponentTransfer id="feComponentTransfer" /> + <feComposite id="feComposite" /> + <feConvolveMatrix id="feConvolveMatrix" /> + <feDiffuseLighting id="feDiffuseLighting" /> + <feDisplacementMap id="feDisplacementMap" /> + <feDistantLight id="feDistantLight" /> + <feDropShadow id="feDropShadow" /> + <feFlood id="feFlood" /> + <feFuncA id="feFuncA" /> + <feFuncB id="feFuncB" /> + <feFuncG id="feFuncG" /> + <feFuncR id="feFuncR" /> + <feGaussianBlur id="feGaussianBlur" /> + <feImage id="feImage" /> + <feMerge id="feMerge" /> + <feMergeNode id="feMergeNode" /> + <feMorphology id="feMorphology" /> + <feOffset id="feOffset" /> + <fePointLight id="fePointLight" /> + <feSpecularLighting id="feSpecularLighting" /> + <feSpotLight id="feSpotLight" /> + <feTile id="feTile" /> + <feTurbulence id="feTurbulence" /> + <filter id="filter" /> + <font id="font" /> + <font-face id="font-face" /> + <font-face-format id="font-face-format" /> + <font-face-name id="font-face-name" /> + <font-face-src id="font-face-src" /> + <font-face-uri id="font-face-uri" /> + <foreignObject id="foreignObject" /> + <g id="g" /> + <glyph id="glyph" /> + <glyphRef id="glyphRef" /> + <hkern id="hkern" /> + <image id="image" /> + <line id="line" /> + <linearGradient id="linearGradient" /> + <marker id="marker" /> + <mask id="mask" /> + <metadata id="metadata" /> + <missing-glyph id="missing-glyph" /> + <mpath id="mpath" /> + <path id="path" /> + <pattern id="pattern" /> + <polygon id="polygon" /> + <polyline id="polyline" /> + <radialGradient id="radialGradient" /> + <rect id="rect" /> + <script id="script" /> + <set id="set" /> + <stop id="stop" /> + <style id="style" /> + <svg id="svg" /> + <switch id="switch" /> + <symbol id="symbol" /> + <text id="text" /> + <textPath id="textPath" /> + <title id="title" /> + <tref id="tref" /> + <tspan id="tspan" /> + <use id="use" /> + <view id="view" /> + <vkern id="vkern" /> +</svg> +</body> +</html> diff --git a/dom/svg/test/test_SVGxxxList.xhtml b/dom/svg/test/test_SVGxxxList.xhtml new file mode 100644 index 0000000000..0465ddb4bb --- /dev/null +++ b/dom/svg/test/test_SVGxxxList.xhtml @@ -0,0 +1,1372 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=515116 +--> +<head> + <title>Generic tests for SVG animated length lists</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="matrixUtils.js"></script> + <script type="text/javascript" src="MutationEventChecker.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug 515116</a> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100" + onload="this.pauseAnimations();"> + <defs> + <filter> + <feComponentTransfer> + <feFuncR id="feFuncR" type="table"/> + </feComponentTransfer> + </filter> + </defs> + <text id="text">text</text> + <path id="path"/> + <polyline id="polyline"/> + <g id="g"/> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + + +SimpleTest.waitForExplicitFinish(); + +/* +This file runs a series of type-agnostic tests to check the state of the mini DOM trees that represent various SVG 'list' attributes (including checking the "object identity" of the objects in those trees) in the face of various changes, both with and without the complication of SMIL animation being active. + +For additional high level information on the tests that are run, see the comment for 'create_animate_elements' below. + +To have the battery of generic tests run for a new list attribute, add an element with that attribute to the document, then add a JavaScript object literal to the following 'tests' array with the following properties: + + target_element_id + The ID of the element that has the attribute that is to be tested. + attr_name + The name of the attribute that is to be tested. + prop_name + The name of the DOM property that corresponds to the attribute that is to + be tested. For some list types the SVGAnimatedXxxList interface is + inherited by the element interface rather than the element having a + property of that type, and in others the list type is not animatable so + there is no SVGAnimatedXxxList interface for that list type. In these + cases this property should be set to null. + bv_name + The name of the DOM base value property for the attribute that is to be + tested. This is usually 'baseVal', but not always. In the case of + SVGStringList, which is not animatable, this is the name of the + SVGStringList property. + av_name + The name of the DOM anim value property for the attribute that is to be + tested. This is usually 'animVal' but not always. In the case of + SVGStringList, which is not animatable, this should be set to null. + el_type + The name of the SVGXxxElement interface on which the property corresponding + to the attribute being tested is defined. + prop_type + The name of the SVGAnimatedXxxList interface (e.g. SVGAnimatedLengthList), + if it exists, and if the element has a property is of this type (as + opposed to the element interface inheriting it). + list_type + The name of the SVGXxxList interface implemented by the baseVal and + animVal objects. + item_type + The name of the SVGXxx interface implemented by the list items. + attr_val_3a: + attr_val_3b: + Two attribute values containing three different items. + attr_val_4 + An attribute value containing four items. + attr_val_5a: + attr_val_5b: + Two attribute values containing five different items. + attr_val_5b_firstItem_x3_constructor: + Function to construct a list-item that should match the first item in a + SVGXxxList after three repeats of a cumulative animation to attr_val_5b. + This function takes t.item_constructor as its only argument. + item_constructor: + Function to create a dummy list item. + item_is: + Function to compare two list items for equality, like "is()". If this + property is omitted, it is assumed that we can just compare + "item.value" (which is the case for most list types). +*/ +// helper method +function keys(obj) { + var rval = []; + for (var prop in obj) { + rval.push(prop); + } + return rval; +} + +var tests = [ + { + // SVGLengthList test: + target_element_id: "text", + attr_name: "x", + prop_name: "x", + bv_name: "baseVal", + av_name: "animVal", + el_type: "SVGTextElement", + prop_type: "SVGAnimatedLengthList", + list_type: "SVGLengthList", + item_type: "SVGLength", + attr_val_3a: "10 20ex, 30in", + attr_val_3b: "30in 10, 20ex", + attr_val_4: "10 20ex, 30in ,40cm", + attr_val_5a: "10 20ex, 30in ,40cm , 50%", + attr_val_5b: "20 50%, 20ex ,30in , 40cm", + attr_val_5b_firstItem_x3_constructor(constructor) { + var expected = constructor(); + expected.value = 60; + return expected; + }, + item_constructor() { + // We need this function literal to avoid "Illegal operation on + // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO. + return document.getElementById("svg").createSVGLength(); + }, + }, + { + // SVGNumberList test: + target_element_id: "text", + attr_name: "rotate", + prop_name: "rotate", + bv_name: "baseVal", + av_name: "animVal", + el_type: "SVGTextElement", + prop_type: "SVGAnimatedNumberList", + list_type: "SVGNumberList", + item_type: "SVGNumber", + attr_val_3a: "0 20 40", + attr_val_3b: "60 40 20", + attr_val_4: "40 20 10 80", + attr_val_5a: "90 30 60 20 70", + attr_val_5b: "30 20 70 30 90", + attr_val_5b_firstItem_x3_constructor(constructor) { + var expected = constructor(); + expected.value = 90; + return expected; + }, + item_constructor() { + // We need this function literal to avoid "Illegal operation on + // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO. + return document.getElementById("svg").createSVGNumber(); + }, + }, + { + // SVGNumberList test: + target_element_id: "feFuncR", + attr_name: "tableValues", + prop_name: "tableValues", + bv_name: "baseVal", + av_name: "animVal", + el_type: "SVGFEComponentTransferElement", + prop_type: "SVGAnimatedNumberList", + list_type: "SVGNumberList", + item_type: "SVGNumber", + attr_val_3a: "0 .5 .2", + attr_val_3b: "1 .7 .1", + attr_val_4: ".5 .3 .8 .2", + attr_val_5a: "3 4 5 6 7", + attr_val_5b: "7 6 5 4 3", + attr_val_5b_firstItem_x3_constructor(constructor) { + var expected = constructor(); + expected.value = 21; + return expected; + }, + item_constructor() { + // We need this function literal to avoid "Illegal operation on + // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO. + return document.getElementById("svg").createSVGNumber(); + }, + }, + { + // SVGPointList test: + target_element_id: "polyline", + attr_name: "points", + prop_name: null, // SVGAnimatedPoints is an inherited interface! + bv_name: "points", + av_name: "animatedPoints", + el_type: "SVGPolylineElement", + prop_type: null, + list_type: "SVGPointList", + item_type: "SVGPoint", + attr_val_3a: " 10,10 50,50 90,10 ", + attr_val_3b: " 10,50 50,10 90,50 ", + attr_val_4: " 10,10 50,50 90,10 200,100 ", + attr_val_5a: " 10,10 50,50 90,10 130,50 170,10 ", + attr_val_5b: " 50,10 50,10 90,50 130,10 170,50 ", + attr_val_5b_firstItem_x3_constructor(constructor) { + var expected = constructor(); + expected.x = 150; + expected.y = 30; + return expected; + }, + item_constructor() { + // XXX return different values each time + return document.getElementById("svg").createSVGPoint(); + }, + item_is(itemA, itemB, message) { + ok(typeof(itemA.x) != "undefined" && + typeof(itemB.x) != "undefined", + "expecting x property"); + ok(typeof(itemA.y) != "undefined" && + typeof(itemB.y) != "undefined", + "expecting y property"); + + is(itemA.x, itemB.x, message); + is(itemA.y, itemB.y, message); + }, + }, + { + // SVGStringList test: + target_element_id: "g", + attr_name: "requiredExtensions", // systemLanguage, viewTarget + prop_name: null, // SVGStringList attributes are not animatable + bv_name: "requiredExtensions", + av_name: null, + el_type: "SVGGElement", + prop_type: null, + list_type: "SVGStringList", + item_type: "DOMString", + attr_val_3a: "http://www.w3.org/TR/SVG11/feature#Shape http://www.w3.org/TR/SVG11/feature#Image " + + "http://www.w3.org/TR/SVG11/feature#Style", + attr_val_3b: "http://www.w3.org/TR/SVG11/feature#CoreAttribute http://www.w3.org/TR/SVG11/feature#Structure " + + "http://www.w3.org/TR/SVG11/feature#Gradient", + attr_val_4: "http://www.w3.org/TR/SVG11/feature#Pattern http://www.w3.org/TR/SVG11/feature#Clip " + + "http://www.w3.org/TR/SVG11/feature#Mask http://www.w3.org/TR/SVG11/feature#Extensibility", + attr_val_5a: "http://www.w3.org/TR/SVG11/feature#BasicStructure http://www.w3.org/TR/SVG11/feature#BasicText " + + "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute " + + "http://www.w3.org/TR/SVG11/feature#BasicClip", + attr_val_5b: "http://www.w3.org/TR/SVG11/feature#DocumentEventsAttribute http://www.w3.org/TR/SVG11/feature#GraphicalEventsAttribute " + + "http://www.w3.org/TR/SVG11/feature#AnimationEventsAttribute http://www.w3.org/TR/SVG11/feature#Hyperlinking " + + "http://www.w3.org/TR/SVG11/feature#XlinkAttribute", + item_constructor() { + return "http://www.w3.org/TR/SVG11/feature#XlinkAttribute"; + }, + }, + { + // SVGTransformList test: + target_element_id: "g", + attr_name: "transform", // gradientTransform, patternTransform + prop_name: "transform", + bv_name: "baseVal", + av_name: "animVal", + el_type: "SVGGElement", + prop_type: "SVGAnimatedTransformList", + list_type: "SVGTransformList", + item_type: "SVGTransform", + attr_val_3a: "translate(20 10) rotate(90 10 10) skewX(45)", + attr_val_3b: "translate(30 40) scale(2) matrix(1 2 3 4 5 6)", + attr_val_4: "scale(3 2) translate(19) skewY(2) rotate(-10)", + attr_val_5a: + "translate(20) rotate(-10) skewY(3) matrix(1 2 3 4 5 6) scale(0.5)", + attr_val_5b: + "skewX(45) rotate(45 -10 -10) skewX(-45) scale(2) matrix(6 5 4 3 2 1)", + // SVGTransformList animation addition is tested in + // test_SVGTransformListAddition.xhtml so we don't need: + // - attr_val_3b + // - attr_val_3b + // - attr_val_5b_firstItem_x3_constructor + // But we populate the first two anyway just in case they are later used for + // something other than testing animation. + // attr_val_5b_firstItem_x3_constructor is only used for animation + item_constructor() { + // XXX populate the matrix with different values each time + return document.getElementById("svg").createSVGTransform(); + }, + item_is(itemA, itemB, message) { + ok(typeof(itemA.type) != "undefined" && + typeof(itemB.type) != "undefined", + "expecting type property"); + ok(typeof(itemA.matrix) != "undefined" && + typeof(itemB.matrix) != "undefined", + "expecting matrix property"); + ok(typeof(itemA.angle) != "undefined" && + typeof(itemB.angle) != "undefined", + "expecting matrix property"); + + is(itemA.type, itemB.type, message); + is(itemA.angle, itemB.angle, message); + cmpMatrix(itemA.matrix, itemB.matrix, message); + }, + }, +]; + + +/* +This function returns a DocumentFragment with three 'animate' element children. The duration of the three animations is as follows: + + animation 1: | *-----------*-----------*-----------* + animation 2: | *--* + animation 3: | *--* + |___________________________________________> time (s) + | | | | | | | | | | | | | | | + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + +The first animation repeats once so that we can test state on a repeat animation. + +The second animation overrides the first animation for a short time, and has fewer list items than the first animation. This allows us to test object identity and other state on and after an overriding animation. Specifically, it allows us to check whether animVal list items are kept or discarded after the end of an overriding animation that has fewer items. + +The third animation has additive="sum", with fewer items than the lower priority animation 1, allowing us to test object identity and other state in that scenario. TODO: some type aware tests to check whether the composite fails or works? + +At t=0s and t=1s we test the effect of an attribute value changes in the absence and presence of SMIL animation respectively. + +At t=10s we programmatically remove the fill="freeze" from animation 1. +*/ +function create_animate_elements(test) { + var SVG_NS = "http://www.w3.org/2000/svg"; + var df = document.createDocumentFragment(); + + if (is_transform_attr(test.attr_name)) { + // animateTransform is "special". Although it targets an + // SVGAnimatedTransformList it only takes SVGTransform values as + // animation values. Therefore all the assumptions we're testing about the + // length of lists don't apply. We simply have to test it separately. + // This is done in test_SVGTransformListAddition.xhtml. + return df; // Return the empty document fragment + } + + var animate1 = document.createElementNS(SVG_NS, "animate"); + var animate2 = document.createElementNS(SVG_NS, "animate"); + var animate3 = document.createElementNS(SVG_NS, "animate"); + + animate1.setAttribute("attributeName", test.attr_name); + animate1.setAttribute("from", test.attr_val_5a); + animate1.setAttribute("to", test.attr_val_5b); + animate1.setAttribute("begin", "1s"); + animate1.setAttribute("dur", "4s"); + animate1.setAttribute("repeatCount", "3"); + animate1.setAttribute("accumulate", "sum"); + animate1.setAttribute("fill", "freeze"); + df.appendChild(animate1); + + animate2.setAttribute("attributeName", test.attr_name); + animate2.setAttribute("from", test.attr_val_3a); + animate2.setAttribute("to", test.attr_val_3b); + animate2.setAttribute("begin", "2s"); + animate2.setAttribute("dur", "1s"); + df.appendChild(animate2); + + animate3.setAttribute("attributeName", test.attr_name); + animate3.setAttribute("from", test.attr_val_3a); + animate3.setAttribute("to", test.attr_val_3b); + animate3.setAttribute("begin", "7s"); + animate3.setAttribute("dur", "1s"); + animate3.setAttribute("additive", "sum"); + df.appendChild(animate3); + + return df; +} + +function is_transform_attr(attr_name) { + return attr_name == "transform" || + attr_name == "gradientTransform" || + attr_name == "patternTransform"; +} + +function get_array_of_list_items(list) { + let array = []; + for (var i = 0; i < list.numberOfItems; ++i) { + array.push(list.getItem(i)); + } + return array; +} + + +/** + * This function tests the SVGXxxList API for the base val list. This means + * running tests for the following property and methods: + * + * numberOfItems + * clear() + * SVGLength initialize(in SVGLength newItem) + * SVGLength getItem(in unsigned long index) + * SVGLength insertItemBefore(in SVGLength newItem, in unsigned long index) + * SVGLength replaceItem(in SVGLength newItem, in unsigned long index) + * SVGLength removeItem(in unsigned long index) + * SVGLength appendItem(in SVGLength newItem) + * + * @param t A test from the 'tests' array. + */ +function run_baseVal_API_tests() { + var res, threw; + var eventChecker = new MutationEventChecker; + + for (var t of tests) { + // Test .clear(): + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + is(t.baseVal.numberOfItems, 4, + "The " + t.list_type + " object should contain four list items."); + + eventChecker.watchAttr(t.element, t.attr_name); + eventChecker.expect("modify"); + res = t.baseVal.clear(); + + is(t.baseVal.numberOfItems, 0, + "The method " + t.list_type + ".clear() should clear the " + t.list_type + + " object."); + is(res, undefined, + "The method " + t.list_type + ".clear() should not return a value."); + ok(t.element.hasAttribute(t.attr_name), + "The method " + t.list_type + ".clear() should not remove the attribute."); + ok(t.element.getAttribute(t.attr_name) === "", + "Cleared " + t.attr_name + " (" + t.list_type + ") but did not get an " + + "empty string back."); + + eventChecker.expect(""); + t.baseVal.clear(); + eventChecker.ignoreEvents(); + + // Test empty strings + + t.element.setAttribute(t.attr_name, ""); + ok(t.element.getAttribute(t.attr_name) === "", + "Set an empty attribute value for " + t.attr_name + " (" + t.list_type + + ") but did not get an empty string back."); + + // Test removed attributes + + t.element.removeAttribute(t.attr_name); + ok(t.element.getAttribute(t.attr_name) === null, + "Removed attribute value for " + t.attr_name + " (" + t.list_type + + ") but did not get null back."); + ok(!t.element.hasAttribute(t.attr_name), + "Removed attribute value for " + t.attr_name + " (" + t.list_type + + ") but hasAttribute still returns true."); + + // Test .initialize(): + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + var item = t.item_constructor(); + // Our current implementation of 'initialize' for most list types performs + // a 'clear' followed by an 'insertItemBefore'. This results in two + // modification events being dispatched. SVGStringList however avoids the + // additional clear. + var expectedModEvents = + t.item_type == "DOMString" ? "modify" : "modify modify"; + eventChecker.expect(expectedModEvents); + res = t.baseVal.initialize(item); + eventChecker.ignoreEvents(); + + + is(t.baseVal.numberOfItems, 1, + "The " + t.list_type + " object should contain one list item."); + ok(res === item, + "The list item returned by " + t.list_type + ".initialize() should be the " + + "exact same object as the item that was passed to that method, since " + + "the item that was passed to that method did not already belong to a " + + "list."); + ok(t.baseVal.getItem(0) === item, + "The list item at index 0 should be the exact same object as the " + + "object that was passed to the " + t.list_type + ".initialize() method, " + + "since the item that was passed to that method did not already " + + "belong to a list."); + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + if (t.item_type != "DOMString") { + var old_items = get_array_of_list_items(t.baseVal); + item = t.baseVal.getItem(3); + res = t.baseVal.initialize(item); + + ok(res !== item && + t.baseVal.getItem(0) !== item && + t.baseVal.getItem(0) !== old_items[0] && + res === t.baseVal.getItem(0), + "The method " + t.list_type + ".initialize() should clone the object that " + + "is passed in if that object is already in a list."); + // [SVGWG issue] not what the spec currently says + + + item = t.baseVal.getItem(0); + res = t.baseVal.initialize(item); + + ok(res !== item && + t.baseVal.getItem(0) !== item, + "The method " + t.list_type + ".initialize() should clone the object that " + + "is passed in, even if that object is the only item in that list."); + // [SVGWG issue] not what the spec currently says + + eventChecker.expect(""); + threw = false; + try { + t.baseVal.initialize({}); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".initialize() should throw if passed an " + + "object of the wrong type."); + eventChecker.ignoreEvents(); + } + + // Test .insertItemBefore(): + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + old_items = get_array_of_list_items(t.baseVal); + item = t.item_constructor(); + eventChecker.expect("modify"); + res = t.baseVal.insertItemBefore(item, 2); + eventChecker.ignoreEvents(); + + is(t.baseVal.numberOfItems, 5, + "The " + t.list_type + " object should contain five list items."); + ok(res === item, + "The list item returned by " + t.list_type + ".insertItemBefore() should " + + "be the exact same object as the item that was passed to that method, " + + "since the item that was passed to that method did not already belong " + + "to a list."); + ok(t.baseVal.getItem(2) === item, + "The list item at index 2 should be the exact same object as the " + + "object that was passed to the " + t.list_type + ".insertItemBefore() " + + "method, since the item that was passed to that method did not " + + "already belong to a list."); + ok(t.baseVal.getItem(3) === old_items[2], + "The list item that was at index 2 should be at index 3 after " + + "inserting a new item at index 2 using the " + t.list_type + + ".insertItemBefore() method."); + + item = t.item_constructor(); + t.baseVal.insertItemBefore(item, 100); + + ok(t.baseVal.getItem(5) === item, + "When the index passed to the " + t.list_type + ".insertItemBefore() " + + "method is out of bounds, the supplied list item should be appended " + + "to the list."); + + item = t.baseVal.getItem(4); + res = t.baseVal.insertItemBefore(item, 2); + + is(t.baseVal.numberOfItems, 7, + "The " + t.list_type + " object should contain seven list items."); + if (t.item_type != "DOMString") { + ok(res !== item && + t.baseVal.getItem(2) !== item && + t.baseVal.getItem(2) !== old_items[2] && + res === t.baseVal.getItem(2), + "The method " + t.list_type + ".insertItemBefore() should clone the " + + "object that is passed in if that object is already in a list."); + // [SVGWG issue] not what the spec currently says + } + + item = t.baseVal.getItem(2); + res = t.baseVal.insertItemBefore(item, 2); + + is(t.baseVal.numberOfItems, 8, + "The " + t.list_type + " object should contain eight list items."); + if (t.item_type != "DOMString") { + ok(res !== item && + t.baseVal.getItem(2) !== item, + "The method " + t.list_type + ".insertItemBefore() should clone the " + + "object that is passed in, even if that object is the item in " + + "the list at the index specified."); + // [SVGWG issue] not what the spec currently says + + eventChecker.expect(""); + threw = false; + try { + t.baseVal.insertItemBefore({}, 2); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".insertItemBefore() should throw if passed " + + "an object of the wrong type."); + eventChecker.ignoreEvents(); + } + + // Test .replaceItem(): + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + old_items = get_array_of_list_items(t.baseVal); + item = t.item_constructor(); + eventChecker.expect("modify"); + res = t.baseVal.replaceItem(item, 2); + eventChecker.ignoreEvents(); + + is(t.baseVal.numberOfItems, 4, + "The " + t.list_type + " object should contain four list items."); + if (t.item_type != "DOMString") { + ok(res === item, + "The list item returned by " + t.list_type + ".replaceItem() should be " + + "the exact same object as the item that was passed to that method, " + + "since the item that was passed to that method did not already belong " + + "to a list."); + } + ok(t.baseVal.getItem(2) === item, + "The list item at index 2 should be the exact same object as the " + + "object that was passed to the " + t.list_type + ".replaceItem() method, " + + "since the item that was passed to that method did not already belong " + + "to a list."); + ok(t.baseVal.getItem(3) === old_items[3], + "The list item that was at index 3 should still be at index 3 after " + + "the item at index 2 was replaced using the " + t.list_type + + ".replaceItem() method."); + + item = t.item_constructor(); + + eventChecker.expect(""); + threw = false; + try { + t.baseVal.replaceItem(item, 100); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".replaceItem() should throw if passed " + + "an index that is out of bounds."); + eventChecker.ignoreEvents(); + + old_items = get_array_of_list_items(t.baseVal); + item = t.baseVal.getItem(3); + res = t.baseVal.replaceItem(item, 1); + + is(t.baseVal.numberOfItems, 4, + "The " + t.list_type + " object should contain four list items."); + if (t.item_type != "DOMString") { + ok(res !== item && + t.baseVal.getItem(1) !== item && + t.baseVal.getItem(1) !== old_items[1] && + res === t.baseVal.getItem(1), + "The method " + t.list_type + ".replaceItem() should clone the object " + + "that is passed in if that object is already in a list."); + // [SVGWG issue] not what the spec currently says + } + + item = t.baseVal.getItem(1); + res = t.baseVal.replaceItem(item, 1); + + is(t.baseVal.numberOfItems, 4, + "The " + t.list_type + " object should contain four list items."); + if (t.item_type != "DOMString") { + ok(res !== item && + t.baseVal.getItem(1) !== item, + "The method " + t.list_type + ".replaceItem() should clone the object " + + "that is passed in, even if the object that object and the object " + + "that is being replaced are the exact same objects."); + // [SVGWG issue] not what the spec currently says + + threw = false; + try { + t.baseVal.replaceItem({}, 2); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".replaceItem() should throw if passed " + + "an object of the wrong type."); + } + + // Test .removeItem(): + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + old_items = get_array_of_list_items(t.baseVal); + item = t.baseVal.getItem(2); + eventChecker.expect("modify"); + res = t.baseVal.removeItem(2); + eventChecker.ignoreEvents(); + + is(t.baseVal.numberOfItems, 3, + "The " + t.list_type + " object should contain three list items."); + if (t.item_type != "DOMString") { + ok(res === item, + "The list item returned by " + t.list_type + ".removeItem() should be the " + + "exact same object as the item that was at the specified index."); + } + ok(t.baseVal.getItem(1) === old_items[1], + "The list item that was at index 1 should still be at index 1 after " + + "the item at index 2 was removed using the " + t.list_type + + ".replaceItem() method."); + ok(t.baseVal.getItem(2) === old_items[3], + "The list item that was at index 3 should still be at index 2 after " + + "the item at index 2 was removed using the " + t.list_type + + ".replaceItem() method."); + + eventChecker.expect(""); + threw = false; + try { + t.baseVal.removeItem(100); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".removeItem() should throw if passed " + + "an index that is out of bounds."); + eventChecker.ignoreEvents(); + + // Test .appendItem(): + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + old_items = get_array_of_list_items(t.baseVal); + item = t.item_constructor(); + eventChecker.expect("modify"); + res = t.baseVal.appendItem(item); + eventChecker.ignoreEvents(); + + is(t.baseVal.numberOfItems, 5, + "The " + t.list_type + " object should contain five list items."); + ok(res === item, + "The list item returned by " + t.list_type + ".appendItem() should be the " + + "exact same object as the item that was passed to that method, since " + + "the item that was passed to that method did not already belong " + + "to a list."); + ok(t.baseVal.getItem(4) === item, + "The last list item should be the exact same object as the object " + + "that was passed to the " + t.list_type + ".appendItem() method, since " + + "the item that was passed to that method did not already belong to " + + "a list."); + ok(t.baseVal.getItem(3) === old_items[3], + "The list item that was at index 4 should still be at index 4 after " + + "appending a new item using the " + t.list_type + ".appendItem() " + + "method."); + + item = t.baseVal.getItem(2); + res = t.baseVal.appendItem(item); + + is(t.baseVal.numberOfItems, 6, + "The " + t.list_type + " object should contain six list items."); + if (t.item_type != "DOMString") { + ok(res !== item && + t.baseVal.getItem(5) !== item && + res === t.baseVal.getItem(5), + "The method " + t.list_type + ".appendItem() should clone the object " + + "that is passed in if that object is already in a list."); + // [SVGWG issue] not what the spec currently says + } + + item = t.baseVal.getItem(5); + res = t.baseVal.appendItem(item); + + is(t.baseVal.numberOfItems, 7, + "The " + t.list_type + " object should contain seven list items."); + if (t.item_type != "DOMString") { + ok(res !== item && + t.baseVal.getItem(6) !== item, + "The method " + t.list_type + ".appendItem() should clone the object " + + "that is passed in, if that object is already the last item in " + + "that list."); + // [SVGWG issue] not what the spec currently says + + eventChecker.expect(""); + threw = false; + try { + t.baseVal.appendItem({}); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".appendItem() should throw if passed " + + "an object of the wrong type."); + eventChecker.ignoreEvents(); + } + + // Test removal and addition events + + eventChecker.expect("remove add"); + t.element.removeAttribute(t.attr_name); + t.element.removeAttributeNS(null, t.attr_name); + res = t.baseVal.appendItem(item); + eventChecker.finish(); + } +} + + +/** + * This function tests the SVGXxxList API for the anim val list (see also the + * comment for test_baseVal_API). + */ +function run_animVal_API_tests() { + var threw, item; + + for (var t of tests) { + if (!t.animVal) + continue; // SVGStringList isn't animatable + + item = t.item_constructor(); + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + is(t.animVal.numberOfItems, 4, + "The " + t.list_type + " object should contain four list items."); + + // Test .clear(): + + threw = false; + try { + t.animVal.clear(); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".clear() should throw when called on an " + + "anim val list, since anim val lists should be readonly."); + + // Test .getItem(): + + item = t.animVal.getItem(2); + ok(item != null && item === t.animVal.getItem(2), + "The method " + t.list_type + ".getItem() should work when called on an " + + "anim val list, and always return the exact same object."); + + // .initialize() + + threw = false; + try { + t.animVal.initialize(item); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".initialize() should throw when called on " + + "an anim val list, since anim val lists should be readonly."); + + // Test .insertItemBefore(): + + threw = false; + try { + t.animVal.insertItemBefore(item, 2); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".insertItemBefore() should throw when " + + "called on an anim val list, since anim val lists should be readonly."); + + // Test .replaceItem(): + + threw = false; + try { + t.animVal.replaceItem(item, 2); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".replaceItem() should throw when called " + + "on an anim val list, since anim val lists should be readonly."); + + // Test .removeItem(): + + threw = false; + try { + t.animVal.removeItem(2); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".removeItem() should throw when called " + + "on an anim val list, since anim val lists should be readonly."); + + // Test .appendItem(): + + threw = false; + try { + t.animVal.appendItem(item); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.list_type + ".appendItem() should throw when called " + + "on an anim val list, since anim val lists should be readonly."); + } +} + + +/** + * This function runs some basic tests to check the effect of setAttribute() + * calls on object identity, without taking SMIL animation into consideration. + */ +function run_basic_setAttribute_tests() { + for (var t of tests) { + // Since the t.prop, t.baseVal and t.animVal objects should never ever + // change, we leave testing of them to our caller so that it can check + // them after all the other mutations such as SMIL changes. + + t.element.setAttribute(t.attr_name, t.attr_val_4); + + ok(t.baseVal.numberOfItems == 4 && t.baseVal.getItem(3) != null, + "The length of the " + t.list_type + " object for " + t.bv_path + " should " + + "have been set to 4 by the setAttribute() call."); + + if (t.animVal) { + ok(t.baseVal.numberOfItems == t.animVal.numberOfItems, + "When no animations are active, the " + t.list_type + " objects for " + + t.bv_path + " and " + t.av_path + " should be the same length (4)."); + + ok(t.baseVal !== t.animVal, + "The " + t.list_type + " objects for " + t.bv_path + " and " + t.av_path + + " should be different objects."); + + ok(t.baseVal.getItem(0) !== t.animVal.getItem(0), + "The " + t.item_type + " list items in the " + t.list_type + " objects for " + + t.bv_path + " and " + t.av_path + " should be different objects."); + } + + // eslint-disable-next-line no-self-compare + ok(t.baseVal.getItem(0) === t.baseVal.getItem(0), + "The exact same " + t.item_type + " DOM object should be returned each " + + "time the item at a given index in the " + t.list_type + " for " + + t.bv_path + " is accessed, given that the index was not made invalid " + + "by a change in list length between the successive accesses."); + + if (t.animVal) { + // eslint-disable-next-line no-self-compare + ok(t.animVal.getItem(0) === t.animVal.getItem(0), + "The exact same " + t.item_type + " DOM object should be returned each " + + "time the item at a given index in the " + t.list_type + " for " + + t.av_path + " is accessed, given that the index was not made invalid " + + "by a change in list length between the successive accesses."); + } + + // Test the effect of setting the attribute to new values: + + t.old_baseVal_items = get_array_of_list_items(t.baseVal); + if (t.animVal) { + t.old_animVal_items = get_array_of_list_items(t.animVal); + } + + t.element.setAttribute(t.attr_name, t.attr_val_3a); + t.element.setAttribute(t.attr_name, t.attr_val_5a); + + ok(t.baseVal.numberOfItems == 5 && t.baseVal.getItem(4) != null, + "The length of the " + t.list_type + " object for " + t.bv_path + " should " + + "have been set to 5 by the setAttribute() call."); + + if (t.animVal) { + ok(t.baseVal.numberOfItems == t.animVal.numberOfItems, + "Since no animations are active, the length of the " + t.list_type + " " + + "objects for " + t.bv_path + " and " + t.av_path + " should be the same " + + "(5)."); + } + + if (t.item_type != "DOMString") { + ok(t.baseVal.getItem(2) === t.old_baseVal_items[2], + "After its attribute changes, list items in the " + t.list_type + " for " + + t.bv_path + " that are at indexes that existed prior to the attribute " + + "change should be the exact same objects as the objects that were " + + "at those indexes prior to the attribute change."); + + ok(t.baseVal.getItem(3) !== t.old_baseVal_items[3], + "After its attribute changes, list items in the " + t.list_type + " for " + + t.bv_path + " that are at indexes that did not exist prior to the " + + "attribute change should not be the same objects as any objects that " + + "were at those indexes at some earlier time."); + } + + if (t.animVal) { + ok(t.animVal.getItem(2) === t.old_animVal_items[2], + "After its attribute changes, list items in the " + t.list_type + " for " + + t.av_path + " that are at indexes that existed prior to the attribute " + + "change should be the exact same objects as the objects that were " + + "at those indexes prior to the attribute change."); + + ok(t.animVal.getItem(3) !== t.old_animVal_items[3], + "After its attribute changes, list items in the " + t.list_type + " for " + + t.av_path + " that are at indexes that did not exist prior to the " + + "attribute change should not be the same objects as any objects " + + "that were at those indexes at some earlier time."); + } + } +} + +/** + * This function verifies that a list's animVal is kept in sync with its + * baseVal, when we add & remove items from the baseVal. + */ +function run_list_mutation_tests() { + for (var t of tests) { + if (t.animVal) { + // Test removeItem() + // ================= + // Save second item in baseVal list; then make it the first item, and + // check that animVal is updated accordingly. + t.element.setAttribute(t.attr_name, t.attr_val_4); + + var secondVal = t.baseVal.getItem(1); + var removedFirstVal = t.baseVal.removeItem(0); + t.item_is(t.animVal.getItem(0), secondVal, + "animVal for " + t.attr_name + " needs update after first item " + + "removed"); + + // Repeat with last item + var secondToLastVal = t.baseVal.getItem(1); + var removedLastVal = t.baseVal.removeItem(2); + + var threw = false; + try { + t.animVal.getItem(2); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.attr_name + ".animVal.getItem() for previously-final " + + "index should throw after final item is removed from baseVal."); + + t.item_is(t.animVal.getItem(1), secondToLastVal, + "animVal for " + t.attr_name + " needs update after last item " + + "removed"); + + // Test insertItemBefore() + // ======================= + // Reset base value, insert value @ start, check that animVal is updated. + t.element.setAttribute(t.attr_name, t.attr_val_3a); + t.baseVal.insertItemBefore(removedLastVal, 0); + t.item_is(t.animVal.getItem(0), removedLastVal, + "animVal for " + t.attr_name + " needs update after insert at " + + "beginning"); + + // Repeat with insert at end + t.element.setAttribute(t.attr_name, t.attr_val_3a); + t.baseVal.insertItemBefore(removedFirstVal, t.baseVal.numberOfItems); + t.item_is(t.animVal.getItem(t.baseVal.numberOfItems - 1), + removedFirstVal, + "animVal for " + t.attr_name + " needs update after insert at end"); + + // Test appendItem() + // ================= + var dummy = t.item_constructor(); + t.baseVal.appendItem(dummy); + t.item_is(t.animVal.getItem(t.baseVal.numberOfItems - 1), dummy, + "animVal for " + t.attr_name + " needs update after appendItem"); + + // Test clear() + // ============ + t.baseVal.clear(); + threw = false; + try { + t.animVal.getItem(0); + } catch (e) { + threw = true; + } + ok(threw, + "The method " + t.attr_name + ".animVal.getItem() should throw after " + + "we've cleared baseVal."); + + is(t.animVal.numberOfItems, 0, + "animVal for " + t.attr_name + " should be empty after baseVal cleared"); + + // Test initialize() + // ================= + t.element.setAttribute(t.attr_name, t.attr_val_3a); + t.baseVal.initialize(dummy); + + is(t.animVal.numberOfItems, 1, + "animVal for " + t.attr_name + " should have length 1 after initialize"); + t.item_is(t.animVal.getItem(0), dummy, + "animVal for " + t.attr_name + " needs update after initialize"); + } + } +} + +/** + * In this function we run a series of tests at various points along the SMIL + * animation timeline, using SVGSVGElement.setCurrentTime() to move forward + * along the timeline. + * + * See the comment for create_animate_elements() for details of the animations + * and their timings. + */ +function run_animation_timeline_tests() { + var svg = document.getElementById("svg"); + + for (var t of tests) { + // Skip if there is no animVal for this test or if it is a transform list + // since these are handled specially + if (!t.animVal || is_transform_attr(t.attr_name)) + continue; + + svg.setCurrentTime(0); // reset timeline + + // Reset attributes before moving along the timeline and triggering SMIL: + t.element.setAttribute(t.attr_name, t.attr_val_4); + t.old_baseVal_items = get_array_of_list_items(t.baseVal); + t.old_animVal_items = get_array_of_list_items(t.animVal); + + + /** ****************** t = 1s ********************/ + + svg.setCurrentTime(1); // begin first animation + + ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && + t.baseVal.getItem(3) === t.old_baseVal_items[3], + "The start of an animation should never affect the " + t.list_type + + " for " + t.bv_path + ", or its list items."); + + ok(t.animVal.numberOfItems == 5 && t.animVal.getItem(4) != null, + "The start of the animation should have changed the number of items " + + "in the " + t.list_type + " for " + t.bv_path + " to 5."); + + // TODO + ok(t.animVal.getItem(3) === t.old_animVal_items[3], + "When affected by SMIL animation, list items in the " + t.list_type + + " for " + t.bv_path + " that are at indexes that existed prior to the " + + "start of the animation should be the exact same objects as the " + + "objects that were at those indexes prior to the start of the " + + "animation."); + + t.old_animVal_items = get_array_of_list_items(t.animVal); + + t.element.setAttribute(t.attr_name, t.attr_val_3a); + + ok(t.baseVal.numberOfItems == 3 && + t.baseVal.getItem(2) === t.old_baseVal_items[2], + "Setting the underlying attribute should change the items in the " + + t.list_type + " for " + t.bv_path + ", including when an animation is " + + "in progress."); + + ok(t.animVal.numberOfItems == 5 && + t.animVal.getItem(4) === t.old_animVal_items[4], + "Setting the underlying attribute should not change the " + t.list_type + + " for " + t.bv_path + " when an animation that does not depend on the " + + "base val is in progress."); + + t.element.setAttribute(t.attr_name, t.attr_val_4); // reset + + t.old_baseVal_items = get_array_of_list_items(t.baseVal); + t.old_animVal_items = get_array_of_list_items(t.animVal); + + + /** ****************** t = 2s ********************/ + + svg.setCurrentTime(2); // begin override animation + + ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && + t.baseVal.getItem(3) === t.old_baseVal_items[3], + "The start of an override animation should never affect the " + + t.list_type + " for " + t.bv_path + ", or its list items."); + + is(t.animVal.numberOfItems, 3, + "The start of the override animation should have changed the number " + + "of items in the " + t.list_type + " for " + t.bv_path + " to 3."); + + ok(t.animVal.getItem(2) === t.old_animVal_items[2], + "When affected by an override SMIL animation, list items in the " + + t.list_type + " for " + t.bv_path + " that are at indexes that existed " + + "prior to the start of the animation should be the exact same " + + "objects as the objects that were at those indexes prior to the " + + "start of that animation."); + + t.old_animVal_items = get_array_of_list_items(t.animVal); + + + /** ****************** t = 3s ********************/ + + svg.setCurrentTime(3); // end of override animation + + ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && + t.baseVal.getItem(3) === t.old_baseVal_items[3], + "The end of an override animation should never affect the " + + t.list_type + " for " + t.bv_path + ", or its list items."); + + is(t.animVal.numberOfItems, 5, + "At the end of the override animation, the number of items in the " + + t.list_type + " for " + t.bv_path + " should have reverted to 5."); + + ok(t.animVal.getItem(2) === t.old_animVal_items[2], + "At the end of the override animation, list items in the " + + t.list_type + " for " + t.bv_path + " that are at indexes that existed " + + "prior to the end of the animation should be the exact same " + + "objects as the objects that were at those indexes prior to the " + + "end of that animation."); + + t.old_animVal_items = get_array_of_list_items(t.animVal); + + + /** ****************** t = 5s ********************/ + + svg.setCurrentTime(5); // animation repeat point + + ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && + t.baseVal.getItem(3) === t.old_baseVal_items[3], + "When a SMIL animation repeats, it should never affect the " + + t.list_type + " for " + t.bv_path + ", or its list items."); + + ok(t.animVal.numberOfItems == t.old_animVal_items.length && + t.animVal.getItem(4) === t.old_animVal_items[4], + "When an animation repeats, the list items that are at a given " + + "index in the " + t.list_type + " for " + t.av_path + " should be the exact " + + "same objects as were at that index before the repeat occurred."); + + + /** ****************** t = 6s ********************/ + + svg.setCurrentTime(6); // inside animation repeat + + ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && + t.baseVal.getItem(3) === t.old_baseVal_items[3], + "When a SMIL animation repeats, it should never affect the " + + t.list_type + " for " + t.bv_path + ", or its list items."); + + ok(t.animVal.numberOfItems == t.old_animVal_items.length && + t.animVal.getItem(4) === t.old_animVal_items[4], + "When an animation repeats, the list items that are at a given " + + "index in the " + t.list_type + " for " + t.av_path + " should be the exact " + + "same objects as were at that index before the repeat occurred."); + + + /** ****************** t = 7s ********************/ + + svg.setCurrentTime(7); // start of additive="sum" animation + + ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && + t.baseVal.getItem(3) === t.old_baseVal_items[3], + "When a new SMIL animation starts and should blend with an " + + "underlying animation, it should never affect the " + + t.list_type + " for " + t.bv_path + ", or its list items."); + + if (t.list_type == "SVGLengthList") { + // Length lists are a special case where it makes sense to allow shorter + // lists to be composed on top of longer lists (but not necessarily vice + // versa - see comment below). + + ok(t.animVal.numberOfItems == t.old_animVal_items.length && + t.animVal.getItem(3) === t.old_animVal_items[3], + 'When an animation with additive="sum" is added on top of an ' + + "existing animation that has more list items, the length of the " + + t.list_type + " for " + t.av_path + " should not change."); + } else { + +/* TODO + ok(false, + 'Decide what to do here - see ' + + 'https://bugzilla.mozilla.org/show_bug.cgi?id=573716 - we ' + + 'probably should be discarding any animation sandwich layers from ' + + 'a layer that fails to add, on up.'); +*/ + + // In other words, we wouldn't need the if-else check here. + + } + + // XXX what if the higher priority sandwich layer has *more* list items + // than the underlying animation? In that case we would need to + // distinguish between different SVGLengthList attributes, since although + // all SVGLengthList attributes allow a short list to be added to longer + // list, they do not all allow a longer list to be added to shorter list. + // Specifically that would not be good for 'x' and 'y' on <text> since + // lengths there are not naturally zero. See the comment in + // SVGLengthListSMILAttr::Add(). + + + /** ****************** t = 13s ********************/ + + svg.setCurrentTime(13); // all animations have finished, but one is frozen + + ok(t.baseVal.numberOfItems == t.old_baseVal_items.length && + t.baseVal.getItem(3) === t.old_baseVal_items[3], + "When a SMIL animation ends, it should never affect the " + + t.list_type + " for " + t.bv_path + ", or its list items."); + + is(t.animVal.numberOfItems, 5, + "Even though all SMIL animation have finished, the number " + + "of items in the " + t.list_type + " for " + t.av_path + + " should still be more than the same as the number of items in " + + t.bv_path + " since one of the animations is still frozen."); + + var expected = t.attr_val_5b_firstItem_x3_constructor(t.item_constructor); + t.item_is(t.animVal.getItem(0), expected, + 'animation with accumulate="sum" and repeatCount="3" for attribute "' + + t.attr_name + '" should end up at 3x the "to" value.'); + + // Unfreeze frozen animation (removing its effects) + var frozen_animate_element = + t.element.querySelector('animate[fill][attributeName="' + t.attr_name + '"]'); + frozen_animate_element.removeAttribute("fill"); + + ok(t.animVal.numberOfItems == t.baseVal.numberOfItems, + "Once all SMIL animation have finished and been un-frozen, the number " + + "of items in the " + t.list_type + " for " + t.av_path + + " should be the same as the number of items in " + t.bv_path + "."); + + ok(t.animVal.getItem(2) === t.old_animVal_items[2], + "Even after an animation finishes and is un-frozen, the list items " + + "that are at a given index in the " + t.list_type + " for " + t.av_path + + " should be the exact same objects as were at that index before the " + + "end and unfreezing of the animation occurred."); + } +} + + +function run_tests() { + // Initialize each test object with some useful properties, and create their + // 'animate' elements. Note that 'prop' and 'animVal' may be null. + for (let t of tests) { + t.element = document.getElementById(t.target_element_id); + t.prop = t.prop_name ? t.element[t.prop_name] : null; + t.baseVal = ( t.prop || t.element )[t.bv_name]; + t.animVal = t.av_name ? ( t.prop || t.element )[t.av_name] : null; + t.bv_path = t.el_type + "." + + (t.prop ? t.prop_name + "." : "") + + t.bv_name; // e.g. 'SVGTextElement.x.baseVal' + if (t.animVal) { + t.av_path = t.el_type + "." + + (t.prop ? t.prop_name + "." : "") + + t.av_name; + } + t.prop_type = t.prop_type || null; + + // use fallback 'is' function, if none was provided. + if (!t.item_is) { + t.item_is = function(itemA, itemB, message) { + ok(typeof(itemA.value) != "undefined" && + typeof(itemB.value) != "undefined", + "expecting value property"); + is(itemA.value, itemB.value, message); + }; + } + + if (t.animVal) { + t.element.appendChild(create_animate_elements(t)); + } + } + + // Run the major test groups: + + run_baseVal_API_tests(); + run_animVal_API_tests(); + run_basic_setAttribute_tests(); + run_list_mutation_tests(); + run_animation_timeline_tests(); + + // After all the other test manipulations, we check that the following + // objects have still not changed, since they never should: + + for (let t of tests) { + if (t.prop) { + ok(t.prop === t.element[t.prop_name], + "The same " + t.prop_type + " object should ALWAYS be returned for " + + t.el_type + "." + t.prop_name + " each time it is accessed."); + } + + ok(t.baseVal === ( t.prop || t.element )[t.bv_name], + "The same " + t.list_type + " object should ALWAYS be returned for " + + t.el_type + "." + t.prop_name + "." + t.bv_name + " each time it is accessed."); + + if (t.animVal) { + ok(t.animVal === ( t.prop || t.element )[t.av_name], + "The same " + t.list_type + " object should ALWAYS be returned for " + + t.el_type + "." + t.prop_name + "." + t.av_name + " each time it is accessed."); + } + } + + SimpleTest.finish(); +} + +window.addEventListener("load", run_tests); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_SVGxxxListIndexing.xhtml b/dom/svg/test/test_SVGxxxListIndexing.xhtml new file mode 100644 index 0000000000..b1dec4df31 --- /dev/null +++ b/dom/svg/test/test_SVGxxxListIndexing.xhtml @@ -0,0 +1,93 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=631437 +--> +<head> + <title>Tests the array indexing and .length on SVGXXXList objects</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=631437">Mozilla Bug 631437</a> +<svg xmlns="http://www.w3.org/2000/svg" id="svg"> + <text id="text" x="10 20 30" rotate="40 50 60">abcde</text> + <path id="path" d="M0,0 L100,100"/> + <polygon id="poly" points="50,50 70,70 90,50"/> + <g id="g" transform="translate(20 30) rotate(50 60 70) scale(2)" + requiredExtensions="foo bar baz"/> +</svg> +<script type="text/javascript"><![CDATA[ + +var text = document.getElementById("text"), + path = document.getElementById("path"), + poly = document.getElementById("poly"), + g = document.getElementById("g"); + +function CheckList(aListObject, aExpectedListLength, aListDescription) { + is(aListObject.numberOfItems, aExpectedListLength, aListDescription + ".numberOfItems"); + is(aListObject.length, aExpectedListLength, aListDescription + ".length"); + for (let i = 0; i < aListObject.length; i++) { + let item = aListObject.getItem(i); + ok(aListObject[i] === item, aListDescription + "[" + i + "]"); + } + is(typeof(aListObject[aListObject.length]), "undefined", aListDescription + "[outOfBounds]"); +} + +var tests = [ + { element: text, + attribute: "x", + listProperty: "x.baseVal", + type: "SVGLengthList", + subtests: [ { values: null, length: 3 }, + { values: "40", length: 1 }, + { values: "1em 2em 3em 4em 5em", length: 5 } ] }, + { element: text, + attribute: "rotate", + listProperty: "rotate.baseVal", + type: "SVGNumberList", + subtests: [ { values: null, length: 3 }, + { values: "10", length: 1 }, + { values: "1 2 3 4 5", length: 5 } ] }, + { element: poly, + attribute: "points", + listProperty: "animatedPoints", + type: "SVGPointList", + subtests: [ { values: null, length: 3 }, + { values: "100,100", length: 1 }, + { values: "0,0 10,10 20,0 30,10 40,0", length: 5 } ] }, + { element: g, + attribute: "transform", + listProperty: "transform.baseVal", + type: "SVGTransformList", + subtests: [ { values: null, length: 3 }, + { values: "skewX(45)", length: 1 }, + { values: "translate(1 2) rotate(3) scale(4) skewY(5) skewX(6)", + length: 5 } ] }, + { element: g, + attribute: "requiredExtensions", + listProperty: "requiredExtensions", + type: "SVGStringList", + subtests: [ { values: null, length: 3 }, + { values: "foo", length: 1 }, + { values: "foo bar baz qux", length: 4 } ] }, +]; + +for (let test of tests) { + let list = test.element; + for (let property of test.listProperty.split(".")) { + list = list[property]; + } + + for (let subtest of test.subtests) { + if (subtest.values) { + test.element.setAttribute(test.attribute, subtest.values); + } + + CheckList(list, subtest.length, + test.type + ": " + test.element.localName + "." + + test.listProperty); + } +} +]]></script> +</body> +</html> diff --git a/dom/svg/test/test_a_href_01.xhtml b/dom/svg/test/test_a_href_01.xhtml new file mode 100644 index 0000000000..a1ff8d7227 --- /dev/null +++ b/dom/svg/test/test_a_href_01.xhtml @@ -0,0 +1,96 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=620295 +https://bugzilla.mozilla.org/show_bug.cgi?id=1245751 +--> +<head> + <title>Test that activating SVG 'a' elements navigate to their xlink:href or href</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=620295">Mozilla Bug 620295</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245751">Mozilla Bug 1245751</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="text/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var testCount = 7; +var didWindowLoad = false; +var frameLoadCount = 0; +var navigationCount = 0; + +function endsWith(s1, s2) { + s1 = String(s1); + return s1.length >= s2.length && s1.substring(s1.length - s2.length) == s2; +} + +function windowLoaded() { + didWindowLoad = true; + doNavigationIfReady(); +} + +function frameLoaded() { + frameLoadCount++; + doNavigationIfReady(); +} + +function doNavigationIfReady() { + if (didWindowLoad && frameLoadCount == testCount) { + doNavigation(); + } +} + +function doNavigation() { + // Test clicking on an unmodified <a>. + doNavigationTest(1, "a_href_helper_01.svg"); + // Test clicking on an <a> whose xlink:href is modified by assigning to href.baseVal. + doNavigationTest(2, "a_href_helper_02_03.svg", function(a) { a.href.baseVal = "a_href_destination.svg"; }); + // Test clicking on an <a> whose xlink:href is modified by a setAttributeNS call. + doNavigationTest(3, "a_href_helper_02_03.svg", function(a) { a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "a_href_destination.svg"); }); + // Test clicking on an <a> whose xlink:href is modified by animation. + doNavigationTest(4, "a_href_helper_04.svg"); + // Test clicking on an unmodified <a> with both href and xlink:href. + doNavigationTest(5, "a_href_helper_05.svg"); + // Test clicking on an <a> whose href is modified by a setAttribute call. + doNavigationTest(6, "a_href_helper_06.svg", function(a) { a.setAttribute("href", "a_href_destination.svg"); }); + // Test clicking on an <a> whose href is modified by animation. + doNavigationTest(7, "a_href_helper_07.svg"); +} + +function doNavigationTest(testNumber, initialHref, f) { + var iframe = document.getElementById("iframe" + testNumber); + var a = iframe.contentDocument.getElementById("a"); + ok(endsWith(iframe.contentWindow.location, initialHref), "Initial href of test " + testNumber); + is("pointer", window.getComputedStyle(a).getPropertyValue("cursor"), "expected pointer cursor"); + iframe.onload = function() { + ok(endsWith(iframe.contentWindow.location, "a_href_destination.svg"), "Final href of test " + testNumber); + if (++navigationCount == testCount) { + SimpleTest.finish(); + } + }; + if (f) { + f(a); + } + sendMouseEvent({type: "click"}, a); +} + +window.onload = windowLoaded; + +]]></script> +</pre> +<div id="content" style="visibility: hidden"> +<!-- These must come after frameLoaded is defined --> +<iframe id="iframe1" src="a_href_helper_01.svg" onload="frameLoaded()"></iframe> +<iframe id="iframe2" src="a_href_helper_02_03.svg" onload="frameLoaded()"></iframe> +<iframe id="iframe3" src="a_href_helper_02_03.svg" onload="frameLoaded()"></iframe> +<iframe id="iframe4" src="a_href_helper_04.svg" onload="frameLoaded()"></iframe> +<iframe id="iframe5" src="a_href_helper_05.svg" onload="frameLoaded()"></iframe> +<iframe id="iframe6" src="a_href_helper_06.svg" onload="frameLoaded()"></iframe> +<iframe id="iframe7" src="a_href_helper_07.svg" onload="frameLoaded()"></iframe> +</div> +</body> +</html> diff --git a/dom/svg/test/test_a_href_02.xhtml b/dom/svg/test/test_a_href_02.xhtml new file mode 100644 index 0000000000..0d2277674e --- /dev/null +++ b/dom/svg/test/test_a_href_02.xhtml @@ -0,0 +1,37 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=620295 +--> +<head> + <title>Test that the href property reflects xlink:href="" on 'a' elements</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=620295">Mozilla Bug 620295</a> +<p id="display"></p> +<div id="content"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <a id="a" xlink:href="a"/> +</svg> +</div> +<pre id="test"> +<script><![CDATA[ + +var a = document.getElementById("a"); + +// Initial attribute value should be reflected in the href property +is(a.href.baseVal, "a", "Initial property value"); + +// Updated attribute value should be reflected in the href property +a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "b"); +is(a.href.baseVal, "b", "Updated property value"); + +// Modifying the href property should cause the attribute to be updated +a.href.baseVal = "c"; +is(a.getAttributeNS("http://www.w3.org/1999/xlink", "href"), "c", "Updated attribute value"); + +]]></script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_animLengthObjectIdentity.xhtml b/dom/svg/test/test_animLengthObjectIdentity.xhtml new file mode 100644 index 0000000000..b4bce755b9 --- /dev/null +++ b/dom/svg/test/test_animLengthObjectIdentity.xhtml @@ -0,0 +1,86 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=508496 +--> +<head> + <title>Test for object identity of SVG animated lengths</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=506856">Mozilla Bug 508496</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <circle cx="-100" cy="-100" r="15" fill="blue" id="circle"> + <animate attributeName="cx" from="0" to="100" dur="4s" begin="1s; 10s" + fill="freeze" id="animate"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test object identity of animated lengths **/ + +/* Global Variables */ +const svgns = "http://www.w3.org/2000/svg"; +var svg = document.getElementById("svg"); +var circle = document.getElementById("circle"); + +SimpleTest.waitForExplicitFinish(); + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + var animLength = circle.cx; + ok(animLength === circle.cx, + "Got different SVGAnimatedLength objects at startup"); + + var baseVal = circle.cx.baseVal; + ok(baseVal === circle.cx.baseVal, + "Got different baseVal SVGLength objects at startup"); + + var animVal = circle.cx.animVal; + ok(animVal === circle.cx.animVal, + "Got different animVal SVGLength objects at startup"); + + var animate = document.getElementById("animate"); + if (animate && animate.targetElement) { + // Sample mid-way through the animation + svg.setCurrentTime(5); + + ok(animLength === circle.cx, + "Got different SVGAnimatedLength objects during animation"); + ok(baseVal === circle.cx.baseVal, + "Got different baseVal SVGLength objects during animation"); + ok(animVal === circle.cx.animVal, + "Got different animVal SVGLength objects during animation"); + } + + // Drop all references to the tear off objects + var oldValue = circle.cx.animVal.value; // Just a float, not an object ref + animLength = null; + baseVal = null; + animVal = null; + SpecialPowers.gc(); + + // The tearoff objects should no longer exist and we should create new ones. + // If somehow, the tearoff objects have died and yet not been removed from the + // hashmap we'll end up in all sorts of trouble when we try to access them. + // So in the following, we're not really interested in the value, just that we + // don't crash. + is(circle.cx.animVal.value, oldValue, + "Unexpected result accessing new(?) length object."); + + SimpleTest.finish(); +} + +window.addEventListener("load", main); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_animLengthReadonly.xhtml b/dom/svg/test/test_animLengthReadonly.xhtml new file mode 100644 index 0000000000..f9c16e7752 --- /dev/null +++ b/dom/svg/test/test_animLengthReadonly.xhtml @@ -0,0 +1,219 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=506856 +--> +<head> + <title>Test for read-only times of SVG animated lengths</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=506856">Mozilla Bug 506856</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" + width="100px" height="100px" viewBox="0 0 100 100" + onload="this.pauseAnimations()"> + <!-- Note: Need a viewBox on the SVG element, or else our percent-length + basis will be '0' (based off of the viewport width, which is 0 in this + case since we're in a display:none iframe. We use viewport width because + the lack of a viewbox forces us into the not-mViewBox::IsValid() case of + SVGSVGElement::GetLength). + + And we don't want a percent-length basis of 0, because then when we call + convertToSpecifiedUnits to convert out of percent units, we divide by 0 + and get unexpected results. + --> + <circle cx="-100" cy="-100" r="15" fill="blue" id="circle"> + <animate attributeName="cx" from="0" to="100" dur="4s" begin="1s; 10s" + fill="freeze" id="animate"/> + <animate attributeName="cy" from="-100" to="-100" dur="4s" begin="1s; 10s" + fill="remove"/> + </circle> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test read-only times of animated lengths **/ + +/* Global Variables */ +const svgns = "http://www.w3.org/2000/svg"; +var svg = document.getElementById("svg"); +var circle = document.getElementById("circle"); + +SimpleTest.waitForExplicitFinish(); + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // Sanity check: check initial values + is(circle.cx.baseVal.value, -100); + is(circle.cx.animVal.value, -100); + is(circle.cy.baseVal.value, -100); + is(circle.cy.animVal.value, -100); + + // (1): Check before animation that animVal's are readonly + ok(checkReadOnly(circle.cx), + "(1) animVal cx is editable when not animated"); + ok(checkReadOnly(circle.cy), + "(1) animVal cy is editable when not animated"); + + // Skip to mid-way through the animation + svg.setCurrentTime(4); + + // (2): Check that whilst animations are active the animVal's are readonly + ok(checkReadOnly(circle.cx), + "(2) animVal cx is editable when animated"); + ok(checkReadOnly(circle.cy), + "(2) animVal cy is editable when animated"); + + // Skip to past end + svg.setCurrentTime(6); + + // (3): Check that frozen animations are readonly and have different values + ok(checkReadOnly(circle.cx), + "(3) animVal cx is editable when frozen"); + checkDiffValue(circle.cx); + + // (4): Check that finished animations are readonly and have same values + ok(checkReadOnly(circle.cy), + "(4) animVal cy is editable when inactive"); + checkSameValue(circle.cy); + + SimpleTest.finish(); +} + +function checkReadOnly(animLength) { + var exceptionCaught = false; + var oldAnimValue = animLength.animVal.value; + + // Try changing value + try { + animLength.animVal.value = (animLength.animVal.value == 77) ? 88 : 77; + } catch (e) { + if (e.name == "NoModificationAllowedError" && + e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR) { + exceptionCaught = true; + } else { + ok(false, "Got unexpected exception " + e); + return false; + } + } + is(animLength.animVal.value, oldAnimValue, + "No exception thrown, but animVal was changed."); + if (animLength.animVal.value != oldAnimValue) return false; + + // valueInSpecifiedUnits + try { + exceptionCaught = false; + animLength.animVal.valueInSpecifiedUnits = -100; + } catch (e) { exceptionCaught = true; } + ok(exceptionCaught, "animVal.valueInSpecifiedUnits failed to throw."); + if (!exceptionCaught) return false; + + // valueAsString + try { + exceptionCaught = false; + animLength.animVal.valueAsString = "-100px"; + } catch (e) { exceptionCaught = true; } + ok(exceptionCaught, "animVal.valueAsString failed to throw."); + if (!exceptionCaught) return false; + + // newValueSpecifiedUnits + try { + exceptionCaught = false; + animLength.animVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, -100); + } catch (e) { exceptionCaught = true; } + ok(exceptionCaught, "animVal.newValueSpecifiedUnits failed to throw."); + if (!exceptionCaught) return false; + + // convertToSpecifiedUnits + try { + exceptionCaught = false; + animLength.animVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_NUMBER); + } catch (e) { exceptionCaught = true; } + ok(exceptionCaught, "animVal.convertToSpecifiedUnits failed to throw."); + + return exceptionCaught; +} + +function checkSameValue(animLength) { + // value + animLength.baseVal.value = 1; + is(animLength.animVal.value, 1, + "un-animated animVal.value not changed after setting baseValue.value"); + + // valueInSpecifiedUnits + animLength.baseVal.valueInSpecifiedUnits = 2; + is(animLength.animVal.value, 2, + "un-animated animVal.value not changed after setting " + + "baseValue.valueInSpecifiedUnits"); + + // valueAsString + animLength.baseVal.valueAsString = "3"; + is(animLength.animVal.value, 3, + "un-animated animVal.value not changed after setting " + + "baseValue.valueAsString"); + + // newValueSpecifiedUnits + animLength.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_CM, 4); + is(animLength.animVal.valueInSpecifiedUnits, 4, + "un-animated animVal.value not changed after setting " + + "baseValue.newValueSpecifiedUnits"); + + // convertToSpecifiedUnits + animLength.baseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_MM); + is(animLength.animVal.valueInSpecifiedUnits, 40, + "un-animated animVal.value not changed after calling " + + "baseValue.convertToSpecifiedUnits"); +} + +function checkDiffValue(animLength) { + // We assume here that the animation is not additive and hence changing the + // baseValue will not be reflected in the animValue + var origValue = animLength.animVal.value; + + // value + animLength.baseVal.value = 1; + is(animLength.animVal.value, origValue, + "animated animVal.value changed after setting baseValue.value"); + + // valueInSpecifiedUnits + animLength.baseVal.valueInSpecifiedUnits = 2; + is(animLength.animVal.value, origValue, + "animated animVal.value changed after setting " + + "baseValue.valueInSpecifiedUnits"); + + // valueAsString + animLength.baseVal.valueAsString = "3"; + is(animLength.animVal.value, origValue, + "animated animVal.value changed after setting baseValue.valueAsString"); + + // newValueSpecifiedUnits + // (Note: we'd like to convert to MM here and CM in the next step for + // consistency with the other tests. However, internally that will cause the + // animVal to be converted to MM and back again which, given certain dpi + // values such as we get in Linux, this may lead to rounding errors so that + // 100 becomes 99.99999237060547. So instead we convert to something + // independent of dpi) + animLength.baseVal.newValueSpecifiedUnits( + SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 40); + is(animLength.animVal.value, origValue, + "animated animVal.value changed after setting " + + "baseValue.newValueSpecifiedUnits"); + + // convertToSpecifiedUnits + animLength.baseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX); + is(animLength.animVal.value, origValue, + "animated animVal.value changed after calling " + + "baseValue.convertToSpecifiedUnits"); +} + +window.addEventListener("load", main); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_animLengthUnits.xhtml b/dom/svg/test/test_animLengthUnits.xhtml new file mode 100644 index 0000000000..59faaed1f0 --- /dev/null +++ b/dom/svg/test/test_animLengthUnits.xhtml @@ -0,0 +1,125 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=507067 +--> +<head> + <title>Test for units of SVG animated lengths</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=507067">Mozilla Bug 507067</a> +<p id="display"></p> +<div id="content"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px" + onload="this.pauseAnimations()"> + <g font-size="10px"> + <circle cx="-100" cy="20" r="15" fill="blue" id="circle"> + <animate attributeName="cx" from="0em" to="10em" dur="8s" begin="1s" + fill="freeze" id="animate"/> + </circle> + </g> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test units of animated lengths **/ + +/* Global Variables */ +const svgns = "http://www.w3.org/2000/svg"; +var svg = document.getElementById("svg"); +var circle = document.getElementById("circle"); +var animate = document.getElementById("animate"); + +SimpleTest.waitForExplicitFinish(); + +// Interop comments are based on: +// +// Opera -- 10 beta 2 +// WebKit -- July 09 trunk build +// Batik -- 1.7 +// Firefox -- July 09 trunk build +// + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // Sanity check: check initial values + is(circle.cx.baseVal.valueInSpecifiedUnits, -100, + "Unexpected initial baseVal"); + is(circle.cx.baseVal.unitType, SVGLength.SVG_LENGTHTYPE_NUMBER, + "Unexpected initial baseVal units"); + is(circle.cx.animVal.valueInSpecifiedUnits, -100, + "Unexpected initial animVal"); + is(circle.cx.animVal.unitType, SVGLength.SVG_LENGTHTYPE_NUMBER, + "Unexpected initial animVal units"); + + // Sample mid-way through the animation + svg.setCurrentTime(5); + + // (1) Check the absolute value is right + // + // We're not too worried about the units. Based on our testing we get: + // Opera: Will use user units for the animVal + // Safari: Doesn't work + // Batik: Will use the units specified on the animation function provided they + // are the same + // FF: Will use the units of the baseVal for the animVal + // + is(circle.cx.baseVal.value, -100, + "(1) Unexpected value for baseVal during animation"); + is(circle.cx.animVal.value, 50, + "(1) Unexpected value for animVal during animation"); + + // Change font-size and check + circle.parentNode.setAttribute("font-size", "5px"); + + // Currently, changing the font-size on a parent doesn't force a resample (see + // bug 508206) so we have to give the animation a chance to run + window.requestAnimationFrame(checkAfterChangeFontSize); +} + +function checkAfterChangeFontSize() { + // (2) Check that changing the font-size of the parent element is reflected in + // the anim val + is(circle.cx.baseVal.value, -100, + "(2) Unexpected value for baseVal after changing font-size during " + + "animation"); + is(circle.cx.animVal.value, 25, + "(2) Unexpected value for animVal after changing font-size during " + + "animation"); + + // Do the same again, when the animation is frozen + svg.setCurrentTime(10); + circle.parentNode.setAttribute("font-size", "7px"); + + // Again, due to bug 508206 we need to give the animation a chance to resample + window.requestAnimationFrame(checkWhilstFrozen); +} + +function checkWhilstFrozen() { + // (3) Check that changing the font-size of the parent element is reflected in + // the anim val + is(circle.cx.baseVal.value, -100, + "(3) Unexpected value for baseVal after changing font-size whilst " + + "frozen"); + is(circle.cx.animVal.value, 70, + "(3) Unexpected value for animVal after changing font-size whilst " + + "frozen"); + + SimpleTest.finish(); +} + +if (animate && animate.targetElement) { + window.addEventListener("load", main); +} else { + ok(true); // Skip tests but don't report 'todo' either + SimpleTest.finish(); +} +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_bbox-changes.xhtml b/dom/svg/test/test_bbox-changes.xhtml new file mode 100644 index 0000000000..1877754a21 --- /dev/null +++ b/dom/svg/test/test_bbox-changes.xhtml @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1159053 +--> +<head> + <title>Test that the results of getBBox update for changes</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<p id="display"> + <svg xmlns="http://www.w3.org/2000/svg"> + <rect id="rect1" x="10" y="10" width="10" height="10"/> + <rect id="rect2" x="30" y="10" width="10" height="10"/> + <g id="g"> + <circle id="circle1" cx="60" cy="20" r="5"/> + <circle id="circle2" cx="120" cy="20" r="5"/> + </g> + </svg> +</p> + +<div id="content" style="display: none"></div> + +<pre id="test"> +<script class="testbody" type="application/javascript">//<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function init_and_run() { + SpecialPowers.pushPrefEnv({"set": [["svg.new-getBBox.enabled", true]]}, run); +} + +function checkBBox(id, options, x, y, width, height, msg) { + var bbox = document.getElementById(id).getBBox(options); + is(bbox.x, x, id + ".getBBox().x" + msg); + is(bbox.y, y, id + ".getBBox().y" + msg); + is(bbox.width, width, id + ".getBBox().width" + msg); + is(bbox.height, height, id + ".getBBox().height" + msg); +} + +function run() { + // First call getBBox on 'rect1' with stroke included: + $("rect1").setAttribute("stroke", "black"); + $("rect1").setAttribute("stroke-width", "10"); + checkBBox("rect1", { fill: true, stroke: true }, 5, 5, 20, 20, " with stroke"); + + // Now remove the stroke from 'rect1' and check again: + $("rect1").removeAttribute("stroke"); + $("rect1").removeAttribute("stroke-width"); + checkBBox("rect1", { fill: true }, 10, 10, 10, 10, " after stroke removed"); + + // First call getBBox on 'rect2' without a stroke included: + checkBBox("rect2", { fill: true }, 30, 10, 10, 10, " with stroke"); + + // Now add a stroke to 'rect2' and check again: + $("rect2").setAttribute("stroke", "black"); + $("rect2").setAttribute("stroke-width", "10"); + checkBBox("rect2", { fill: true, stroke: true }, 25, 5, 20, 20, " with stroke"); + + // Check the initial result for getBBox on the group: + checkBBox("g", { fill: true }, 55, 15, 70, 10, " before child moves"); + + // Now move one of the circle children and check again: + $("circle2").setAttribute("cx", "110"); + checkBBox("g", { fill: true }, 55, 15, 60, 10, " after child moves"); + + SimpleTest.finish(); +} + +window.addEventListener("load", init_and_run); + +//]]></script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_bbox-with-invalid-viewBox.xhtml b/dom/svg/test/test_bbox-with-invalid-viewBox.xhtml new file mode 100644 index 0000000000..c962b469f6 --- /dev/null +++ b/dom/svg/test/test_bbox-with-invalid-viewBox.xhtml @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=433063 +--> +<head> + <title>Test for getBBox when the viewBox is invalid</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<p id="display"></p> +<div id="content" style="display: none"></div> + +<svg id="svg" xmlns="http://www.w3.org/2000/svg" + style="width:200px; height:200px;" viewBox="0 0 200 0"> + <rect width="120" height="120"/> +</svg> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function run() { + var bbox = $("svg").getBBox(); + is(bbox.x, 0, "Check bbox.x"); + is(bbox.y, 0, "Check bbox.y"); + is(bbox.width, 120, "Check bbox.width"); + is(bbox.height, 120, "Check bbox.height"); + SimpleTest.finish(); +} + +window.addEventListener("load", run); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_bbox.xhtml b/dom/svg/test/test_bbox.xhtml new file mode 100644 index 0000000000..19e1aadf2c --- /dev/null +++ b/dom/svg/test/test_bbox.xhtml @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=449327 +--> +<head> + <title>Test for getBBox</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="bbox-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript">//<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var doc = $("svg").contentDocument; + + function isFuzzy(a, b, error, name) { + ok(!(Math.abs(a - b) > error), + name + " - got " + a + ", expected " + b + " (within " + error + ")"); + } + + function getBBox(id) { + return doc.getElementById(id).getBBox(); + } + function checkBBox(id, x, y, width, height, error) { + var bbox = getBBox(id); + isFuzzy(bbox.x, x, error, id + ".getBBox().x"); + isFuzzy(bbox.y, y, error, id + ".getBBox().y"); + isFuzzy(bbox.width, width, error, id + ".getBBox().width"); + isFuzzy(bbox.height, height, error, id + ".getBBox().height"); + } + function compareBBox(id1, id2) { + var bbox1 = getBBox(id1); + var bbox2 = getBBox(id2); + is(bbox1.x, bbox2.x, id1 + ".getBBox().x"); + is(bbox1.y, bbox2.y, id1 + ".getBBox().y"); + isFuzzy(bbox1.width, bbox2.width, 0.0002, id1 + ".getBBox().width"); + is(bbox1.height, bbox2.height, id1 + ".getBBox().height"); + } + function compareBBoxFuzzy(id1, id2, err) { + var bbox1 = getBBox(id1); + var bbox2 = getBBox(id2); + isfuzzy(bbox1.x, bbox2.x, err, id1 + ".getBBox().x"); + isfuzzy(bbox1.y, bbox2.y, err, id1 + ".getBBox().y"); + isfuzzy(bbox1.width, bbox2.width, err, id1 + ".getBBox().width"); + isfuzzy(bbox1.height, bbox2.height, err, id1 + ".getBBox().height"); + } + function compareBBoxHeight(id1, id2) { + var bbox1 = getBBox(id1); + var bbox2 = getBBox(id2); + is(bbox1.height, bbox2.height, id1 + ".getBBox().height"); + } + function compareBBoxWidthWithScaleFuzzy(id1, id2, scaleOfId2, err) { + var bbox1 = getBBox(id1); + var bbox2 = getBBox(id2); + isfuzzy(bbox1.width, bbox2.width * scaleOfId2, err, id1 + ".getBBox().width"); + } + + checkBBox("fO", 10, 10, 100, 100, 0.0); + checkBBox("i", 10, 10, 100, 100, 0.0); + compareBBoxHeight("a", "b"); + compareBBoxHeight("a", "y"); + compareBBox("b", "tspantext1"); + compareBBoxFuzzy("tspantext1", "tspan1", 5); + compareBBoxHeight("tspantext2", "tspan2"); + compareBBoxWidthWithScaleFuzzy("tspantext2", "tspan2", 2, 5); + compareBBoxHeight("text", "lrmText"); + checkBBox("v", 95, 45, 10, 155, 0.001); + checkBBox("h", 195, 45, 105, 55, 0.001); + checkBBox("e", 95, 95, 10, 10, 0.001); + checkBBox("use_v", 195, 145, 10, 155, 0.001); + checkBBox("use_h", 295, 145, 105, 55, 0.001); + checkBBox("use_e", 195, 195, 10, 10, 0.001); + SimpleTest.finish(); +} + +window.addEventListener("load", run); + +//]]></script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_bounds.html b/dom/svg/test/test_bounds.html new file mode 100644 index 0000000000..f349123ba3 --- /dev/null +++ b/dom/svg/test/test_bounds.html @@ -0,0 +1,306 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=463934 +--> +<head> + <title>Test for Bug 463934</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style> + #css-trans-rect-1 { + transform: scaleX(2) + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463934">Mozilla Bug 463934</a> +<p id="display"></p> +<div id="content"> + <svg id="outer-1" xmlns="http://www.w3.org/2000/svg" width="30" height="30"></svg> + <svg id="outer-2" xmlns="http://www.w3.org/2000/svg" width="30" height="30" + style="padding: 10px;"> + </svg> + <svg id="outer-3" xmlns="http://www.w3.org/2000/svg" width="30" height="30" + style="border: 10px solid black;"> + </svg> + <svg id="outer-4" xmlns="http://www.w3.org/2000/svg" width="30" height="30" + style="border: 10px solid black; padding: 10px"> + </svg> + <svg id="outer-5" xmlns="http://www.w3.org/2000/svg" width="30" height="30"> + <rect id="css-trans-rect-1" width="6" height="6"></rect> + </svg> +</div> + +<iframe id="svg" src="bounds-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function Rect(left, top, width, height) { + this.left = left; + this.top = top; + this.width = width; + this.height = height; +} + +Rect.prototype.roundOut = function() { + this.width = Math.ceil(this.left + this.width) - Math.floor(this.left); + this.height = Math.ceil(this.top + this.height) - Math.floor(this.top); + this.left = Math.floor(this.left); + this.top = Math.floor(this.top); +}; + +function runTest() { + var bounds; + + bounds = document.getElementById("outer-1").getBoundingClientRect(); + is(bounds.width, 30, "outer-svg 1 getBoundingClientRect().width"); + is(bounds.height, 30, "outer-svg 1 getBoundingClientRect().height"); + + bounds = document.getElementById("outer-2").getBoundingClientRect(); + is(bounds.width, 50, "outer-svg 2 getBoundingClientRect().width"); + is(bounds.height, 50, "outer-svg 2 getBoundingClientRect().height"); + + bounds = document.getElementById("outer-3").getBoundingClientRect(); + is(bounds.width, 50, "outer-svg 3 getBoundingClientRect().width"); + is(bounds.height, 50, "outer-svg 3 getBoundingClientRect().height"); + + bounds = document.getElementById("outer-4").getBoundingClientRect(); + is(bounds.width, 70, "outer-svg 4 getBoundingClientRect().width"); + is(bounds.height, 70, "outer-svg 4 getBoundingClientRect().height"); + + bounds = document.getElementById("css-trans-rect-1").getBoundingClientRect(); + is(bounds.width, 12, "css-trans-rect getBoundingClientRect().width"); + is(bounds.height, 6, "css-trans-rect getBoundingClientRect().height"); + + var doc = $("svg").contentWindow.document; + + var svg1Bounds = doc.getElementById("svg1").getBoundingClientRect(); + is(svg1Bounds.left, 10, "svg1.getBoundingClientRect().left"); + is(svg1Bounds.top, 10, "svg1.getBoundingClientRect().top"); + is(svg1Bounds.width, 25, "svg1.getBoundingClientRect().width"); + is(svg1Bounds.height, 30, "svg1.getBoundingClientRect().height"); + + var svg2Bounds = doc.getElementById("svg2").getBoundingClientRect(); + is(svg2Bounds.left, 0, "svg2.getBoundingClientRect().left"); + is(svg2Bounds.top, 0, "svg2.getBoundingClientRect().top"); + is(svg2Bounds.width, 2, "svg2.getBoundingClientRect().width"); + is(svg2Bounds.height, 2, "svg2.getBoundingClientRect().height"); + + var svg3Bounds = doc.getElementById("svg3").getBoundingClientRect(); + is(svg3Bounds.left, 0, "svg3.getBoundingClientRect().left"); + is(svg3Bounds.top, 0, "svg3.getBoundingClientRect().top"); + is(svg3Bounds.width, 1, "svg3.getBoundingClientRect().width"); + is(svg3Bounds.height, 1, "svg3.getBoundingClientRect().height"); + + var use1Bounds = doc.getElementById("use1").getBoundingClientRect(); + is(use1Bounds.left, 100, "use1.getBoundingClientRect().left"); + is(use1Bounds.top, 50, "use1.getBoundingClientRect().top"); + is(use1Bounds.width, 50, "use1.getBoundingClientRect().width"); + is(use1Bounds.height, 10, "use1.getBoundingClientRect().height"); + + var text1 = doc.getElementById("text1"); + + var text1Bounds = text1.getBoundingClientRect(); + var text2Bounds = doc.getElementById("text2").getBoundingClientRect(); + var text3Bounds = doc.getElementById("text3").getBoundingClientRect(); + + const sin45 = Math.sin(Math.PI / 4); + + isfuzzy(text1Bounds.left, 24, 1, "text1.getBoundingClientRect().left"); + + is(text2Bounds.left, text1Bounds.left + 100, "text2.getBoundingClientRect().left"); + is(text2Bounds.top, text1Bounds.top, "text2.getBoundingClientRect().top"); + isfuzzy(text2Bounds.width, text1Bounds.width, 0.1, "text2.getBoundingClientRect().width"); + is(text2Bounds.height, text1Bounds.height, "text2.getBoundingClientRect().height"); + + var r = (text1Bounds.width + text1Bounds.height) * sin45; + isfuzzy(text3Bounds.width, Math.ceil(r), 1, "text3.getBoundingClientRect().width"); + isfuzzy(text3Bounds.height, Math.ceil(r), 1, "text3.getBoundingClientRect().height"); + + var rect1Bounds = doc.getElementById("rect1").getBoundingClientRect(); + var rect2Bounds = doc.getElementById("rect2").getBoundingClientRect(); + var rect3Bounds = doc.getElementById("rect3").getBoundingClientRect(); + var rect4Bounds = doc.getElementById("rect4").getBoundingClientRect(); + + is(rect1Bounds.left, 50, "rect1.getBoundingClientRect().left"); + is(rect1Bounds.top, 50, "rect1.getBoundingClientRect().top"); + is(rect1Bounds.width, 50, "rect1.getBoundingClientRect().width"); + is(rect1Bounds.height, 50, "rect1.getBoundingClientRect().height"); + + var rect = new Rect(175 - 50 * sin45, 75 - 50 * sin45, 50 * sin45 * 2, 50 * sin45 * 2); + isfuzzy(rect2Bounds.left, rect.left, 0.1, "rect2.getBoundingClientRect().left"); + isfuzzy(rect2Bounds.top, rect.top, 0.1, "rect2.getBoundingClientRect().top"); + isfuzzy(rect2Bounds.width, rect.width, 0.1, "rect2.getBoundingClientRect().width"); + isfuzzy(rect2Bounds.height, rect.height, 0.1, "rect2.getBoundingClientRect().height"); + + is(rect3Bounds.left, 50, "rect3.getBoundingClientRect().left"); + is(rect3Bounds.top, 160, "rect3.getBoundingClientRect().top"); + is(rect3Bounds.width, 100, "rect3.getBoundingClientRect().width"); + is(rect3Bounds.height, 100, "rect3.getBoundingClientRect().height"); + + rect = new Rect(350 - 100 * sin45, 150 - 100 * sin45, 100 * sin45 * 2, 100 * sin45 * 2); + isfuzzy(rect4Bounds.left, rect.left, 0.1, "rect4.getBoundingClientRect().left"); + isfuzzy(rect4Bounds.top, rect.top, 0.1, "rect4.getBoundingClientRect().top"); + isfuzzy(rect4Bounds.width, rect.width, 0.1, "rect4.getBoundingClientRect().width"); + isfuzzy(rect4Bounds.height, rect.height, 0.1, "rect4.getBoundingClientRect().height"); + + var rect1aBounds = doc.getElementById("rect1a").getBoundingClientRect(); + var rect2aBounds = doc.getElementById("rect2a").getBoundingClientRect(); + var rect3aBounds = doc.getElementById("rect3a").getBoundingClientRect(); + var rect3bBounds = doc.getElementById("rect3b").getBoundingClientRect(); + var rect4aBounds = doc.getElementById("rect4a").getBoundingClientRect(); + + is(rect1aBounds.left, 48, "rect1a.getBoundingClientRect().left"); + is(rect1aBounds.top, 48, "rect1a.getBoundingClientRect().top"); + is(rect1aBounds.width, 54, "rect1a.getBoundingClientRect().width"); + is(rect1aBounds.height, 54, "rect1a.getBoundingClientRect().height"); + + rect = new Rect(175 - 54 * sin45, 75 - 54 * sin45, 54 * sin45 * 2, 54 * sin45 * 2); + isfuzzy(rect2aBounds.left, rect.left, 0.1, "rect2a.getBoundingClientRect().left"); + isfuzzy(rect2aBounds.top, rect.top, 0.1, "rect2a.getBoundingClientRect().top"); + isfuzzy(rect2aBounds.width, rect.width, 0.1, "rect2a.getBoundingClientRect().width"); + isfuzzy(rect2aBounds.height, rect.height, 0.1, "rect2a.getBoundingClientRect().height"); + + is(rect3aBounds.left, 46, "rect3a.getBoundingClientRect().left"); + is(rect3aBounds.top, 156, "rect3a.getBoundingClientRect().top"); + is(rect3aBounds.width, 108, "rect3a.getBoundingClientRect().width"); + is(rect3aBounds.height, 108, "rect3a.getBoundingClientRect().height"); + + isfuzzy(rect3bBounds.left, 198, 0.1, "rect3b.getBoundingClientRect().left"); + isfuzzy(rect3bBounds.top, 198, 0.1, "rect3b.getBoundingClientRect().top"); + isfuzzy(rect3bBounds.width, 54, 0.1, "rect3b.getBoundingClientRect().width"); + isfuzzy(rect3bBounds.height, 54, 0.1, "rect3b.getBoundingClientRect().height"); + + rect = new Rect(350 - 108 * sin45, 150 - 108 * sin45, 108 * sin45 * 2, 108 * sin45 * 2); + isfuzzy(rect4aBounds.left, rect.left, 0.1, "rect4a.getBoundingClientRect().left"); + isfuzzy(rect4aBounds.top, rect.top, 0.1, "rect4a.getBoundingClientRect().top"); + isfuzzy(rect4aBounds.width, rect.width, 0.1, "rect4a.getBoundingClientRect().width"); + isfuzzy(rect4aBounds.height, rect.height, 0.1, "rect4a.getBoundingClientRect().height"); + + var text1a = doc.getElementById("text1a"); + + var text1aBounds = text1a.getBoundingClientRect(); + var text2aBounds = doc.getElementById("text2a").getBoundingClientRect(); + + isfuzzy(text1aBounds.left, 82, 1, "text1a.getBoundingClientRect().left"); + is(text1aBounds.width, text1Bounds.width + 4, "text1a.getBoundingClientRect().width"); + + is(text2aBounds.left, text1aBounds.left + 100 - 3, "text2a.getBoundingClientRect().left"); + isfuzzy(text2aBounds.width, text1aBounds.width + 6, 0.1, "text2a.getBoundingClientRect().width"); + + var iBounds = doc.getElementById("i").getBoundingClientRect(); + is(iBounds.left, 20, "i.getBoundingClientRect().left"); + is(iBounds.top, 20, "i.getBoundingClientRect().top"); + is(iBounds.width, 200, "i.getBoundingClientRect().width"); + is(iBounds.height, 200, "i.getBoundingClientRect().height"); + + var text4Bounds = doc.getElementById("text4").getBoundingClientRect(); + is(text4Bounds.left, 0, "text4.getBoundingClientRect().left"); + is(text4Bounds.top, 0, "text4.getBoundingClientRect().top"); + is(text4Bounds.width, 0, "text4.getBoundingClientRect().width"); + is(text4Bounds.height, 0, "text4.getBoundingClientRect().height"); + + var gBounds = doc.getElementById("g2").getBoundingClientRect(); + is(gBounds.left, 100, "g2.getBoundingClientRect().left"); + is(gBounds.top, 100, "g2.getBoundingClientRect().top"); + is(gBounds.width, 50, "g2.getBoundingClientRect().width"); + is(gBounds.height, 50, "g2.getBoundingClientRect().height"); + + var nonScalingStrokedCircle1Bounds = + doc.getElementById("nonScalingStrokedCircle1").getBoundingClientRect(); + isfuzzy(nonScalingStrokedCircle1Bounds.left, 10, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().left"); + isfuzzy(nonScalingStrokedCircle1Bounds.top, 105, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().top"); + isfuzzy(nonScalingStrokedCircle1Bounds.width, 70, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().width"); + isfuzzy(nonScalingStrokedCircle1Bounds.height, 50, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().height"); + + var nonScalingStrokedEllipse1Bounds = + doc.getElementById("nonScalingStrokedEllipse1").getBoundingClientRect(); + isfuzzy(nonScalingStrokedEllipse1Bounds.left, 5, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().left"); + isfuzzy(nonScalingStrokedEllipse1Bounds.top, 40, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().top"); + isfuzzy(nonScalingStrokedEllipse1Bounds.width, 30, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().width"); + isfuzzy(nonScalingStrokedEllipse1Bounds.height, 40, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().height"); + + var nonScalingStrokedLine1Bounds = + doc.getElementById("nonScalingStrokedLine1").getBoundingClientRect(); + isfuzzy(nonScalingStrokedLine1Bounds.left, 235, 0.1, + "nonScalingStrokedLine1.getBoundingClientRect().left"); + isfuzzy(nonScalingStrokedLine1Bounds.top, 10, 0.1, + "nonScalingStrokedLine1.getBoundingClientRect().top"); + isfuzzy(nonScalingStrokedLine1Bounds.width, 10, 0.1, + "nonScalingStrokedLine1.getBoundingClientRect().width"); + isfuzzy(nonScalingStrokedLine1Bounds.height, 25, 0.1, + "nonScalingStrokedLine1.getBoundingClientRect().height"); + + var nonScalingStrokedLine2Bounds = + doc.getElementById("nonScalingStrokedLine2").getBoundingClientRect(); + var capDelta = 5 / Math.SQRT2 + 5 / Math.SQRT2; + rect = new Rect(260 - capDelta, 15 - capDelta, 20 / Math.SQRT2 + 2 * capDelta, + 20 / Math.SQRT2 + 2 * capDelta); + isfuzzy(nonScalingStrokedLine2Bounds.left, rect.left, 0.1, + "nonScalingStrokedLine2.getBoundingClientRect().left"); + isfuzzy(nonScalingStrokedLine2Bounds.top, rect.top, 0.1, + "nonScalingStrokedLine2.getBoundingClientRect().top"); + isfuzzy(nonScalingStrokedLine2Bounds.width, rect.width, 0.15, + "nonScalingStrokedLine2.getBoundingClientRect().width"); + isfuzzy(nonScalingStrokedLine2Bounds.height, rect.height, 0.15, + "nonScalingStrokedLine2.getBoundingClientRect().height"); + + var nonScalingStrokedLine3Bounds = + doc.getElementById("nonScalingStrokedLine3").getBoundingClientRect(); + capDelta = 5 / Math.SQRT2; + rect = new Rect(280 - capDelta, 15 - capDelta, 20 / Math.SQRT2 + 2 * capDelta, + 20 / Math.SQRT2 + 2 * capDelta); + isfuzzy(nonScalingStrokedLine3Bounds.left, rect.left, 0.1, + "nonScalingStrokedLine3.getBoundingClientRect().left"); + isfuzzy(nonScalingStrokedLine3Bounds.top, rect.top, 0.1, + "nonScalingStrokedLine3.getBoundingClientRect().top"); + isfuzzy(nonScalingStrokedLine3Bounds.width, rect.width, 0.1, + "nonScalingStrokedLine3.getBoundingClientRect().width"); + isfuzzy(nonScalingStrokedLine3Bounds.height, rect.height, 0.1, + "nonScalingStrokedLine3.getBoundingClientRect().height"); + + var shapeWithMarker1Bounds = + doc.getElementById("shapeWithMarker1").getBoundingClientRect(); + isfuzzy(shapeWithMarker1Bounds.left, 160, 0.1, + "shapeWithMarker1Bounds.left"); + isfuzzy(shapeWithMarker1Bounds.top, 120, 0.1, + "shapeWithMarker1Bounds.top"); + isfuzzy(shapeWithMarker1Bounds.width, 10 + Math.SQRT2 * 50, 0.1, + "shapeWithMarker1Bounds.width"); + isfuzzy(shapeWithMarker1Bounds.height, 20, 0.1, + "shapeWithMarker1Bounds.height"); + + var rotatedLine1Bounds = + doc.getElementById("rotatedLine1").getBoundingClientRect(); + isfuzzy(rotatedLine1Bounds.left, 160, 0.1, "rotatedLine1Bounds.left"); + isfuzzy(rotatedLine1Bounds.top, 145, 0.1, "rotatedLine1Bounds.top"); + isfuzzy(rotatedLine1Bounds.width, Math.SQRT2 * 20, 0.1, + "rotatedLine1Bounds.width"); + isfuzzy(rotatedLine1Bounds.height, 10, 0.1, "rotatedLine1Bounds.height"); + + var cssTransRect2Bounds = + doc.getElementById("css-trans-rect-2").getBoundingClientRect(); + is(cssTransRect2Bounds.left, 10, "cssTransRect2Bounds.left"); + is(cssTransRect2Bounds.top, 5, "cssTransRect2Bounds.top"); + is(cssTransRect2Bounds.width, 12, "cssTransRect2Bounds.width"); + is(cssTransRect2Bounds.height, 6, "cssTransRect2Bounds.height"); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTest); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_bug1426594.html b/dom/svg/test/test_bug1426594.html new file mode 100644 index 0000000000..ac975093c8 --- /dev/null +++ b/dom/svg/test/test_bug1426594.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1426594 +--> +<head> + <title>Test for Bug 1426594</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + function runTests() { + let textElement = document.getElementById("textId"), + tspanElement = document.getElementById("tspanId"); + + isfuzzy(textElement.getBoundingClientRect().width, tspanElement.getBoundingClientRect().width, 5); + isfuzzy(textElement.getBoundingClientRect().height, tspanElement.getBoundingClientRect().height, 5); + + SimpleTest.finish(); + } + </script> +</head> +<body onload="runTests()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589640">Mozilla Bug 1426594</a> +<svg height="1.5em" width="200px"> +<text id="textId" y="1em"><tspan id="tspanId">ABCDEF</tspan></text> +</svg> +<div style="pointer-events: none; border: 1px solid red; position: absolute; + z-index: 1" + id="highlight"> +</div> +</body> +</html> diff --git a/dom/svg/test/test_bug872812.html b/dom/svg/test/test_bug872812.html new file mode 100644 index 0000000000..5369504c8f --- /dev/null +++ b/dom/svg/test/test_bug872812.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=872812 +--> +<head> + <title>Test for Bug 872812</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=872812">Mozilla Bug 872812</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="viewport-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> + +var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); +ok(svg, "SVG exists"); +var a = document.createEvent("CustomEvent").initCustomEvent("", "", "", svg.viewBox); +ok(true, "CustomEvent exists and we are not crashed!"); + +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_dataTypes.html b/dom/svg/test/test_dataTypes.html new file mode 100644 index 0000000000..a02a52e2a1 --- /dev/null +++ b/dom/svg/test/test_dataTypes.html @@ -0,0 +1,377 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=437448 +--> +<head> + <title>Test for Bug 437448</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437448">Mozilla Bug 437448</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="dataTypes-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTests() { + var doc = $("svg").contentWindow.document; + var filter = doc.getElementById("filter"); + var convolve = doc.getElementById("convolve"); + var blur = doc.getElementById("blur"); + var marker = doc.getElementById("marker"); + + // class attribute + filter.setAttribute("class", "foo"); + is(filter.getAttribute("class"), "foo", "class attribute"); + is(filter.className.baseVal, "foo", "className baseVal"); + is(filter.className.animVal, "foo", "className animVal"); + filter.className.baseVal = "bar"; + is(filter.getAttribute("class"), "bar", "class attribute"); + is(filter.className.baseVal, "bar", "className baseVal"); + is(filter.className.animVal, "bar", "className animVal"); + filter.removeAttribute("class"); + is(filter.hasAttribute("class"), false, "class attribute"); + ok(filter.getAttribute("class") === null, "removed class attribute"); + is(filter.className.baseVal, "", "className baseVal"); + is(filter.className.animVal, "", "className animVal"); + filter.setAttribute("class", ""); + ok(filter.getAttribute("class") === "", "empty class attribute"); + + // length attribute + + marker.setAttribute("markerWidth", "12.5"); + is(marker.markerWidth.baseVal.value, 12.5, "length baseVal"); + is(marker.markerWidth.animVal.value, 12.5, "length animVal"); + + var baseMarkerWidth = marker.markerWidth.baseVal; + var animMarkerWidth = marker.markerWidth.animVal; + marker.setAttribute("markerWidth", "15.5"); + is(baseMarkerWidth.value, 15.5, "length baseVal"); + is(animMarkerWidth.value, 15.5, "length animVal"); + + marker.markerWidth.baseVal.value = 7.5; + is(marker.markerWidth.animVal.value, 7.5, "length animVal"); + is(marker.getAttribute("markerWidth"), "7.5", "length attribute"); + + marker.setAttribute("markerWidth", ""); + ok(marker.getAttribute("markerWidth") === "", "empty length attribute"); + marker.removeAttribute("markerWidth"); + ok(marker.getAttribute("markerWidth") === null, "removed length attribute"); + + // number attribute + + convolve.setAttribute("divisor", "12.5"); + is(convolve.divisor.baseVal, 12.5, "number baseVal"); + is(convolve.divisor.animVal, 12.5, "number animVal"); + + convolve.divisor.baseVal = 7.5; + is(convolve.divisor.animVal, 7.5, "number animVal"); + is(convolve.getAttribute("divisor"), "7.5", "number attribute"); + + convolve.setAttribute("divisor", ""); + ok(convolve.getAttribute("divisor") === "", "empty number attribute"); + convolve.removeAttribute("divisor"); + ok(convolve.getAttribute("divisor") === null, "removed number attribute"); + + // number-optional-number attribute + + blur.setAttribute("stdDeviation", "20.5"); + is(blur.stdDeviationX.baseVal, 20.5, "number-optional-number first baseVal"); + is(blur.stdDeviationX.animVal, 20.5, "number-optional-number first animVal"); + is(blur.stdDeviationY.baseVal, 20.5, "number-optional-number second baseVal"); + is(blur.stdDeviationY.animVal, 20.5, "number-optional-number second animVal"); + + blur.stdDeviationX.baseVal = 8.5; + is(blur.stdDeviationX.animVal, 8.5, "number-optional-number first animVal"); + is(blur.stdDeviationY.animVal, 20.5, "number-optional-number second animVal"); + is(blur.getAttribute("stdDeviation"), "8.5, 20.5", "number-optional-number attribute"); + + blur.stdDeviationY.baseVal = 8.5; + is(blur.getAttribute("stdDeviation"), "8.5", "number-optional-number attribute"); + + blur.setStdDeviation(24.5, 0.5); + is(blur.stdDeviationX.baseVal, 24.5, "number-optional-number first baseVal"); + is(blur.stdDeviationX.animVal, 24.5, "number-optional-number first animVal"); + is(blur.stdDeviationY.baseVal, 0.5, "number-optional-number second baseVal"); + is(blur.stdDeviationY.animVal, 0.5, "number-optional-number second animVal"); + + blur.setAttribute("stdDeviation", ""); + ok(blur.getAttribute("stdDeviation") === "", + "empty number-optional-number attribute"); + blur.removeAttribute("stdDeviation"); + ok(blur.getAttribute("stdDeviation") === null, + "removed number-optional-number attribute"); + + // integer attribute + + convolve.setAttribute("targetX", "12"); + is(convolve.targetX.baseVal, 12, "integer baseVal"); + is(convolve.targetX.animVal, 12, "integer animVal"); + convolve.targetX.baseVal = 7; + is(convolve.targetX.animVal, 7, "integer animVal"); + is(convolve.getAttribute("targetX"), "7", "integer attribute"); + convolve.setAttribute("targetX", ""); + ok(convolve.getAttribute("targetX") === "", "empty integer attribute"); + convolve.removeAttribute("targetX"); + ok(convolve.getAttribute("targetX") === null, "removed integer attribute"); + + // integer-optional-integer attribute + + convolve.setAttribute("order", "5"); + is(convolve.orderX.baseVal, 5, "integer-optional-integer first baseVal"); + is(convolve.orderX.animVal, 5, "integer-optional-integer first animVal"); + is(convolve.orderY.baseVal, 5, "integer-optional-integer second baseVal"); + is(convolve.orderY.animVal, 5, "integer-optional-integer second animVal"); + + convolve.orderX.baseVal = 7; + is(convolve.orderX.animVal, 7, "integer-optional-integer first animVal"); + is(convolve.orderY.animVal, 5, "integer-optional-integer second animVal"); + is(convolve.getAttribute("order"), "7, 5", "integer-optional-integer attribute"); + + convolve.orderY.baseVal = 7; + is(convolve.getAttribute("order"), "7", "integer-optional-integer attribute"); + + convolve.setAttribute("order", "11, 13"); + is(convolve.orderX.baseVal, 11, "integer-optional-integer first baseVal"); + is(convolve.orderX.animVal, 11, "integer-optional-integer first animVal"); + is(convolve.orderY.baseVal, 13, "integer-optional-integer second baseVal"); + is(convolve.orderY.animVal, 13, "integer-optional-integer second animVal"); + + // 32 bit integer range + convolve.setAttribute("order", "-2147483648, 2147483647"); + is(convolve.orderX.baseVal, -2147483648, "integer-optional-integer first baseVal"); + is(convolve.orderX.animVal, -2147483648, "integer-optional-integer first animVal"); + is(convolve.orderY.baseVal, 2147483647, "integer-optional-integer second baseVal"); + is(convolve.orderY.animVal, 2147483647, "integer-optional-integer second animVal"); + + // too big, clamp + convolve.setAttribute("order", "-2147483649, 2147483648"); + is(convolve.orderX.baseVal, -2147483648, "integer-optional-integer first baseVal"); + is(convolve.orderX.animVal, -2147483648, "integer-optional-integer first animVal"); + is(convolve.orderY.baseVal, 2147483647, "integer-optional-integer second baseVal"); + is(convolve.orderY.animVal, 2147483647, "integer-optional-integer second animVal"); + + // invalid + convolve.setAttribute("order", "-00000000000invalid, 214748364720invalid"); + is(convolve.orderX.baseVal, 3, "integer-optional-integer first baseVal"); + is(convolve.orderX.animVal, 3, "integer-optional-integer first animVal"); + is(convolve.orderY.baseVal, 3, "integer-optional-integer second baseVal"); + is(convolve.orderY.animVal, 3, "integer-optional-integer second animVal"); + + convolve.setAttribute("order", ""); + ok(convolve.getAttribute("order") === "", + "empty integer-optional-integer attribute"); + convolve.removeAttribute("order"); + ok(convolve.getAttribute("order") === null, + "removed integer-optional-integer attribute"); + + // angle attribute + + marker.setAttribute("orient", "90deg"); + is(marker.orientAngle.baseVal.value, 90, "angle baseVal"); + is(marker.orientAngle.animVal.value, 90, "angle animVal"); + + var baseAngle = marker.orientAngle.baseVal; + var animAngle = marker.orientAngle.animVal; + marker.setAttribute("orient", "45deg"); + is(baseAngle.value, 45, "angle baseVal"); + is(animAngle.value, 45, "angle animVal"); + + marker.orientAngle.baseVal.value = 30; + is(marker.orientAngle.animVal.value, 30, "angle animVal"); + is(marker.getAttribute("orient"), "30deg", "angle attribute"); + + marker.setAttribute("orient", "auto"); + is(marker.getAttribute("orient"), "auto", "checking 'auto' string preserved"); + is(marker.orientType.baseVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO, "type baseVal"); + is(marker.orientType.animVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO, "type animVal"); + + marker.setAttribute("orient", "auto-start-reverse"); + is(marker.getAttribute("orient"), "auto-start-reverse", "checking 'auto-start-reverse' string preserved"); + is(marker.orientType.baseVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO_START_REVERSE, "type baseVal"); + is(marker.orientType.animVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO_START_REVERSE, "type animVal"); + + marker.setAttribute("orient", ""); + ok(marker.getAttribute("orient") === "", "empty angle attribute"); + marker.removeAttribute("orient"); + ok(marker.getAttribute("orient") === null, "removed angle attribute"); + + // boolean attribute + + convolve.setAttribute("preserveAlpha", "false"); + is(convolve.preserveAlpha.baseVal, false, "boolean baseVal"); + is(convolve.preserveAlpha.animVal, false, "boolean animVal"); + convolve.preserveAlpha.baseVal = true; + is(convolve.preserveAlpha.animVal, true, "boolean animVal"); + is(convolve.getAttribute("preserveAlpha"), "true", "boolean attribute"); + convolve.setAttribute("preserveAlpha", ""); + ok(convolve.getAttribute("preserveAlpha") === "", "empty boolean attribute"); + convolve.removeAttribute("preserveAlpha"); + ok(convolve.getAttribute("preserveAlpha") === null, + "removed boolean attribute"); + + // enum attribute + + is(1, SVGFEConvolveMatrixElement.SVG_EDGEMODE_DUPLICATE, "SVG_EDGEMODE_DUPLICATE value"); + is(2, SVGFEConvolveMatrixElement.SVG_EDGEMODE_WRAP, "SVG_EDGEMODE_WRAP value"); + + convolve.setAttribute("edgeMode", "wrap"); + is(convolve.edgeMode.baseVal, SVGFEConvolveMatrixElement.SVG_EDGEMODE_WRAP, "enum baseVal"); + is(convolve.edgeMode.animVal, SVGFEConvolveMatrixElement.SVG_EDGEMODE_WRAP, "enum animVal"); + convolve.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_DUPLICATE; + is(convolve.edgeMode.animVal, SVGFEConvolveMatrixElement.SVG_EDGEMODE_DUPLICATE, "enum animVal"); + is(convolve.getAttribute("edgeMode"), "duplicate", "enum attribute"); + convolve.setAttribute("edgeMode", ""); + ok(convolve.getAttribute("edgeMode") === "", "empty enum attribute"); + convolve.removeAttribute("edgeMode"); + ok(convolve.getAttribute("edgeMode") === null, "removed enum attribute"); + + // string attribute + + convolve.setAttribute("result", "foo"); + is(convolve.result.baseVal, "foo", "string baseVal"); + is(convolve.result.animVal, "foo", "string animVal"); + convolve.result.baseVal = "bar"; + is(convolve.result.animVal, "bar", "string animVal"); + is(convolve.getAttribute("result"), "bar", "string attribute"); + convolve.setAttribute("result", ""); + ok(convolve.getAttribute("result") === "", "empty string attribute"); + convolve.removeAttribute("result"); + ok(convolve.getAttribute("result") === null, "removed string attribute"); + + // preserveAspectRatio attribute + + is(0, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_UNKNOWN, "SVG_PRESERVEASPECTRATIO_UNKNOWN value"); + is(1, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_NONE, "SVG_PRESERVEASPECTRATIO_NONE value"); + is(3, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN, "SVG_PRESERVEASPECTRATIO_XMIDYMIN value"); + is(5, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID, "SVG_PRESERVEASPECTRATIO_XMINYMID value"); + is(7, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID, "SVG_PRESERVEASPECTRATIO_XMAXYMID value"); + is(10, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMAX, "SVG_PRESERVEASPECTRATIO_XMAXYMAX value"); + + is(0, SVGPreserveAspectRatio.SVG_MEETORSLICE_UNKNOWN, "SVG_MEETORSLICE_UNKNOWN value"); + is(1, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "SVG_MEETORSLICE_MEET value"); + is(2, SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, "SVG_MEETORSLICE_SLICE value"); + + marker.setAttribute("preserveAspectRatio", "xMinYMid slice"); + is(marker.preserveAspectRatio.baseVal.align, + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID, "preserveAspectRatio.align baseVal"); + is(marker.preserveAspectRatio.animVal.align, + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID, "preserveAspectRatio.align animVal"); + is(marker.preserveAspectRatio.baseVal.meetOrSlice, + SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, "preserveAspectRatio.meetOrSlice baseVal"); + is(marker.preserveAspectRatio.animVal.meetOrSlice, + SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, "preserveAspectRatio.meetOrSlice animVal"); + marker.preserveAspectRatio.baseVal.align = + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN; + is(marker.preserveAspectRatio.animVal.align, + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN, "preserveAspectRatio animVal"); + is(marker.preserveAspectRatio.animVal.meetOrSlice, + SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, "preserveAspectRatio.meetOrSlice animVal"); + marker.preserveAspectRatio.baseVal.meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET; + is(marker.preserveAspectRatio.animVal.align, + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN, "preserveAspectRatio animVal"); + is(marker.preserveAspectRatio.animVal.meetOrSlice, + SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "preserveAspectRatio.meetOrSlice animVal"); + is(marker.getAttribute("preserveAspectRatio"), "xMidYMin meet", "preserveAspectRatio attribute"); + + var basePreserveAspectRatio = marker.preserveAspectRatio.baseVal; + var animPreserveAspectRatio = marker.preserveAspectRatio.animVal; + marker.setAttribute("preserveAspectRatio", "xMaxYMid slice"); + is(basePreserveAspectRatio.align, + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID, "preserveAspectRatio.align baseVal"); + is(animPreserveAspectRatio.align, + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID, "preserveAspectRatio.align animVal"); + is(basePreserveAspectRatio.meetOrSlice, + SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, "preserveAspectRatio.meetOrSlice baseVal"); + is(animPreserveAspectRatio.meetOrSlice, + SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, "preserveAspectRatio.meetOrSlice animVal"); + + marker.setAttribute("preserveAspectRatio", " none"); // invalid, space at beginning + is(basePreserveAspectRatio.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID, + "default preserveAspectRatio attribute"); + + marker.setAttribute("preserveAspectRatio", "none "); // invalid, space at end + is(basePreserveAspectRatio.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID, + "default preserveAspectRatio attribute"); + + marker.setAttribute("preserveAspectRatio", ""); + ok(marker.getAttribute("preserveAspectRatio") === "", + "empty preserveAspectRatio attribute"); + marker.removeAttribute("preserveAspectRatio"); + ok(marker.getAttribute("preserveAspectRatio") === null, + "removed preserveAspectRatio attribute"); + + // viewBox attribute + var baseViewBox = marker.viewBox.baseVal; + var animViewBox = marker.viewBox.animVal; + is(baseViewBox, null, "viewBox baseVal"); + is(animViewBox, null, "viewBox animVal"); + + marker.setAttribute("viewBox", "1 2 3 4"); + is(marker.viewBox.baseVal.x, 1, "viewBox.x baseVal"); + is(marker.viewBox.animVal.x, 1, "viewBox.x animVal"); + is(marker.viewBox.baseVal.y, 2, "viewbox.y baseVal"); + is(marker.viewBox.animVal.y, 2, "viewbox.y animVal"); + is(marker.viewBox.baseVal.width, 3, "viewbox.width baseVal"); + is(marker.viewBox.animVal.width, 3, "viewbox.width animVal"); + is(marker.viewBox.baseVal.height, 4, "viewbox.height baseVal"); + is(marker.viewBox.animVal.height, 4, "viewbox.height animVal"); + marker.viewBox.baseVal.x = 5; + is(marker.viewBox.animVal.x, 5, "viewBox.x animVal"); + marker.viewBox.baseVal.y = 6; + is(marker.viewBox.animVal.y, 6, "viewBox.y animVal"); + marker.viewBox.baseVal.width = 7; + is(marker.viewBox.animVal.width, 7, "viewBox.width animVal"); + marker.viewBox.baseVal.height = 8; + is(marker.viewBox.animVal.height, 8, "viewBox.height animVal"); + is(marker.getAttribute("viewBox"), "5 6 7 8", "viewBox attribute"); + var storedViewBox = marker.viewBox.baseVal; + marker.removeAttribute("viewBox"); + is(marker.hasAttribute("viewBox"), false, "viewBox hasAttribute"); + ok(marker.getAttribute("viewBox") === null, "removed viewBox attribute"); + is(marker.viewBox.baseVal, null, "viewBox baseVal"); + is(marker.viewBox.animVal, null, "viewBox animVal"); + + is(storedViewBox.width, 7, "Should not lose values"); + storedViewBox.width = 200; + is(storedViewBox.width, 200, "Should be able to change detached viewBox rect"); + is(marker.hasAttribute("viewBox"), false, "viewBox hasAttribute should still be false"); + ok(marker.getAttribute("viewBox") === null, "viewBox attribute should still be null"); + is(marker.viewBox.baseVal, null, "viewBox baseVal"); + is(marker.viewBox.animVal, null, "viewBox animVal"); + + marker.setAttribute("viewBox", "none"); + is(marker.hasAttribute("viewBox"), true, "viewBox hasAttribute"); + is(marker.viewBox.baseVal, null, "viewBox baseVal"); + is(marker.viewBox.animVal, null, "viewBox animVal"); + + marker.setAttribute("viewBox", ""); + is(marker.hasAttribute("viewBox"), true, "viewBox hasAttribute"); + ok(marker.getAttribute("viewBox") === "", "empty viewBox attribute"); + + marker.setAttribute("viewBox", "9 10 11 12"); + marker.removeAttribute("viewBox"); + marker.setAttribute("viewBox", "9 10 11 12"); + is(marker.viewBox.baseVal.x, 9, "viewBox.x baseVal after re-setting attribute to same rect value"); + is(marker.viewBox.baseVal.y, 10, "viewBox.y baseVal after re-setting attribute to same rect value"); + is(marker.viewBox.baseVal.width, 11, "viewBox.width baseVal after re-setting attribute to same rect value"); + is(marker.viewBox.baseVal.height, 12, "viewBox.height baseVal after re-setting attribute to same rect value"); + is(marker.viewBox.animVal.x, 9, "viewBox.x animVal after re-setting attribute to same rect value"); + is(marker.viewBox.animVal.y, 10, "viewBox.y animVal after re-setting attribute to same rect value"); + is(marker.viewBox.animVal.width, 11, "viewBox.width animVal after re-setting attribute to same rect value"); + is(marker.viewBox.animVal.height, 12, "viewBox.height animVal after re-setting attribute to same rect value"); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTests); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_dataTypesModEvents.html b/dom/svg/test/test_dataTypesModEvents.html new file mode 100644 index 0000000000..e85165db44 --- /dev/null +++ b/dom/svg/test/test_dataTypesModEvents.html @@ -0,0 +1,257 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=629200 +--> +<head> + <title>Test for Bug 629200</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="MutationEventChecker.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=629200">Mozilla + Bug 629200</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="dataTypes-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTests() { + var doc = $("svg").contentWindow.document; + var filter = doc.getElementById("filter"); + var convolve = doc.getElementById("convolve"); + var blur = doc.getElementById("blur"); + var marker = doc.getElementById("marker"); + var eventChecker = new MutationEventChecker; + + // class attribute + + eventChecker.watchAttr(filter, "class"); + eventChecker.expect("add modify remove add"); + filter.setAttribute("class", "foo"); + filter.className.baseVal = "bar"; + filter.removeAttribute("class"); + filter.removeAttributeNS(null, "class"); + filter.className.baseVal = "foo"; + + eventChecker.expect(""); + filter.className.baseVal = "foo"; + filter.setAttribute("class", "foo"); + + // length attribute + + eventChecker.watchAttr(marker, "markerWidth"); + eventChecker.expect("add modify modify modify modify modify remove add"); + marker.setAttribute("markerWidth", "12.5"); + marker.markerWidth.baseVal.value = 8; + marker.markerWidth.baseVal.valueInSpecifiedUnits = 9; + marker.markerWidth.baseVal.valueAsString = "10"; + marker.markerWidth.baseVal.convertToSpecifiedUnits( + SVGLength.SVG_LENGTHTYPE_CM); + marker.markerWidth.baseVal.newValueSpecifiedUnits( + SVGLength.SVG_LENGTHTYPE_MM, 11); + marker.removeAttribute("markerWidth"); + marker.removeAttributeNS(null, "markerWidth"); + marker.markerWidth.baseVal.value = 8; + + eventChecker.expect("remove add modify"); + marker.removeAttribute("markerWidth"); + console.log(marker.getAttribute("markerWidth")); + marker.markerWidth.baseVal.convertToSpecifiedUnits( + SVGLength.SVG_LENGTHTYPE_NUMBER); + console.log(marker.getAttribute("markerWidth")); + marker.markerWidth.baseVal.value = 8; + + eventChecker.expect(""); + marker.markerWidth.baseVal.value = 8; + marker.setAttribute("markerWidth", "8"); + marker.markerWidth.baseVal.value = 8; + marker.markerWidth.baseVal.valueAsString = "8"; + marker.markerWidth.baseVal.convertToSpecifiedUnits( + SVGLength.SVG_LENGTHTYPE_NUMBER); + marker.markerWidth.baseVal.newValueSpecifiedUnits( + SVGLength.SVG_LENGTHTYPE_NUMBER, 8); + + // number attribute + + eventChecker.watchAttr(convolve, "divisor"); + eventChecker.expect("add modify remove add"); + convolve.setAttribute("divisor", "12.5"); + convolve.divisor.baseVal = 6; + convolve.removeAttribute("divisor"); + convolve.removeAttributeNS(null, "divisor"); + convolve.divisor.baseVal = 8; + + eventChecker.expect(""); + convolve.divisor.baseVal = 8; + convolve.setAttribute("divisor", "8"); + + // number-optional-number attribute + + eventChecker.watchAttr(blur, "stdDeviation"); + eventChecker.expect("add modify remove add"); + blur.setAttribute("stdDeviation", "5, 6"); + blur.stdDeviationX.baseVal = 8; + blur.removeAttribute("stdDeviation"); + blur.removeAttributeNS(null, "stdDeviation"); + blur.setAttribute("stdDeviation", "2, 3"); + + eventChecker.expect(""); + blur.stdDeviationX.baseVal = 2; + blur.stdDeviationY.baseVal = 3; + blur.setAttribute("stdDeviation", "2, 3"); + + // integer attribute + + eventChecker.watchAttr(convolve, "targetX"); + eventChecker.expect("add modify remove add"); + convolve.setAttribute("targetX", "12"); + convolve.targetX.baseVal = 6; + convolve.removeAttribute("targetX"); + convolve.removeAttributeNS(null, "targetX"); + convolve.targetX.baseVal = 8; + + // Check redundant change when comparing typed value to attribute value + eventChecker.expect(""); + convolve.setAttribute("targetX", "8"); + // Check redundant change when comparing attribute value to typed value + eventChecker.expect("remove add"); + convolve.removeAttribute("targetX"); + convolve.setAttribute("targetX", "8"); + convolve.targetX.baseVal = 8; + + // integer-optional-integer attribute + + eventChecker.watchAttr(convolve, "order"); + eventChecker.expect("add modify remove add"); + convolve.setAttribute("order", "5, 7"); + convolve.orderX.baseVal = 9; + convolve.removeAttribute("order"); + convolve.removeAttributeNS(null, "order"); + convolve.setAttribute("order", "9, 5"); + + eventChecker.expect(""); + convolve.orderX.baseVal = 9; + convolve.setAttribute("order", "9, 5"); + convolve.orderY.baseVal = 5; + + // angle attribute + + eventChecker.watchAttr(marker, "orient"); + eventChecker.expect("add modify modify modify modify modify remove add"); + marker.setAttribute("orient", "90deg"); + marker.orientAngle.baseVal.value = 12; + marker.orientAngle.baseVal.valueInSpecifiedUnits = 23; + marker.orientAngle.baseVal.valueAsString = "34"; + marker.orientAngle.baseVal.newValueSpecifiedUnits( + SVGAngle.SVG_ANGLETYPE_GRAD, 34); + marker.orientAngle.baseVal.newValueSpecifiedUnits( + SVGAngle.SVG_ANGLETYPE_GRAD, 45); + marker.removeAttribute("orient"); + marker.removeAttributeNS(null, "orient"); + marker.orientAngle.baseVal.value = 40; + + eventChecker.expect(""); + marker.orientAngle.baseVal.value = 40; + marker.orientAngle.baseVal.valueInSpecifiedUnits = 40; + marker.orientAngle.baseVal.valueAsString = "40"; + marker.orientAngle.baseVal.convertToSpecifiedUnits( + SVGAngle.SVG_ANGLETYPE_UNSPECIFIED); + marker.orientAngle.baseVal.newValueSpecifiedUnits( + SVGAngle.SVG_ANGLETYPE_UNSPECIFIED, 40); + + // boolean attribute + + eventChecker.watchAttr(convolve, "preserveAlpha"); + eventChecker.expect("add modify remove add"); + convolve.setAttribute("preserveAlpha", "true"); + convolve.preserveAlpha.baseVal = false; + convolve.removeAttribute("preserveAlpha"); + convolve.removeAttributeNS(null, "preserveAlpha"); + convolve.preserveAlpha.baseVal = true; + + eventChecker.expect(""); + convolve.preserveAlpha.baseVal = true; + convolve.setAttribute("preserveAlpha", "true"); + + // enum attribute + + eventChecker.watchAttr(convolve, "edgeMode"); + eventChecker.expect("add modify remove add"); + convolve.setAttribute("edgeMode", "none"); + convolve.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_WRAP; + convolve.removeAttribute("edgeMode"); + convolve.removeAttributeNS(null, "edgeMode"); + convolve.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE; + + eventChecker.expect(""); + convolve.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE; + convolve.setAttribute("edgeMode", "none"); + convolve.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE; + + // string attribute + + eventChecker.watchAttr(convolve, "result"); + eventChecker.expect("add modify remove add"); + convolve.setAttribute("result", "bar"); + convolve.result.baseVal = "foo"; + convolve.removeAttribute("result"); + convolve.removeAttributeNS(null, "result"); + convolve.result.baseVal = "bar"; + + eventChecker.expect(""); + convolve.result.baseVal = "bar"; + convolve.setAttribute("result", "bar"); + convolve.result.baseVal = "bar"; + + // preserveAspectRatio attribute + + eventChecker.watchAttr(marker, "preserveAspectRatio"); + eventChecker.expect("add modify remove add"); + marker.setAttribute("preserveAspectRatio", "xMaxYMid slice"); + marker.preserveAspectRatio.baseVal.align = + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMAX; + marker.removeAttribute("preserveAspectRatio"); + marker.removeAttributeNS(null, "preserveAspectRatio"); + marker.preserveAspectRatio.baseVal.align = + SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN; + + eventChecker.expect(""); + marker.preserveAspectRatio.baseVal.meetOrSlice = + SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET; + marker.setAttribute("preserveAspectRatio", "xMidYMin meet"); + + // viewBox attribute + + eventChecker.watchAttr(marker, "viewBox"); + eventChecker.expect("add modify remove add"); + marker.setAttribute("viewBox", "1 2 3 4"); + marker.viewBox.baseVal.height = 5; + marker.removeAttribute("viewBox"); + marker.removeAttributeNS(null, "viewBox"); + marker.setAttribute("viewBox", "none"); + marker.setAttribute("viewBox", "none"); + + eventChecker.ignoreEvents(); + marker.setAttribute("viewBox", "1 2 3 4"); + eventChecker.expect(""); + marker.viewBox.baseVal.height = 4; + marker.viewBox.baseVal.x = 1; + marker.setAttribute("viewBox", "1 2 3 4"); + + eventChecker.finish(); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTests); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_fragments.html b/dom/svg/test/test_fragments.html new file mode 100644 index 0000000000..b12833e899 --- /dev/null +++ b/dom/svg/test/test_fragments.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=759124 +--> +<head> + <title>Test for Bug 759124</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=759124">Mozilla Bug 759124</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +var svg = $("svg"); + +SimpleTest.waitForExplicitFinish(); + +function Test(svgFragmentIdentifier, viewBoxString, + preserveAspectRatioString, zoomAndPanString) { + this.svgFragmentIdentifier = svgFragmentIdentifier; +} + +function runTests() { + var doc = svg.contentWindow.document; + var rootElement = doc.documentElement; + + var tests = [ + new Test("svgView(viewBox(0,0,200,200))"), + new Test("svgView(preserveAspectRatio(xMaxYMin slice))"), + new Test("svgView(viewBox(1,2,3,4);preserveAspectRatio(xMinYMax))"), + new Test("svgView(viewBox(none))"), + new Test("svgView(zoomAndPan(disable))"), + new Test("svgView(transform(translate(-10,-20) scale(2) rotate(45) translate(5,10)))"), + ]; + + var src = svg.getAttribute("src"); + + is(false, rootElement.hasAttribute("viewBox"), + "expecting to start without a viewBox set"); + is(false, rootElement.hasAttribute("preserveAspectRatio"), + "expecting to start without preserveAspectRatio set"); + is(false, rootElement.hasAttribute("zoomAndPan"), + "expecting to start without zoomAndPan set"); + + for (var j = 0; j < 2; j++) { + var initialViewBox = rootElement.getAttribute("viewBox"); + var initialPreserveAspectRatio = + rootElement.getAttribute("preserveAspectRatio"); + var initialZoomAndPan = rootElement.getAttribute("zoomAndPan"); + var initialTransform = rootElement.getAttribute("transform"); + + for (var i = 0; i < tests.length; i++) { + var test = tests[i]; + svg.setAttribute("src", src + "#" + test.svgFragmentIdentifier); + + // check that assigning a viewSpec does not modify the underlying + // attribute values. + is(rootElement.getAttribute("viewBox"), + initialViewBox, "unexpected viewBox"); + + is(rootElement.getAttribute("preserveAspectRatio"), + initialPreserveAspectRatio, "unexpected preserveAspectRatio"); + + is(rootElement.getAttribute("zoomAndPan"), + initialZoomAndPan, "unexpected zoomAndPan"); + + is(rootElement.getAttribute("transform"), + initialTransform, "unexpected transform"); + } + + // repeat tests with underlying attributes set to values + rootElement.setAttribute("viewBox", "0 0 100 100"); + rootElement.setAttribute("preserveAspectRatio", "none"); + rootElement.setAttribute("zoomAndPan", "disable"); + rootElement.setAttribute("transform", "translate(10,10)"); + } + + SimpleTest.finish(); +} + +svg.addEventListener("load", runTests); +svg.setAttribute("src", "fragments-helper.svg"); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_getBBox-method.html b/dom/svg/test/test_getBBox-method.html new file mode 100644 index 0000000000..bad8f1ffed --- /dev/null +++ b/dom/svg/test/test_getBBox-method.html @@ -0,0 +1,248 @@ +<!DOCTYPE HTML> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=999964 +--> +<head> + <meta charset="utf-8"/> + <title>Test case for Bug 999964</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> + +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=999964">Mozilla Bug 999964</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="getBBox-method-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> + + /** Test case for Bug 999964 **/ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var flag = SpecialPowers.getBoolPref("svg.new-getBBox.enabled"); + if (!flag) { + ok(!flag, "skip test for bug999964."); + SimpleTest.finish(); + return; + } + + var doc = $("svg").contentDocument; + + function isFuzzy(a, b, error, name) { + ok(!(Math.abs(a - b) > error), name, "got " + a + ", expected " + b + " (within " + error + ")"); + } + + function getBBox(id, opt) { + return doc.getElementById(id).getBBox(opt); + } + + function checkBBox(id, opt, x, y, width, height, error) { + var bbox = getBBox(id, opt); + isFuzzy(bbox.x, x, error, id + ".getBBox().x"); + isFuzzy(bbox.y, y, error, id + ".getBBox().y"); + isFuzzy(bbox.width, width, error, id + ".getBBox().width"); + isFuzzy(bbox.height, height, error, id + ".getBBox().height"); + } + + function compareBBox1(id1, id2) { + var bbox1 = getBBox(id1); + var bbox2 = getBBox(id2); + is(bbox1.x, bbox2.x, id1 + ".getBBox().x"); + is(bbox1.y, bbox2.y, id1 + ".getBBox().y"); + isFuzzy(bbox1.width, bbox2.width, 0.0002, id1 + ".getBBox().width"); + isFuzzy(bbox1.height, bbox2.height, 0.0001, id1 + ".getBBox().height"); + } + + function compareBBox2(id1, id2) { + // without 'x' + var bbox1 = getBBox(id1); + var bbox2 = getBBox(id2); + is(bbox1.y, bbox2.y, id1 + ".getBBox().y"); + isFuzzy(bbox1.width, bbox2.width, 0.0002, id1 + ".getBBox().width"); + isFuzzy(bbox1.height, bbox2.height, 0.0001, id1 + ".getBBox().height"); + } + + var opt = { fill: true, stroke: true, markers: true, clipped: true }; + + // <text> + // fill + opt = { fill: true, stroke: false, markers: false, clipped: false }; + compareBBox1("text1", "text3"); + compareBBox1("text2", "text4"); + compareBBox1("text5", "text6"); + // all + opt = { fill: true, stroke: true, markers: true, clipped: true }; + compareBBox2("text1", "text3"); + compareBBox2("text2", "text4"); + compareBBox2("text5", "text6"); + // clipped + opt = { fill: false, stroke: false, markers: false, clipped: true }; + compareBBox2("text1", "text3"); + compareBBox2("text2", "text4"); + compareBBox2("text5", "text6"); + + // <image> + opt = { fill: true, stroke: true, markers: true, clipped: true }; + checkBBox("image1", opt, 250, 250, 100, 100); + checkBBox("image2", opt, 53, 53, 149, 149); + checkBBox("image3", opt, 205, 53, 148, 149); + checkBBox("image4", opt, 53, 205, 149, 148); + checkBBox("image5", opt, 205, 205, 148, 148); + checkBBox("image6", opt, 52, 52, 100, 100); + checkBBox("image7", opt, 255, 52, 100, 100); + checkBBox("image8", opt, 52, 255, 100, 100); + checkBBox("image9", opt, 255, 255, 100, 100); + checkBBox("image10", opt, 200, 200, 200, 200); + checkBBox("image11", opt, 0, 0, 0, 0); + checkBBox("image12", opt, 43, 43, 714, 660); + checkBBox("image13", opt, 50, 50, 300, 300); + checkBBox("image14", opt, 0, 0, 0, 0); + + opt = { fill: true, stroke: false, markers: false, clipped: false }; + checkBBox("image1", opt, 150, 150, 200, 200, 0); + checkBBox("image2", opt, 2, 2, 200, 200, 0); + checkBBox("image3", opt, 205, 2, 200, 200, 0); + checkBBox("image4", opt, 2, 205, 200, 200, 0); + checkBBox("image5", opt, 205, 205, 200, 200, 0); + checkBBox("image6", opt, 2, 2, 200, 200, 0); + checkBBox("image7", opt, 205, 2, 200, 200, 0); + checkBBox("image8", opt, 2, 205, 200, 200, 0); + checkBBox("image9", opt, 205, 205, 200, 200, 0); + checkBBox("image10", opt, 0, 0, 400, 400, 0); + checkBBox("image11", opt, 0, 0, 400, 400, 0); + checkBBox("image12", opt, 25, 43, 768, 768, 0); + checkBBox("image13", opt, 0, 0, 400, 400, 0); + + // <path> + opt = { fill: true, stroke: true, markers: true, clipped: true }; + checkBBox("path1", opt, 2, 17, 120, 95, 0); + checkBBox("path2", opt, 156, 21, 116, 91, 0); + checkBBox("path3", opt, 6, 121, 116, 91, 0); + checkBBox("path4", opt, 2, 17, 98, 83, 0); + checkBBox("path5", opt, 156, 21, 44, 79, 0); + checkBBox("path6", opt, 6, 150, 94, 62, 0); + checkBBox("path7", opt, 2, 17, 98, 83, 0); + checkBBox("path8", opt, 156, 21, 94, 79, 0); + checkBBox("path9", opt, 6, 121, 94, 79, 0); + checkBBox("path10", opt, 10, 25, 100, 75, 0); + checkBBox("path11", opt, 160, 25, 100, 75, 0); + checkBBox("path12", opt, 10, 125, 100, 75, 0); + + opt = { fill: true, stroke: false, markers: false, clipped: true }; + checkBBox("path1", opt, 10, 25, 100, 75, 0); + checkBBox("path2", opt, 160, 25, 100, 75, 0); + checkBBox("path3", opt, 10, 125, 100, 75, 0); + checkBBox("path4", opt, 10, 25, 90, 75, 0); + checkBBox("path5", opt, 160, 25, 40, 75, 0); + checkBBox("path6", opt, 10, 150, 90, 50, 0); + checkBBox("path7", opt, 10, 25, 90, 75, 0); + checkBBox("path8", opt, 160, 25, 90, 75, 0); + checkBBox("path9", opt, 10, 125, 90, 75, 0); + checkBBox("path10", opt, 10, 25, 100, 75, 0); + checkBBox("path11", opt, 160, 25, 100, 75, 0); + checkBBox("path12", opt, 10, 125, 100, 75, 0); + + opt = { fill: true, stroke: false, markers: false, clipped: false }; + checkBBox("path1", opt, 10, 25, 100, 75, 0); + checkBBox("path2", opt, 160, 25, 100, 75, 0); + checkBBox("path3", opt, 10, 125, 100, 75, 0); + checkBBox("path4", opt, 10, 25, 100, 75, 0); + checkBBox("path5", opt, 160, 25, 100, 75, 0); + checkBBox("path6", opt, 10, 125, 100, 75, 0); + checkBBox("path7", opt, 10, 25, 100, 75, 0); + checkBBox("path8", opt, 160, 25, 100, 75, 0); + checkBBox("path9", opt, 10, 125, 100, 75, 0); + checkBBox("path10", opt, 10, 25, 100, 75, 0); + checkBBox("path11", opt, 160, 25, 100, 75, 0); + checkBBox("path12", opt, 10, 125, 100, 75, 0); + checkBBox("path13", opt, 0, 0, 100, 100, 0); + + opt = { fill: false, stroke: true, markers: false, clipped: false }; + checkBBox("path1", opt, 2, 17, 116, 91, 0); + checkBBox("path2", opt, 156, 21, 108, 83, 0); + checkBBox("path3", opt, 6, 121, 108, 83, 0); + checkBBox("path4", opt, 2, 17, 116, 91, 0); + checkBBox("path5", opt, 156, 21, 108, 83, 0); + checkBBox("path6", opt, 6, 121, 108, 83, 0); + checkBBox("path7", opt, 2, 17, 116, 91, 0); + checkBBox("path8", opt, 156, 21, 108, 83, 0); + checkBBox("path9", opt, 6, 121, 108, 83, 0); + checkBBox("path10", opt, 2, 17, 116, 91, 0); + checkBBox("path11", opt, 156, 21, 108, 83, 0); + checkBBox("path12", opt, 6, 121, 108, 83, 0); + + opt = { fill: false, stroke: false, markers: true, clipped: false }; + checkBBox("path1", opt, 10, 25, 112, 87, 0); + checkBBox("path2", opt, 160, 25, 112, 87, 0); + checkBBox("path3", opt, 10, 125, 112, 87, 0); + checkBBox("path4", opt, 10, 25, 112, 87, 0); + checkBBox("path5", opt, 160, 25, 112, 87, 0); + checkBBox("path6", opt, 10, 125, 112, 87, 0); + checkBBox("path7", opt, 10, 25, 112, 87, 0); + checkBBox("path8", opt, 160, 25, 112, 87, 0); + checkBBox("path9", opt, 10, 125, 112, 87, 0); + checkBBox("path10", opt, 10, 25, 112, 87, 0); + checkBBox("path11", opt, 160, 25, 112, 87, 0); + checkBBox("path12", opt, 10, 125, 112, 87, 0); + + // <use> + opt = { fill: true, stroke: false, markers: false, clipped: false }; + checkBBox("use1", opt, 70, 70, 180, 180, 0); + checkBBox("use2", opt, 250, 70, 180, 180, 0); + checkBBox("use3", opt, 70, 250, 180, 180, 0); + checkBBox("use4", opt, 22, 22, 180, 180, 0); + checkBBox("use5", opt, 225, 22, 180, 180, 0); + checkBBox("use6", opt, 22, 225, 180, 180, 0); + checkBBox("use7", opt, 225, 225, 180, 180, 0); + + opt = { fill: true, stroke: true, markers: true, clipped: true }; + checkBBox("use1", opt, 70, 66, 180, 94, 0); + checkBBox("use2", opt, 250, 70, 180, 90, 0); + checkBBox("use3", opt, 70, 250, 180, 90, 0); + checkBBox("use4", opt, 18, 18, 134, 134, 0); + checkBBox("use5", opt, 221, 18, 134, 134, 0); + checkBBox("use6", opt, 18, 221, 134, 134, 0); + checkBBox("use7", opt, 221, 221, 134, 134, 0); + checkBBox("use8", opt, 0, 0, 0, 0, 0); + + // <foreignObject> + opt = { fill: true, stroke: false, markers: false, clipped: false }; + checkBBox("fo1", opt, 2, 2, 200, 200, 0); + checkBBox("fo2", opt, 205, 2, 200, 200, 0); + checkBBox("fo3", opt, 2, 205, 200, 200, 0); + checkBBox("fo4", opt, 205, 205, 200, 200, 0); + checkBBox("fo5", opt, 250, 250, 200, 200, 0); + checkBBox("fo6", opt, 0, 0, 200, 200, 0); + checkBBox("fo7", opt, 0, 0, 200, 200, 0); + + opt = { fill: true, stroke: true, markers: true, clipped: true }; + checkBBox("fo1", opt, 53, 53, 51, 51, 0); + checkBBox("fo2", opt, 205, 53, 148, 149, 0); + checkBBox("fo3", opt, 53, 205, 149, 148, 0); + checkBBox("fo4", opt, 207, 207, 100, 100, 0); + checkBBox("fo5", opt, 0, 0, 0, 0, 0); + checkBBox("fo6", opt, 100, 100, 100, 100, 0); + checkBBox("fo7", opt, 10, 10, 180, 180, 0); + checkBBox("fo8", opt, 0, 0, 0, 0, 0); + + // from http://www.w3.org/Graphics/SVG/Test/20110816/harness/htmlObjectApproved/masking-path-07-b.html + opt = { fill: true, stroke: true, markers: true, clipped: true }; + checkBBox("rect-1", opt, 10, 10, 140, 140, 0); + checkBBox("rect-2", opt, 50, 30, 25, 100, 0); + checkBBox("rect-3", opt, 50, 50, 100, 100, 0); + checkBBox("g1", opt, 50, 50, 100, 100, 0); + + SimpleTest.finish(); +} + +window.addEventListener("load", run); + +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_getCTM.html b/dom/svg/test/test_getCTM.html new file mode 100644 index 0000000000..860b660867 --- /dev/null +++ b/dom/svg/test/test_getCTM.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=366697 +--> +<head> + <title>Test for Bug 366697</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style> + #padsvg1 { padding-left: 27px; padding-top: 43px; } + #transrect1 { transform: scale(2,3); } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=366697">Mozilla Bug 366697</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="getCTM-helper.svg"></iframe> + +<svg id="padsvg1" width="100" height="100"> + <rect id="transrect1" width="10" height="10" /> +</svg> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTest() { + var doc = $("svg").contentWindow.document; + + /* Minimal */ + var buggy = doc.getElementById("buggy"); + is(buggy.getCTM().e, 30, "buggy.getCTM().e"); + is(buggy.getCTM().f, 40, "buggy.getCTM().f"); + + var transrect1 = document.getElementById("transrect1"); + is(transrect1.getCTM().a, 2, "transrect1.getCTM().a"); + is(transrect1.getCTM().d, 3, "transrect1.getCTM().d"); + + var padsvg1 = document.getElementById("padsvg1"); + is(padsvg1.getScreenCTM().e - padsvg1.getBoundingClientRect().x, 27, "padsvg1.getScreenCTM().e"); + is(padsvg1.getScreenCTM().f - padsvg1.getBoundingClientRect().y, 43, "padsvg1.getScreenCTM().f"); + + var root = doc.documentElement; + var inner = doc.getElementById("inner"); + var g1 = doc.getElementById("g1"); + var outer = doc.getElementById("outer"); + var outer2 = doc.getElementById("outer2"); + var g2 = doc.getElementById("g2"); + var g3 = doc.getElementById("g3"); + var g4 = doc.getElementById("g4"); + var g5 = doc.getElementById("g5"); + var symbolRect = doc.getElementById("symbolRect"); + var fO = doc.getElementById("fO"); + /* Tests the consistency with nearestViewportElement + (code is from test_viewport.html) */ + // root.nearestViewportElement == null + is((function() { try { return root.getCTM(); } catch (e) { return e; } })(), null, "root.getCTM()"); + // inner.nearestViewportElement == root + is((function() { try { return inner.getCTM().e; } catch (e) { return e; } })(), 1, "inner.getCTM().e"); + is((function() { try { return inner.getCTM().f; } catch (e) { return e; } })(), 2, "inner.getCTM().f"); + // g1.nearestViewportElement == inner + is((function() { try { return g1.getCTM().e; } catch (e) { return e; } })(), 30, "g1.getCTM().e"); + is((function() { try { return g1.getCTM().f; } catch (e) { return e; } })(), 40, "g1.getCTM().f"); + // outer.nearestViewportElement == null + is((function() { try { return outer.getCTM(); } catch (e) { return e; } })(), null, "outer.getCTM()"); + // g2.nearestViewportElement == outer + is((function() { try { return g2.getCTM().e; } catch (e) { return e; } })(), 600, "g2.getCTM().e"); + is((function() { try { return g2.getCTM().f; } catch (e) { return e; } })(), 700, "g2.getCTM().f"); + // g3.nearestViewportElement == null + is((function() { try { return g3.getCTM(); } catch (e) { return e; } })(), null, "g3.getCTM()"); + // g4.nearestViewportElement == null + is((function() { try { return g4.getCTM().e; } catch (e) { return e; } })(), 1, "g4.getCTM().e"); + is((function() { try { return g4.getCTM().f; } catch (e) { return e; } })(), 2, "g4.getCTM().f"); + // symbolRect.nearestViewportElement == sym + is((function() { try { return symbolRect.getCTM().e; } catch (e) { return e; } })(), 70, "symbolRect.getCTM().e"); + is((function() { try { return symbolRect.getCTM().f; } catch (e) { return e; } })(), 80, "symbolRect.getCTM().f"); + // fO.nearestViewportElement == <svg> with no 'id' + is((function() { try { return fO.getCTM().e; } catch (e) { return e; } })(), 2, "fO.getCTM().e"); + is((function() { try { return fO.getCTM().f; } catch (e) { return e; } })(), 3, "fO.getCTM().f"); + // g5.nearestViewportElement == inner-2 + is((function() { try { return g5.getCTM(); } catch (e) { return e; } })(), null, "g5.getCTM()"); + + /* Tests the consistency with farthestViewportElement + (code is from test_viewport.html) */ + // root.farthestViewportElement == null (but actually == root) + is((function() { try { return root.getScreenCTM().e; } catch (e) { return e; } })(), 11, "root.getScreenCTM().e"); + is((function() { try { return root.getScreenCTM().f; } catch (e) { return e; } })(), 22, "root.getScreenCTM().f"); + // inner.farthestViewportElement == root + is((function() { try { return inner.getScreenCTM().e; } catch (e) { return e; } })(), 15, "inner.getScreenCTM().e"); + is((function() { try { return inner.getScreenCTM().f; } catch (e) { return e; } })(), 28, "inner.getScreenCTM().f"); + // g1.farthestViewportElement == root + is((function() { try { return g1.getScreenCTM().e; } catch (e) { return e; } })(), 45, "g1.getScreenCTM().e"); + is((function() { try { return g1.getScreenCTM().f; } catch (e) { return e; } })(), 68, "g1.getScreenCTM().f"); + // outer.farthestViewportElement == null (but actually == root) + is((function() { try { return outer.getScreenCTM().e; } catch (e) { return e; } })(), 46, "outer.getScreenCTM().e"); + is((function() { try { return outer.getScreenCTM().f; } catch (e) { return e; } })(), 69, "outer.getScreenCTM().f"); + // outer.farthestViewportElement == null (but actually == root) + is((function() { try { return outer2.getScreenCTM().e; } catch (e) { return e; } })(), -19, "outer2.getScreenCTM().e"); + is((function() { try { return outer2.getScreenCTM().f; } catch (e) { return e; } })(), -8, "outer2.getScreenCTM().f"); + // g2.farthestViewportElement == outer (but actually == root) + is((function() { try { return g2.getScreenCTM().e; } catch (e) { return e; } })(), 646, "g2.getScreenCTM().e"); + is((function() { try { return g2.getScreenCTM().f; } catch (e) { return e; } })(), 769, "g2.getScreenCTM().f"); + // g3.farthestViewportElement == null (but actually == null) + is((function() { try { return g3.getScreenCTM(); } catch (e) { return e; } })(), null, "g3.getScreenCTM()"); + // symbolRect.farthestViewportElement == root + is((function() { try { return symbolRect.getScreenCTM().e; } catch (e) { return e; } })(), 85, "symbolRect.getScreenCTM().e"); + is((function() { try { return symbolRect.getScreenCTM().f; } catch (e) { return e; } })(), 108, "symbolRect.getScreenCTM().f"); + // fO.farthestViewportElement == root + is((function() { try { return fO.getScreenCTM().e; } catch (e) { return e; } })(), 16, "symbolRect.getScreenCTM().e"); + is((function() { try { return fO.getScreenCTM().f; } catch (e) { return e; } })(), 29, "symbolRect.getScreenCTM().f"); + // g5.farthestViewportElement == root + is((function() { try { return g5.getScreenCTM(); } catch (e) { return e; } })(), null, "g5.getScreenCTM()"); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTest); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_getElementById.xhtml b/dom/svg/test/test_getElementById.xhtml new file mode 100644 index 0000000000..82050bea55 --- /dev/null +++ b/dom/svg/test/test_getElementById.xhtml @@ -0,0 +1,65 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test getElementById behaviour</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="matrixUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"> + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="1" id="svg"> + <!-- decoy element, same Id but not a <g> --> + <rect id="g"/> + <svg id="inner"> + <!-- the one we want to find --> + <g id="g"/> + <!-- check we don't get confused by CSS selectors --> + <g id="foo bar"/> + <g id="goo > car"/> + <g id="hoo~dar"/> + <g id="ioo+ear"/> + </svg> + <g id="g2"/> + </svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function main() { + var svgns = "http://www.w3.org/2000/svg"; + + var svg = document.getElementById("inner"); + + is(svg.getElementById("g").nodeName, "g", "expected to find g element child"); + is(svg.getElementById("foo bar").nodeName, "g", "expected to find foo bar element child"); + is(svg.getElementById("goo > car").nodeName, "g", "expected to find goo > car element child"); + is(svg.getElementById("hoo~dar").nodeName, "g", "expected to find hoo~dar element child"); + is(svg.getElementById("ioo+ear").nodeName, "g", "expected to find ioo+ear element child"); + + is(svg.getElementById("g2"), null, "did not expect to find an element with id g2"); + + // no element with Id = "g3" in the document at all + is(svg.getElementById("g3"), null, "did not expect to find an element with id g3"); + + svg = document.createElementNS(svgns, "svg"); + + var c = document.createElementNS(svgns, "circle"); + c.setAttribute("id", "c"); + svg.appendChild(c); + + is(svg.getElementById("c").nodeName, "circle", "expected to find circle element child"); + + SimpleTest.finish(); +} + +window.addEventListener("load", main); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_getPathSegListAtLength_with_d_property.html b/dom/svg/test/test_getPathSegListAtLength_with_d_property.html new file mode 100644 index 0000000000..c16ad71adf --- /dev/null +++ b/dom/svg/test/test_getPathSegListAtLength_with_d_property.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>Test for getPathSegAtLength for d property</title> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<style> +svg { + width: 10%; + height: 10%; + background: #eee; +} +svg path { + fill: none; + stroke: #000; +} +</style> + +<div id="log"></div> + +<svg viewBox="0 0 20 10"> + <path id='target1' d="M2,2 L8,8 L12,4"/> +</svg> +<svg viewBox="0 0 20 10"> + <path id='target2' style='d: path("M2,2 L8,8 L12,4")' /> +</svg> + +<script> +/* global test, assert_equals */ + +'use strict'; + +test(function() { + let target1 = document.getElementById('target1'); + let target2 = document.getElementById('target2'); + assert_equals(target1.getPathSegAtLength(5), 1); + assert_equals(target2.getPathSegAtLength(5), 1); + + assert_equals(target1.getPathSegAtLength(10), 2); + assert_equals(target2.getPathSegAtLength(10), 2); +}, "getPathSegAtLength works properly on both d attribute and d property"); + +test(function() { + let target = document.getElementById('target1'); + // Note: in order to make sure we flush style properly, we call + // getComputedStyle after setting an initial value first, so if + // getPathSegAtLength(5) doesn't flush style, it returns 0. + target.style.d = 'none'; + assert_equals(getComputedStyle(target).d, "none"); + + target.style.d = 'path("M2,2 h3 v5")'; + assert_equals(target.getPathSegAtLength(5), 2); +}, "getPathSegAtLength works properly after updating d property"); + +</script> diff --git a/dom/svg/test/test_getSubStringLength.xhtml b/dom/svg/test/test_getSubStringLength.xhtml new file mode 100644 index 0000000000..1d9784e003 --- /dev/null +++ b/dom/svg/test/test_getSubStringLength.xhtml @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=420243 +--> +<head> + <title>Test for Bug 420243</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=420243">Mozilla Bug 420243</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="getSubStringLength-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTests(text, charWidth) { + function chars(n) { return charWidth * n; } + + function expectThrow(charnum, nchars) { + try { + text.getSubStringLength(charnum, nchars); + ok(false, + "text.getSubStringLength(" + charnum + "," + nchars + ") " + + "should have thrown"); + } catch (e) { + is(e.name, "IndexSizeError", + "expected an index error for " + + "text.getSubStringLength(" + charnum + "," + nchars + ")"); + is(e.code, DOMException.INDEX_SIZE_ERR, + "expected an index error for " + + "text.getSubStringLength(" + charnum + "," + nchars + ")"); + } + } + + function expectValue(charnum, nchars, expected) { + try { + isfuzzy(text.getSubStringLength(charnum, nchars), expected, 0.01, + "text.getSubStringLength(" + charnum + "," + nchars + ") " + + "returned wrong value"); + } catch (e) { + ok(false, + "unexpected exception for " + + "text.getSubStringLength(" + charnum + "," + nchars + ")"); + } + } + + expectThrow(100, 2); + expectThrow(100, 0); + expectThrow(3, 0); + expectThrow(3, 1); + + expectValue(1, 3, chars(2)); + expectValue(0, 4, chars(3)); + expectValue(0, 0, chars(0)); + expectValue(1, 0, chars(0)); + expectValue(2, 0, chars(0)); + expectValue(0, 1, chars(1)); + expectValue(1, 1, chars(1)); + expectValue(2, 1, chars(1)); + expectValue(0, 2, chars(2)); + expectValue(1, 2, chars(2)); + expectValue(0, 3, chars(3)); + expectValue(1, 100, chars(2)); + expectValue(2, 100, chars(1)); +} + + +function run() { + try { + var document = $("svg").contentWindow.document; + var text = document.getElementById("text"); + + runTests(text, text.getSubStringLength(0, 1)); + } catch (e) { + ok(false, "threw error: " + e); + } + + SimpleTest.finish(); +} + +window.addEventListener("load", run); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_getTotalLength.xhtml b/dom/svg/test/test_getTotalLength.xhtml new file mode 100644 index 0000000000..0809f40ca1 --- /dev/null +++ b/dom/svg/test/test_getTotalLength.xhtml @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1474284 +--> +<head> + <title>Test for Bug 1474284</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1474284">Mozilla Bug 1474284</a> +<p id="display"></p> + +<svg xmlns="http://www.w3.org/2000/svg"> + <path id="path1" stroke="#000" fill="none" + d="M 50,40 + C 50,40 0,60 30,20"/> + <symbol font-size="10" width="20em" height="20em"> + <rect id="r1" x="5em" y="6em" width="20%" height="30%" /> + </symbol> +</svg> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function run() { + isfuzzy(document.getElementById("path1").getTotalLength(), + 55.19, 0.02, + 'getTotalLength() on element id="path1" returned the wrong value'); + + let r1 = document.getElementById("r1"); + is(r1.getTotalLength(), 200, "getTotalLength() should work for non-rendered element"); + + let r2 = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + r2.setAttribute("width", 200); + r2.setAttribute("height", 300); + is(r2.getTotalLength(), 1000, "getTotalLength() should work for a rect element not in the document"); + + let c = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + c.setAttribute("r", 200); + isfuzzy(c.getTotalLength(), 2 * Math.PI * 200, 0.2, "getTotalLength() should work for a circle element not in the document"); + + let e = document.createElementNS("http://www.w3.org/2000/svg", "ellipse"); + e.setAttribute("rx", 200); + e.setAttribute("ry", 200); + isfuzzy(e.getTotalLength(), 2 * Math.PI * 200, 0.2, "getTotalLength() should work for an ellipse element not in the document"); + + SimpleTest.finish(); +} + +window.addEventListener("load", run); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_hit-testing-and-viewbox.xhtml b/dom/svg/test/test_hit-testing-and-viewbox.xhtml new file mode 100644 index 0000000000..6ab83e54be --- /dev/null +++ b/dom/svg/test/test_hit-testing-and-viewbox.xhtml @@ -0,0 +1,81 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1486952 +--> +<head> + <title>Test that hit-testing works after a viewBox update</title> + + <style> + :hover { fill: lime; } + </style> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + const div = $("div"); + const offsetX = div.offsetLeft; + const offsetY = div.offsetTop; + const outerRect = $("outerRect"); + const innerRect = $("innerRect"); + const outerSVG = $("outerSVG"); + const innerSVG = $("innerSVG"); + let got; + + // Update the inner SVG viewBox to "move" the inner rectangle off its current + // position on screen: + innerSVG.setAttribute("viewBox", "-25 0 50 50"); + got = document.elementFromPoint(offsetX + 150, offsetY + 25); + is(got, innerRect, "Should hit inner rectangle (1)"); + + // Update the inner SVG viewBox again. (At the time of writing, a reflow is + // triggered the first time you change viewBox on an inner svg, so the + // previous test is expected to always pass. This next test will fail if we're + // updating overflows on the inner svg frame instead of its children). + innerSVG.setAttribute("viewBox", "0 -25 50 50"); + got = document.elementFromPoint(offsetX + 100, offsetY + 75); + is(got, innerRect, "Should hit inner rectangle (2)"); + + // Now update the outer SVG viewBox and confirm that both rectangles are + // registered. (Note that in this case the overflow rectangle of the inner + // svg is the inner svg's viewport, so be sure to "move" the outer svg so that + // the "new" outer rectangle and inner svg are off the current outerRect + // union inner svg viewport - hit testing still works in that union.) + outerSVG.setAttribute("viewBox", "-200 0 400 100"); + // Outer: + got = document.elementFromPoint(offsetX + 250, offsetY + 50); + is(got, outerRect, "Should hit outer rectangle"); + // Inner: + got = document.elementFromPoint(offsetX + 300, offsetY + 75); + is(got, innerRect, "Should hit inner rectangle (3)"); + + SimpleTest.finish(); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1486952">Mozilla Bug 1486952</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"></div> + <svg xmlns="http://www.w3.org/2000/svg" id="outerSVG" width="400" height="100" + viewBox="0 0 400 100"> + <rect x="25" y="25" width="50" height="50" fill="red" id="outerRect" /> + <svg x="75" width="100" height="100" viewBox="0 0 50 50" id="innerSVG"> + <rect width="25" height="25" fill="red" id="innerRect" /> + </svg> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_lang.xhtml b/dom/svg/test/test_lang.xhtml new file mode 100644 index 0000000000..df11660df2 --- /dev/null +++ b/dom/svg/test/test_lang.xhtml @@ -0,0 +1,90 @@ +<!DOCTYPE html> +<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=721920 +--> +<head> + <title>Test for Bug 721920</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + +svg text { word-spacing: 1em; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=721920">Mozilla Bug 721920</a> +<p id="display"> + <svg xmlns="http://www.w3.org/2000/svg" width="400" height="300"> + <g lang="zh-Hans"> + <text id="s0" y="40" style="font-size: 0">汉字</text> + <text id="s4" y="80" style="font-size: 4px">汉字</text> + <text id="s12" y="120" style="font-size: 12px">汉字</text> + <text id="s28" y="160" style="font-size: 28px">汉字</text> + </g> + </svg> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +//<![CDATA[ + +/** Test for Bug 721920 **/ + +SimpleTest.waitForExplicitFinish(); + +var elts = [ + document.getElementById("s0"), + document.getElementById("s4"), + document.getElementById("s12"), + document.getElementById("s28"), +]; + +function fs(idx) { + // The computed font size actually *doesn't* currently reflect the + // minimum font size preference, but things in em units do. Hence + // why we use word-spacing here. + // test_bug401046.html uses margin-bottom instead, but there's an + // SVG bug that prevents that working in SVG (bug 728723). + return getComputedStyle(elts[idx], "").wordSpacing; +} + +SpecialPowers.pushPrefEnv({"clear": [["font.minimum-size.zh-CN"]]}, step1); + +function step1() { + is(fs(0), "0px", "at min font size 0, 0px should compute to 0px"); + is(fs(1), "4px", "at min font size 0, 4px should compute to 4px"); + is(fs(2), "12px", "at min font size 0, 12px should compute to 12px"); + is(fs(3), "28px", "at min font size 0, 28px should compute to 28px"); + + SpecialPowers.pushPrefEnv({"set": [["font.minimum-size.zh-CN", 7]]}, step2); +} + +function step2() { + is(fs(0), "0px", "at min font size 7, 0px should compute to 0px"); + is(fs(1), "7px", "at min font size 7, 4px should compute to 7px"); + is(fs(2), "12px", "at min font size 7, 12px should compute to 12px"); + is(fs(3), "28px", "at min font size 7, 28px should compute to 28px"); + + SpecialPowers.pushPrefEnv({"set": [["font.minimum-size.zh-CN", 18]]}, step3); +} + +function step3() { + is(fs(0), "0px", "at min font size 18, 0px should compute to 0px"); + is(fs(1), "18px", "at min font size 18, 4px should compute to 18px"); + is(fs(2), "18px", "at min font size 18, 12px should compute to 18px"); + is(fs(3), "28px", "at min font size 18, 28px should compute to 28px"); + + SpecialPowers.pushPrefEnv({"clear": [["font.minimum-size.zh-CN"]]}, SimpleTest.finish); +} + +//]]> +</script> +</pre> +</body> +</html> + diff --git a/dom/svg/test/test_length.xhtml b/dom/svg/test/test_length.xhtml new file mode 100644 index 0000000000..513c65cbb2 --- /dev/null +++ b/dom/svg/test/test_length.xhtml @@ -0,0 +1,58 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=342513 +--> +<head> + <title>Test SVG Length conversions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=342513">Mozilla Bug 342513</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"> + </div> + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="1" id="svg"> + </svg> + <svg xmlns="http://www.w3.org/2000/svg" width="600" height="400" font-size="5"> + <svg font-size="10" width="20em" height="20em"> + <rect id="r1" x="5em" y="6em" width="20%" height="30%" /> + </svg> + </svg> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var svgDoc = document.getElementById("svg"); + var divElem = document.getElementById("div"); + var expectedWidth = divElem.clientWidth; + // test for the pixel width of the svg + is(svgDoc.width.baseVal.value, expectedWidth, "svgDoc.width.baseVal.value"); + + // set the SVG width to ~50% in pixels + svgDoc.width.baseVal.newValueSpecifiedUnits(svgDoc.width.baseVal.SVG_LENGTHTYPE_PX, Math.floor(expectedWidth / 2)); + svgDoc.width.baseVal.convertToSpecifiedUnits(svgDoc.width.baseVal.SVG_LENGTHTYPE_PERCENTAGE); + // the valueInSpecifiedUnits should now be 50% + is(Math.round(svgDoc.width.baseVal.valueInSpecifiedUnits), 50, "valueInSpecifiedUnits after convertToSpecifiedUnits"); + + let r1 = document.getElementById("r1"); + is(r1.width.baseVal.value, 40, "width in em for elements inside inner <svg> should be resolved against the inner <svg>"); + is(r1.height.baseVal.value, 60, "height in em for elements inside inner <svg> should be resolved against the inner <svg>"); + + SimpleTest.finish(); +} + +window.addEventListener("load", run); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_lengthParsing.html b/dom/svg/test/test_lengthParsing.html new file mode 100644 index 0000000000..8471b19855 --- /dev/null +++ b/dom/svg/test/test_lengthParsing.html @@ -0,0 +1,82 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=946529 +--> +<head> + <meta charset="utf-8"> + <title>Test transform parsing</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946529">Mozilla Bug 946529</a> +<p id="display"></p> +<div id="content" style="display: none"> + <svg width="100%" height="1" id="svg"> + <rect id="rect"/> + </svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +// Test cases +checkParseOk("", 0); +checkParseOk("-.1", -0.1); +checkParseOk("1e1", 10); +checkParseOk("1em", 1, "em"); +checkParseOk("1ex", 1, "ex"); +checkParseOk("1e1em", 10, "em"); +checkParseOk("1E+2", 100); +checkParseOk(".1e-2", 0.001); +checkParseOk(" 10", 10); +checkParseOk("10 ", 10); +checkParseOk(" 10 ", 10); +checkParseOk(" 10em ", 10, "em"); + +// Fail cases +checkParseFail("1e"); +checkParseFail("1 e"); +checkParseFail("1 em"); +checkParseFail("1ee"); +checkParseFail(" 10 20"); + +function checkParseOk(spec, valueInUnits, units) { + var rect = document.getElementById("rect"); + + // Clear previous value + rect.removeAttribute("x"); + rect.setAttribute("x", spec); + + // Check number part + const tolerance = 1 / 65535; + var actual = rect.x.baseVal.valueInSpecifiedUnits; + ok(Math.abs(actual - valueInUnits) < tolerance, + spec + " (value) - got " + actual + ", expected " + valueInUnits); + + // Check unit part + var unitMapping = { + "unknown": SVGLength.SVG_LENGTHTYPE_UNKNOWN, + "": SVGLength.SVG_LENGTHTYPE_NUMBER, + "%": SVGLength.SVG_LENGTHTYPE_PERCENTAGE, + "em": SVGLength.SVG_LENGTHTYPE_EMS, + "ex": SVGLength.SVG_LENGTHTYPE_EXS, + "px": SVGLength.SVG_LENGTHTYPE_PX, + "cm": SVGLength.SVG_LENGTHTYPE_CM, + "mm": SVGLength.SVG_LENGTHTYPE_MM, + "in": SVGLength.SVG_LENGTHTYPE_IN, + "pt": SVGLength.SVG_LENGTHTYPE_PT, + "pc": SVGLength.SVG_LENGTHTYPE_PC, + }; + if (typeof units == "undefined") { + units = ""; + } + is(rect.x.baseVal.unitType, unitMapping[units], spec + " (unit)"); +} + +function checkParseFail(spec) { + checkParseOk(spec, 0); +} +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_markerOrient.xhtml b/dom/svg/test/test_markerOrient.xhtml new file mode 100644 index 0000000000..1b91a9a62a --- /dev/null +++ b/dom/svg/test/test_markerOrient.xhtml @@ -0,0 +1,110 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=892372 +--> +<head> + <title>Test for Bug 892372</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 892372 **/ + SimpleTest.waitForExplicitFinish(); + + function testAutoIsSet(marker) { + is(marker.orientType.baseVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO, + "orientType baseVal for auto"); + is(marker.orientType.animVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO, + "orientType animVal for auto"); + is(marker.orientAngle.baseVal.value, 0, "orientAngle baseVal for auto"); + is(marker.orientAngle.animVal.value, 0, "orientAngle animVal for auto"); + } + + function testAutoStartReverseIsSet(marker) { + is(marker.orientType.baseVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO_START_REVERSE, + "orientType baseVal for auto-start-reverse"); + is(marker.orientType.animVal, SVGMarkerElement.SVG_MARKER_ORIENT_AUTO_START_REVERSE, + "orientType animVal for auto-start-reverse"); + is(marker.orientAngle.baseVal.value, 0, + "orientAngle baseVal for auto-start-reverse"); + is(marker.orientAngle.animVal.value, 0, + "orientAngle animVal for auto-start-reverse"); + } + + function testAngleIsSet(marker, angleVal) { + is(marker.orientType.baseVal, SVGMarkerElement.SVG_MARKER_ORIENT_ANGLE, + "orientType baseVal after numeric angle is set"); + is(marker.orientType.animVal, SVGMarkerElement.SVG_MARKER_ORIENT_ANGLE, + "orientType animVal after numeric angle is set"); + is(marker.orientAngle.baseVal.value, angleVal, + "orientAngle baseVal after numeric angle is set"); + is(marker.orientAngle.animVal.value, angleVal, + "orientAngle animVal after numeric angle is set"); + } + + function run() { + var m = $("m"); + + // Testing two conditions: + // 1) If orient is set to a numeric angle and then set to auto or + // auto-start-reverse, orientAngle should return a value of 0 + // 2) If orient is set to something of type other than + // SVG_MARKER_ORIENT_ANGLE and then set to a numeric angle, + // orientType should return SVG_MARKER_ORIENT_ANGLE + + // default is orient="0" + testAngleIsSet(m, 0); + + m.setOrientToAuto(); + testAutoIsSet(m); + + // testing condition 2 for an angle set using setOrientToAngle + var a = $("svg").createSVGAngle(); + a.newValueSpecifiedUnits(SVGAngle.SVG_ANGLETYPE_DEG, 90); + m.setOrientToAngle(a); + testAngleIsSet(m, a.value); + + // testing condition 1 for orient set using setOrientToAuto + m.setOrientToAuto(); + testAutoIsSet(m); + + // testing condition 2 for an angle set using setAttribute + m.setAttribute("orient", "180"); + testAngleIsSet(m, 180); + + // testing condition 1 for orient set to "auto" using setAttribute + m.setAttribute("orient", "auto"); + testAutoIsSet(m); + + m.setAttribute("orient", "270"); + + // testing condition 1 for orient set to "auto-start-reverse" using + // setAttribute + m.setAttribute("orient", "auto-start-reverse"); + testAutoStartReverseIsSet(m); + + SimpleTest.finish(); + } + + window.addEventListener("load", run); + + ]]> +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=892372">Mozilla Bug 892372</a> +<p id="display"></p> +<div id="content" style="display: none"> + + <svg xmlns="http://www.w3.org/2000/svg" id="svg"> + <defs> + <marker id="m" /> + </defs> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_non-scaling-stroke.html b/dom/svg/test/test_non-scaling-stroke.html new file mode 100644 index 0000000000..49010eed0a --- /dev/null +++ b/dom/svg/test/test_non-scaling-stroke.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=829085 +--> +<head> + <title>Test for Bug 829085 - non-scaling-stroke hit testing</title> + <meta charset="utf-8"></meta> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=829085">Mozilla Bug 829085 - non-scaling-stroke hit testing</a> +<p id="display"></p> +<div id="content"> + + <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="300" height="250"> + <rect width="100%" height="100%" fill="none" stroke-width="4" stroke="blue"/> + <style> + line:hover { stroke: lime; } + </style> + <g transform="scale(1.3) translate(100, -80) rotate(45)"> + <line id="line" stroke="teal" stroke-width="120px" + x1="50" y1="130" x2="200" y2="130" + vector-effect="non-scaling-stroke"></line> + </g> + </svg> + +</div> +<pre id="test"> +<script type="application/javascript"> + + function startTest() { + SimpleTest.waitForFocus(function() { + disableNonTestMouseEvents(true); + // Send a click + synthesizeMouseExpectEvent($("svg"), 170, 100, { }, + $("line"), "click", + "Testing mouse event on non-scaling-stroke"); + disableNonTestMouseEvents(false); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(startTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_nonAnimStrings.xhtml b/dom/svg/test/test_nonAnimStrings.xhtml new file mode 100644 index 0000000000..5ceabed892 --- /dev/null +++ b/dom/svg/test/test_nonAnimStrings.xhtml @@ -0,0 +1,78 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=589436 +--> +<head> + <title>Test for non-animated strings</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589436">Mozilla Bug 589436</a> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120px" height="120px" + onload="this.pauseAnimations()"> + <defs> + <path id="moveFarAway" d="M300,300 h0"/> + <path id="moveToUpperLeft" d="M100,100 h0"/> + </defs> + <script id="script"> + <animate attributeName="xlink:href" from="" to="animated" dur="0.5s" begin="1s" + fill="freeze" id="animate"/> + </script> + <rect class="test" x="0" y="0" width="50" height="50"> + <animateMotion begin="1" dur="1" fill="freeze"> + <mpath id="mpath" xlink:href="#moveFarAway"> + <animate attributeName="xlink:href" from="#moveFarAway" to="#moveToUpperLeft" dur="0.5s" begin="1s" + fill="freeze"/> + </mpath> + </animateMotion> + </rect> +</svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test some strings are not animatable **/ + +/* Global Variables */ +var svg = document.getElementById("svg"); +var script = document.getElementById("script"); +var mpath = document.getElementById("mpath"); +var animate = document.getElementById("animate"); + +SimpleTest.waitForExplicitFinish(); + +function main() { + ok(svg.animationsPaused(), "should be paused by <svg> load handler"); + is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); + + // Sanity check: check initial values + is(script.href.baseVal, "", "Unexpected initial script baseVal"); + is(script.href.animVal, "", "Unexpected initial script animVal"); + is(mpath.href.baseVal, "#moveFarAway", "Unexpected initial mpath baseVal"); + is(mpath.href.animVal, "#moveFarAway", "Unexpected initial mpath animVal"); + + // Move to the end of the animation - should make no difference + svg.setCurrentTime(2); + + is(script.href.baseVal, "", "Unexpected value for script baseVal after animation"); + is(script.href.animVal, "", "Unexpected value for script animVal after animation"); + is(mpath.href.baseVal, "#moveFarAway", "Unexpected value for mpath baseVal after animation"); + is(mpath.href.animVal, "#moveFarAway", "Unexpected value for mpath animVal after animation"); + + SimpleTest.finish(); +} + +if (animate && animate.targetElement) { + window.addEventListener("load", main); +} else { + ok(true); // Skip tests but don't report 'todo' either + SimpleTest.finish(); +} +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_object-delayed-intrinsic-size.html b/dom/svg/test/test_object-delayed-intrinsic-size.html new file mode 100644 index 0000000000..18645892f7 --- /dev/null +++ b/dom/svg/test/test_object-delayed-intrinsic-size.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1063073 +--> +<html> + <head> + <title>Test that <object> embedding SVG and using its intrinsic + size will resize if the <object> gets a reflow before the + root-<svg> gets its nsSVGOuterSVGFrame + </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script> + +// This test checks for a race condition. If it fails intermittently then it +// may actually be a full failure. + +SimpleTest.waitForExplicitFinish(); + +function runTest() { + var object = document.querySelector("object"); + var cs = document.defaultView.getComputedStyle(object); + var width = cs.getPropertyValue("width"); + is(width, "70px", "Check that the <object> size updated"); + SimpleTest.finish(); +} + + </script> + </head> + <body onload="runTest();"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1063073">Mozilla Bug 1063073</a> + <p id="display"></p> + <div id="content"> + <object style="border:1px solid black" type="image/svg+xml" + data="object-delayed-intrinsic-size.sjs"></object> + </div> + </body> +</html> + diff --git a/dom/svg/test/test_onerror.xhtml b/dom/svg/test/test_onerror.xhtml new file mode 100644 index 0000000000..394c88c890 --- /dev/null +++ b/dom/svg/test/test_onerror.xhtml @@ -0,0 +1,35 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500261 +--> +<head> + <title>Test onerror behaviour</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500261">Mozilla Bug 500261</a> +<p id="display"></p> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + ok(true, "onerror method called"); + SimpleTest.finish(); +} + +]]> +</script> +<div id="content"> + + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="1" id="svg"> + <image width="1" height="1" xlink:href="http://localhost/serverGone.gif" onerror="run()"/> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_onload.xhtml b/dom/svg/test/test_onload.xhtml new file mode 100644 index 0000000000..982cbb52bf --- /dev/null +++ b/dom/svg/test/test_onload.xhtml @@ -0,0 +1,35 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1668942 +--> +<head> + <title>Test onload behaviour</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1668942">Mozilla Bug 1668942</a> +<p id="display"></p> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + ok(true, "onload method called"); + SimpleTest.finish(); +} + +]]> +</script> +<div id="content"> + + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="1"> + <image width="1" height="1" xlink:href="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" onload="run()"/> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_onload2.xhtml b/dom/svg/test/test_onload2.xhtml new file mode 100644 index 0000000000..6572fc6de8 --- /dev/null +++ b/dom/svg/test/test_onload2.xhtml @@ -0,0 +1,48 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1474311 +--> +<head> + <title>Test onload behaviour</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1474311">Mozilla Bug 1474311</a> +<p id="display"></p> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + + let doc = document.implementation.createHTMLDocument(''); + + doc.body.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image id="image" xlink:href="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" height="200" width="200"/></svg>'; + + let img = doc.body.firstChild.firstChild; + + document.getElementById('svg').appendChild(img); + + img.addEventListener('load', function () { + ok(true, "onload method called"); + SimpleTest.finish(); + }); + + img.addEventListener('error', function () { + ok(false, "onerror method called"); + }); +} + +]]> +</script> +<div id="content"> + + <svg id="svg" onload="run()" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="1"> + </svg> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pairParsing.html b/dom/svg/test/test_pairParsing.html new file mode 100644 index 0000000000..9a603dc1d5 --- /dev/null +++ b/dom/svg/test/test_pairParsing.html @@ -0,0 +1,43 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1512745 +--> +<head> + <meta charset="utf-8"> + <title>Test pair parsing</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1512745">Mozilla Bug 1512745</a> + +<svg width="230" height="120" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + + <filter><feGaussianBlur id="x1" stdDeviation=" 5 " /></filter> + <filter><feGaussianBlur id="x2" stdDeviation=" 5 10 " /></filter> + <filter><feGaussianBlur id="x3" stdDeviation="5 10 " /></filter> + <filter><feGaussianBlur id="x4" stdDeviation=" 5,10 " /></filter> +</svg> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + function checkValue(id, x, y) { + if (y === undefined) { + y = x; + } + let e = document.getElementById(id); + is(e.stdDeviationX.baseVal, x, "Wrong stdDeviationX"); + is(e.stdDeviationY.baseVal, y, "Wrong stdDeviationY"); + } + + checkValue("x1", 5); + checkValue("x2", 5, 10); + checkValue("x3", 5, 10); + checkValue("x4", 5, 10); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pathAnimInterpolation.xhtml b/dom/svg/test/test_pathAnimInterpolation.xhtml new file mode 100644 index 0000000000..3f2e6659a9 --- /dev/null +++ b/dom/svg/test/test_pathAnimInterpolation.xhtml @@ -0,0 +1,341 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=619498 +--> +<head> + <title>Test interpolation between different path segment types</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619498">Mozilla Bug 619498</a> +<svg xmlns="http://www.w3.org/2000/svg" id="svg" visibility="hidden" + onload="this.pauseAnimations()"/> +<script type="application/javascript"><![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var gSVG = document.getElementById("svg"); + +// Array of all subtests to run. This is populated by addTest. +var gTests = []; + +// Array of all path segment types. +var gTypes = "zMmLlCcQqAaHhVvSsTt".split(""); + +// Property names on the SVGPathSeg objects for the given segment type, in the +// order that they would appear in a path data string. +var gArgumentNames = { + Z: [], + M: ["x", "y"], + L: ["x", "y"], + C: ["x1", "y1", "x2", "y2", "x", "y"], + Q: ["x1", "y1", "x", "y"], + A: ["r1", "r2", "angle", "largeArcFlag", "sweepFlag", "x", "y"], + H: ["x"], + V: ["y"], + S: ["x2", "y2", "x", "y"], + T: ["x", "y"], +}; + +// All of these prefixes leave the current point at 100,100. Some of them +// affect the implied control point if followed by a smooth quadratic or +// cubic segment, but no valid interpolations depend on those control points. +var gPrefixes = [ + [1, "M100,100"], + [2, "M50,50 M100,100"], + [2, "M50,50 m50,50"], + [2, "M50,50 L100,100"], + [2, "M50,50 l50,50"], + [3, "M50,50 H100 V100"], + [3, "M50,50 h50 V100"], + [3, "M50,50 H100 v50"], + [2, "M50,50 A10,10,10,0,0,100,100"], + [2, "M50,50 a10,10,10,0,0,50,50"], + [4, "M50,50 l50,50 z m50,50"], + + // These leave the quadratic implied control point at 125,125. + [2, "M50,50 Q75,75,100,100"], + [2, "M50,50 q25,25,50,50"], + [2, "M75,75 T100,100"], + [2, "M75,75 t25,25"], + [3, "M50,50 T62.5,62.5 t37.5,37.5"], + [3, "M50,50 T62.5,62.5 T100,100"], + [3, "M50,50 t12.5,12.5 t37.5,37.5"], + [3, "M50,50 t12.5,12.5 T100,100"], + [3, "M50,50 Q50,50,62.5,62.5 t37.5,37.5"], + [3, "M50,50 Q50,50,62.5,62.5 T100,100"], + [3, "M50,50 q0,0,12.5,12.5 t37.5,37.5"], + [3, "M50,50 q0,0,12.5,12.5 T100,100"], + + // These leave the cubic implied control point at 125,125. + [2, "M50,50 C10,10,75,75,100,100"], + [2, "M50,50 c10,10,25,25,50,50"], + [2, "M50,50 S75,75,100,100"], + [2, "M50,50 s25,25,50,50"], + [3, "M50,50 S10,10,75,75 S75,75,100,100"], + [3, "M50,50 S10,10,75,75 s0,0,25,25"], + [3, "M50,50 s10,10,25,25 S75,75,100,100"], + [3, "M50,50 s10,10,25,25 s0,0,25,25"], + [3, "M50,50 C10,10,20,20,75,75 S75,75,100,100"], + [3, "M50,50 C10,10,20,20,75,75 s0,0,25,25"], + [3, "M50,50 c10,10,20,20,25,25 S75,75,100,100"], + [3, "M50,50 c10,10,20,20,25,25 s0,0,25,25"], +]; + +// These are all of the suffixes whose result is not dependent on whether the +// preceding segment types are quadratic or cubic types. Each entry is: +// +// "<fromType><toType>": [fromArguments, +// toArguments, +// expectedArguments, +// expectedArgumentsAdditive] +// +// As an example: +// +// "Mm": [[10, 20], [30, 40], [-30, -20], [-120, -100]] +// +// This will testing interpolating between "M10,20" and "m30,40". All of the +// these tests assume that the current point is left at 100,100. So the above +// entry represents two kinds of tests, one where additive and one not: +// +// <path d="... M10,20"> +// <animate attributeName="d" from="... M10,20" to="... m30,40"/> +// </path> +// +// and +// +// <path d="... M10,20"> +// <animate attributeName="d" from="... M10,20" to="... m30,40" +// additive="sum"/> +// </path> +// +// where the "..." is some prefix that leaves the current point at 100,100. +// Each of the suffixes here in gSuffixes will be paired with each of the +// prefixes in gPrefixes, all of which leave the current point at 100,100. +// (Thus the above two tests for interpolating between "M" and "m" will be +// performed many times, with different preceding commands.) +// +// The expected result of the non-additive test is "m-30,-20". Since the +// animation is from an absolute moveto to a relative moveto, we first +// convert the "M10,20" into its relative form, which is "m-90,-80" due to the +// current point being 100,100. Half way through the animation between +// "m-90,-80" and "m30,40" is thus "m-30,-20". +// +// The expected result of the additive test is "m-120,-100". We take the +// halfway value of the animation, "m-30,-20" and add it on to the underlying +// value. Since the underlying value "M10,20" is an absolute moveto, we first +// convert it to relative, "m-90,-80", and then add the "m-30,-20" to it, +// giving us the result "m-120,-100". +var gSuffixes = { + // Same path segment type, no conversion required. + MM: [[10, 20], [30, 40], [20, 30], [30, 50]], + mm: [[10, 20], [30, 40], [20, 30], [30, 50]], + LL: [[10, 20], [30, 40], [20, 30], [30, 50]], + ll: [[10, 20], [30, 40], [20, 30], [30, 50]], + CC: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120], + [40, 50, 60, 70, 80, 90], [50, 70, 90, 110, 130, 150]], + cc: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120], + [40, 50, 60, 70, 80, 90], [50, 70, 90, 110, 130, 150]], + QQ: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]], + qq: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]], + AA: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100], + [35, 45, 55, 0, 0, 65, 75], [45, 65, 85, 0, 0, 105, 125]], + aa: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100], + [35, 45, 55, 0, 0, 65, 75], [45, 65, 85, 0, 0, 105, 125]], + HH: [[10], [20], [15], [25]], + hh: [[10], [20], [15], [25]], + VV: [[10], [20], [15], [25]], + vv: [[10], [20], [15], [25]], + SS: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]], + ss: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]], + TT: [[10, 20], [30, 40], [20, 30], [30, 50]], + tt: [[10, 20], [30, 40], [20, 30], [30, 50]], + + // Relative <-> absolute conversion. + Mm: [[10, 20], [30, 40], [-30, -20], [-120, -100]], + mM: [[10, 20], [30, 40], [70, 80], [180, 200]], + Ll: [[10, 20], [30, 40], [-30, -20], [-120, -100]], + lL: [[10, 20], [30, 40], [70, 80], [180, 200]], + Cc: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120], + [-10, 0, 10, 20, 30, 40], [-100, -80, -60, -40, -20, 0]], + cC: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120], + [90, 100, 110, 120, 130, 140], [200, 220, 240, 260, 280, 300]], + Qq: [[10, 20, 30, 40], [50, 60, 70, 80], + [-20, -10, 0, 10], [-110, -90, -70, -50]], + qQ: [[10, 20, 30, 40], [50, 60, 70, 80], + [80, 90, 100, 110], [190, 210, 230, 250]], + Aa: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100], + [35, 45, 55, 0, 0, 15, 25], [45, 65, 85, 0, 0, -45, -25]], + aA: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100], + [35, 45, 55, 0, 0, 115, 125], [45, 65, 85, 0, 0, 255, 275]], + Hh: [[10], [20], [-35], [-125]], + hH: [[10], [20], [65], [175]], + Vv: [[10], [20], [-35], [-125]], + vV: [[10], [20], [65], [175]], + Tt: [[10, 20], [30, 40], [-30, -20], [-120, -100]], + tT: [[10, 20], [30, 40], [70, 80], [180, 200]], + Ss: [[10, 20, 30, 40], [50, 60, 70, 80], + [-20, -10, 0, 10], [-110, -90, -70, -50]], + sS: [[10, 20, 30, 40], [50, 60, 70, 80], + [80, 90, 100, 110], [190, 210, 230, 250]], +}; + +// Returns an array of property names that exist on an SVGPathSeg object +// corresponding to the given segment type, in the order that they would +// be present in a path data string. +function argumentNames(aType) { + return gArgumentNames[aType.toUpperCase()]; +} + +// Creates and returns a new element and sets some attributes on it. +function newElement(aNamespaceURI, aLocalName, aAttributes) { + var e = document.createElementNS(aNamespaceURI, aLocalName); + if (aAttributes) { + for (let [name, value] of Object.entries(aAttributes)) { + e.setAttribute(name, value); + } + } + return e; +} + +// Creates and returns a new SVG element and sets some attributes on it. +function newSVGElement(aLocalName, aAttributes) { + return newElement("http://www.w3.org/2000/svg", aLocalName, aAttributes); +} + +// Creates a subtest and adds it to the document. +// +// * aPrefixLength/aPrefix the prefix to use +// * aFromType/aFromArguments the segment to interpolate from +// * aToType/aToArguments the segment to interpolate to +// * aExpectedType/aExpectedArguments the expected result of the interpolated +// segment half way through the animation +// duration +// * aAdditive whether the subtest is for an additive +// animation +function addTest(aPrefixLength, aPrefix, aFromType, aFromArguments, + aToType, aToArguments, aExpectedType, aExpectedArguments, + aAdditive) { + var fromPath = aPrefix + aFromType + aFromArguments, + toPath = aPrefix + aToType + aToArguments; + + var path = newSVGElement("path", { d: fromPath }); + var animate = + newSVGElement("animate", { attributeName: "d", + from: fromPath, + to: toPath, + dur: "8s", + additive: aAdditive ? "sum" : "replace" }); + path.appendChild(animate); + gSVG.appendChild(path); + + gTests.push({ element: path, + prefixLength: aPrefixLength, + from: fromPath, + to: toPath, + toType: aToType, + expectedType: aExpectedType, + expected: aExpectedArguments, + usesAddition: aAdditive }); +} + +// Generates an array of path segment arguments for the given type. aOffset +// is a number to add on to all non-Boolean segment arguments. +function generatePathSegmentArguments(aType, aOffset) { + var args = new Array(argumentNames(aType).length); + for (let i = 0; i < args.length; i++) { + args[i] = i * 10 + aOffset; + } + if (aType == "A" || aType == "a") { + args[3] = 0; + args[4] = 0; + } + return args; +} + +// Returns whether interpolating between the two given types is valid. +function isValidInterpolation(aFromType, aToType) { + return aFromType.toUpperCase() == aToType.toUpperCase(); +} + +// Runs the test. +function run() { + for (let additive of [false, true]) { + let indexOfExpectedArguments = additive ? 3 : 2; + + // Add subtests for each combination of prefix and suffix, and additive + // or not. + for (let [typePair, suffixEntry] of Object.entries(gSuffixes)) { + let fromType = typePair[0], + toType = typePair[1], + fromArguments = suffixEntry[0], + toArguments = suffixEntry[1], + expectedArguments = suffixEntry[indexOfExpectedArguments]; + + for (let prefixEntry of gPrefixes) { + let [prefixLength, prefix] = prefixEntry; + addTest(prefixLength, prefix, fromType, fromArguments, + toType, toArguments, toType, expectedArguments, additive); + } + } + + // Test that differences in arc flag parameters cause the + // interpolation/addition not to occur. + addTest(1, "M100,100", + "A", [10, 20, 30, 0, 0, 40, 50], + "a", [60, 70, 80, 0, 1, 90, 100], + "a", [60, 70, 80, 0, 1, 90, 100], additive); + addTest(1, "M100,100", + "A", [10, 20, 30, 0, 0, 40, 50], + "a", [60, 70, 80, 1, 0, 90, 100], + "a", [60, 70, 80, 1, 0, 90, 100], additive); + + // Test all pairs of segment types that cannot be interpolated between. + for (let fromType of gTypes) { + let fromArguments = generatePathSegmentArguments(fromType, 0); + for (let toType of gTypes) { + if (!isValidInterpolation(fromType, toType)) { + let toArguments = generatePathSegmentArguments(toType, 1000); + addTest(1, "M100,100", fromType, fromArguments, + toType, toArguments, toType, toArguments, additive); + } + } + } + } + + // Move the document time to half way through the animations. + gSVG.setCurrentTime(4); + + // Inspect the results of each subtest. + for (let test of gTests) { + let list = test.element.animatedPathSegList; + is(list.numberOfItems, test.prefixLength + 1, + "Length of animatedPathSegList for interpolation " + + (test.usesAddition ? "with addition " : "") + + " from " + test.from + " to " + test.to); + + let seg = list.getItem(list.numberOfItems - 1); + let propertyNames = argumentNames(test.expectedType); + + let actual = []; + for (let i = 0; i < test.expected.length; i++) { + actual.push(+seg[propertyNames[i]]); + } + + is(seg.pathSegTypeAsLetter + actual, test.expectedType + test.expected, + "Path segment for interpolation " + + (test.usesAddition ? "with addition " : "") + + " from " + test.from + " to " + test.to); + } + + // Clear all the tests. We have tons of them attached to the DOM and refresh driver tick will + // go through them all by calling animation controller. + gSVG.remove(); + + SimpleTest.finish(); +} + +window.addEventListener("load", run); +]]></script> +</body> +</html> diff --git a/dom/svg/test/test_pointAtLength.xhtml b/dom/svg/test/test_pointAtLength.xhtml new file mode 100644 index 0000000000..652fac0e69 --- /dev/null +++ b/dom/svg/test/test_pointAtLength.xhtml @@ -0,0 +1,49 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=643419 +--> +<head> + <title>Test getPointAtLength</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var p1 = document.getElementById("p1"); + var point = p1.getPointAtLength(200); + is(point.x, 200); + is(point.y, 50); + + // set the pathLength to twice its actual length + // and check that makes no difference + p1.setAttribute("pathLength", "800"); + point = p1.getPointAtLength(200); + is(point.x, 200); + is(point.y, 50); + + SimpleTest.finish(); +} + +window.addEventListener("load", run); +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=643419">Mozilla Bug 643419</a> +<p id="display"></p> +<div id="content"> + + <svg xmlns="http://www.w3.org/2000/svg" width="750"> + <defs> + <path id="p1" d="M 0 50 h 400"/> + </defs> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pointer-events-1a.xhtml b/dom/svg/test/test_pointer-events-1a.xhtml new file mode 100644 index 0000000000..b7bba026c2 --- /dev/null +++ b/dom/svg/test/test_pointer-events-1a.xhtml @@ -0,0 +1,27 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=619959 +--> +<head> + <title>Test 'pointer-events' handling</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run_tests(0)"> +<script class="testbody" src="pointer-events.js"></script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619959">Mozilla Bug 619959</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"></div> + + <svg xmlns="http://www.w3.org/2000/svg" id="svg"> + <rect id="rect" x="10" y="10" width="40" height="40" stroke-width="20"/> + <text id="text" x="190" y="50" font-size="40px" stroke-width="20">X</text> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pointer-events-1b.xhtml b/dom/svg/test/test_pointer-events-1b.xhtml new file mode 100644 index 0000000000..7cd353de4d --- /dev/null +++ b/dom/svg/test/test_pointer-events-1b.xhtml @@ -0,0 +1,27 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=619959 +--> +<head> + <title>Test 'pointer-events' handling</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run_tests(1)"> +<script class="testbody" src="pointer-events.js"></script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619959">Mozilla Bug 619959</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"></div> + + <svg xmlns="http://www.w3.org/2000/svg" id="svg"> + <rect id="rect" x="10" y="10" width="40" height="40" stroke-width="20"/> + <text id="text" x="190" y="50" font-size="40px" stroke-width="20">X</text> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pointer-events-2.xhtml b/dom/svg/test/test_pointer-events-2.xhtml new file mode 100644 index 0000000000..7f2e828521 --- /dev/null +++ b/dom/svg/test/test_pointer-events-2.xhtml @@ -0,0 +1,71 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500174 +--> +<head> + <title>Test Pointer Events</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + pass(); + + document.getElementById("circle").setAttribute("clip-path", "#(unknown)"); + + pass(); + + SimpleTest.finish(); +} + +function pass() { + let div = document.getElementById("div"); + // Get the coords of the origin of the SVG canvas: + let originX = div.offsetLeft; + let originY = div.offsetTop; + let circle = document.getElementById("circle"); + + let elementFromPoint = document.elementFromPoint(originX + 55, originY + 55); + is(elementFromPoint, circle, 'Over circle stroke with pointer-events="all"'); + + elementFromPoint = document.elementFromPoint(originX + 205, originY + 55); + is(elementFromPoint, circle, "Over foreignObject, outside clip path"); + + elementFromPoint = document.elementFromPoint(originX + 225, originY + 75); + // XXX disabled. See See https://bugzilla.mozilla.org/show_bug.cgi?id=580983#c116 + // is(elementFromPoint, path, 'Over foreignObject, inside clip path'); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500174">Mozilla Bug 500174</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"> + </div> + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" id="svg"> + <defs> + <clipPath id="clip"> + <rect x="20" y="20" width="30" height="30"/> + </clipPath> + </defs> + <rect id="bad" width="100%" height="100%" fill="blue"/> + <circle id="circle" cx="50%" cy="50%" r="500" stroke-width="500" fill="none" pointer-events="all"/> + <foreignObject id="fo" x="200" y="50" width="50" height="50" clip-path="url(#clip)"> + <svg> + <path id="path" d="M0,0 H50 V50 H0 Z" fill="green"/> + </svg> + </foreignObject> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pointer-events-3.xhtml b/dom/svg/test/test_pointer-events-3.xhtml new file mode 100644 index 0000000000..a66297fc45 --- /dev/null +++ b/dom/svg/test/test_pointer-events-3.xhtml @@ -0,0 +1,54 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=762679 +--> +<head> + <title>Test pointer events for small objects scaled up</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var div = document.getElementById("div"); + // Get the coords of the origin of the SVG canvas: + var originX = div.offsetLeft; + var originY = div.offsetTop; + var circle = document.getElementById("circle"); + + var elementFromPoint = document.elementFromPoint(originX + 150, originY + 52); + is(elementFromPoint, circle, "Top of circle should hit"); + + elementFromPoint = document.elementFromPoint(originX + 249, originY + 150); + is(elementFromPoint, circle, "Right of circle should hit"); + + elementFromPoint = document.elementFromPoint(originX + 150, originY + 249); + is(elementFromPoint, circle, "Bottom of circle should hit"); + + elementFromPoint = document.elementFromPoint(originX + 51, originY + 150); + is(elementFromPoint, circle, "Left of circle should hit"); + + SimpleTest.finish(); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=762679">Mozilla Bug 762679</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"> + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="400" id="svg"> + <circle id="circle" cx="1.5" cy="1.5" r="1" transform="scale(100, 100)"/> + </svg> + </div> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pointer-events-4.xhtml b/dom/svg/test/test_pointer-events-4.xhtml new file mode 100644 index 0000000000..e6600bd0f8 --- /dev/null +++ b/dom/svg/test/test_pointer-events-4.xhtml @@ -0,0 +1,109 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=820506 +--> +<head> + <title>Test pointer events with clipPath</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var div = document.getElementById("div"); + // Get the coords of the origin of the SVG canvas: + var originX = div.offsetLeft; + var originY = div.offsetTop; + var r1 = document.getElementById("r1"); + var r2 = document.getElementById("r2"); + var element; + var background = document.getElementById("background"); + + // Test r1 just outsite the clip area: + + element = document.elementFromPoint(originX + 19, originY + 19); + is(element, background, "Should not hit top-left of r1"); + + element = document.elementFromPoint(originX + 101, originY + 19); + is(element, background, "Should not hit top-right of r1"); + + element = document.elementFromPoint(originX + 101, originY + 101); + is(element, background, "Should not hit bottom-right of r1"); + + element = document.elementFromPoint(originX + 19, originY + 101); + is(element, background, "Should not hit bottom-left of r1"); + + // Test r1 just inside the clip area: + + element = document.elementFromPoint(originX + 21, originY + 21); + is(element, r1, "Should hit top-left of r1"); + + element = document.elementFromPoint(originX + 99, originY + 21); + is(element, r1, "Should hit top-right of r1"); + + element = document.elementFromPoint(originX + 99, originY + 99); + is(element, r1, "Should hit bottom-right of r1"); + + element = document.elementFromPoint(originX + 21, originY + 99); + is(element, r1, "Should hit bottom-left of r1"); + + // Test r2 just outsite the clip area: + + element = document.elementFromPoint(originX + 109, originY + 19); + is(element, background, "Should not hit top-left of r2"); + + element = document.elementFromPoint(originX + 201, originY + 19); + is(element, background, "Should not hit top-right of r2"); + + element = document.elementFromPoint(originX + 201, originY + 101); + is(element, background, "Should not hit bottom-right of r2"); + + element = document.elementFromPoint(originX + 109, originY + 101); + is(element, background, "Should not hit bottom-left of r2"); + + // Test r2 just inside the clip area: + + element = document.elementFromPoint(originX + 121, originY + 21); + is(element, r2, "Should hit top-left of r2"); + + element = document.elementFromPoint(originX + 199, originY + 21); + is(element, r2, "Should hit top-right of r2"); + + element = document.elementFromPoint(originX + 199, originY + 99); + is(element, r2, "Should hit bottom-right of r2"); + + element = document.elementFromPoint(originX + 121, originY + 99); + is(element, r2, "Should hit bottom-left of r2"); + + SimpleTest.finish(); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500174">Mozilla Bug 500174</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"> + </div> + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" id="svg"> + <clipPath id="cp1" clipPathUnits="userSpaceOnUse"> + <rect x="20" y="20" width="80" height="80"/> + </clipPath> + <clipPath id="cp2" clipPathUnits="objectBoundingBox"> + <rect x="0.1" y="0.1" width="0.8" height="0.8"/> + </clipPath> + <rect id="background" width="100%" height="100%" fill="blue"/> + <rect id="r1" x="10" y="10" width="100" height="100" clip-path="url(#cp1)"/> + <rect id="r2" x="110" y="10" width="100" height="100" clip-path="url(#cp2)"/> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pointer-events-6.xhtml b/dom/svg/test/test_pointer-events-6.xhtml new file mode 100644 index 0000000000..e6e40aedb1 --- /dev/null +++ b/dom/svg/test/test_pointer-events-6.xhtml @@ -0,0 +1,69 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=500174 +--> +<head> + <title>Test pointer events for clip-path on non-SVG elements</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var div = document.getElementById("div"); + // Get the coords of the origin of the SVG canvas: + var originX = div.offsetLeft; + var originY = div.offsetTop; + var elementFromPoint; + + elementFromPoint = document.elementFromPoint(originX + 18, originY + 30); + isnot(elementFromPoint, div, "Outside left edge of clipped area"); + + elementFromPoint = document.elementFromPoint(originX + 22, originY + 30); + is(elementFromPoint, div, "Inside left edge of clipped area"); + + elementFromPoint = document.elementFromPoint(originX + 30, originY + 18); + isnot(elementFromPoint, div, "Outside top edge of clipped area"); + + elementFromPoint = document.elementFromPoint(originX + 30, originY + 22); + is(elementFromPoint, div, "Inside top edge of clipped area"); + + elementFromPoint = document.elementFromPoint(originX + 42, originY + 30); + isnot(elementFromPoint, div, "Outside right edge of clipped area"); + + elementFromPoint = document.elementFromPoint(originX + 38, originY + 30); + is(elementFromPoint, div, "Inside right edge of clipped area"); + + elementFromPoint = document.elementFromPoint(originX + 30, originY + 42); + isnot(elementFromPoint, div, "Outside bottom edge of clipped area"); + + elementFromPoint = document.elementFromPoint(originX + 30, originY + 38); + is(elementFromPoint, div, "Inside bottom edge of clipped area"); + + SimpleTest.finish(); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500174">Mozilla Bug 500174</a> +<p id="display"></p> +<div id="content"> + + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" id="svg"> + <defs> + <clipPath id="clip"> + <rect x="20" y="20" width="20" height="20"/> + </clipPath> + </defs> + </svg> + <div id="div" style="width:100px; height:100px; clip-path:url(#clip)"></div> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_pointer-events-7.xhtml b/dom/svg/test/test_pointer-events-7.xhtml new file mode 100644 index 0000000000..d254c40192 --- /dev/null +++ b/dom/svg/test/test_pointer-events-7.xhtml @@ -0,0 +1,65 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1119698 +--> +<head> + <title>Test pointer events with image</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var div = document.getElementById("div"); + // Get the coords of the origin of the SVG canvas: + var originX = div.offsetLeft; + var originY = div.offsetTop; + var image4 = document.getElementById("image4"); + var image5 = document.getElementById("image5"); + var element; + var background = document.getElementById("background"); + + element = document.elementFromPoint(originX + 20, originY + 20); + is(element, background, "Should not hit visibility:hidden image by default"); + + element = document.elementFromPoint(originX + 120, originY + 20); + is(element, background, "Should not hit pointer-events:none image"); + + element = document.elementFromPoint(originX + 220, originY + 20); + is(element, background, "Should not hit pointer-events:visible visibility:hidden image"); + + element = document.elementFromPoint(originX + 320, originY + 20); + is(element, image4, "Should hit pointer-events:painted visibility:hidden image"); + + element = document.elementFromPoint(originX + 420, originY + 20); + is(element, image5, "Should hit pointer-events:stroke visibility:hidden image"); + + SimpleTest.finish(); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1119698">Mozilla Bug 1119698</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"> + </div> + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" id="svg"> + <rect id="background" width="100%" height="100%" fill="blue"/> + <image x="10" y="10" width="100" height="100" visibility="hidden" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAxJREFUCNdj+M/AAAADAQEAGN2NsAAAAABJRU5ErkJggg"/> + <image x="110" y="10" width="100" height="100" pointer-events="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAxJREFUCNdj+M/AAAADAQEAGN2NsAAAAABJRU5ErkJggg"/> + <image x="210" y="10" width="100" height="100" pointer-events="visible" visibility="hidden" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAxJREFUCNdj+M/AAAADAQEAGN2NsAAAAABJRU5ErkJggg"/> + <image id="image4" x="310" y="10" width="100" height="100" pointer-events="painted" visibility="hidden" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAxJREFUCNdj+M/AAAADAQEAGN2NsAAAAABJRU5ErkJggg"/> + <image id="image5" x="410" y="10" width="100" height="100" pointer-events="stroke" visibility="hidden" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAxJREFUCNdj+M/AAAADAQEAGN2NsAAAAABJRU5ErkJggg"/> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_scientific.html b/dom/svg/test/test_scientific.html new file mode 100644 index 0000000000..8bc0d67631 --- /dev/null +++ b/dom/svg/test/test_scientific.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=302971 +--> +<head> + <title>Test for Bug 302971</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=302971">Mozilla Bug 302971</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="scientific-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + function runTests() { + var doc = $("svg").contentWindow.document; + var rect = doc.getElementById("rect"); + + // ordinary + + rect.setAttribute("stroke-width", "5"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "5px", "Ordinary"); + + // valid exponential notation + + rect.setAttribute("stroke-width", "4E1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "40px", "Exponent"); + + rect.setAttribute("stroke-width", "6e1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "60px", "Lower-case Exponent"); + + rect.setAttribute("stroke-width", "2E+1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "20px", "Positive Exponent"); + + rect.setAttribute("stroke-width", "100E-1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "10px", "Negative Exponent"); + + rect.setAttribute("stroke-width", "0.7E1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "7px", "Floating Point with Exponent"); + + rect.setAttribute("stroke-width", "50.0E-1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "5px", "Floating Point with Negative Exponent"); + + rect.setAttribute("stroke-width", "0.8E+1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "8px", "Floating Point with Positive Exponent"); + + rect.setAttribute("stroke-width", "4E1px"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "40px", "Units"); + + // check units that begin with the letter e + + var font_size = doc.defaultView.getComputedStyle(rect).getPropertyValue("font-size"); + + rect.setAttribute("stroke-width", "1em"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), font_size, "em Units"); + + // invalid exponential notation + + rect.setAttribute("stroke-width", "1E1.1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "1px", "Floating Point Exponent"); + + rect.setAttribute("stroke-width", "E1"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "1px", "No Mantissa"); + + rect.setAttribute("stroke-width", "1 e"); + is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "1px", "Spaces"); + + SimpleTest.finish(); + } + + window.addEventListener("load", runTests); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_selectSubString.xhtml b/dom/svg/test/test_selectSubString.xhtml new file mode 100644 index 0000000000..6755b65c56 --- /dev/null +++ b/dom/svg/test/test_selectSubString.xhtml @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=398825 +--> +<head> + <title>Test for Bug 398825</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=398825">Mozilla Bug 398825</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="selectSubString-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTests() { + var document = $("svg").contentWindow.document; + var text = document.getElementById("text"); + + function expectThrow(charnum, nchars) { + try { + text.selectSubString(charnum, nchars); + ok(false, + "text.selectSubString(" + charnum + "," + nchars + ") " + + "should have thrown"); + } catch (e) { + is(e.name, "IndexSizeError", + "expected an index error for " + + "text.selectSubString(" + charnum + "," + nchars + ")"); + is(e.code, DOMException.INDEX_SIZE_ERR, + "expected an index error for " + + "text.selectSubString(" + charnum + "," + nchars + ")"); + } + } + + function expectNoThrow(charnum, nchars, expected) { + try { + text.selectSubString(charnum, nchars); + ok(true, + "text.selectSubString(" + charnum + "," + nchars + ") " + + "should not have thrown"); + } catch (e) { + ok(false, + "unexpected exception for " + + "text.selectSubString(" + charnum + "," + nchars + ")"); + } + } + + expectThrow(100, 2); + expectThrow(100, 0); + expectThrow(3, 0); + expectThrow(3, 100); + expectThrow(3, 100); + expectThrow(100, 100); + + expectNoThrow(1, 100); + expectNoThrow(2, 100); + expectNoThrow(1, 3); + expectNoThrow(0, 4); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTests); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_stroke-hit-testing.xhtml b/dom/svg/test/test_stroke-hit-testing.xhtml new file mode 100644 index 0000000000..7fc9329260 --- /dev/null +++ b/dom/svg/test/test_stroke-hit-testing.xhtml @@ -0,0 +1,66 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=676001 +--> +<head> + <title>Test hit-testing of stroke</title> + <style> + +:hover { stroke: lime; } + + </style> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var div = document.getElementById("div"); + var line = document.getElementById("line"); + var circle = document.getElementById("circle"); + var offsetX = div.offsetLeft; + var offsetY = div.offsetTop; + var got; + + // line + got = document.elementFromPoint(offsetX + 116, offsetY + 103); + is(got, line, "Should hit line (1)"); + + got = document.elementFromPoint(offsetX + 123, offsetY + 108); + is(got, line, "Should hit line (2)"); + + // circle + got = document.elementFromPoint(offsetX + 188, offsetY + 158); + is(got, circle, "Should hit circle (1)"); + + got = document.elementFromPoint(offsetX + 185, offsetY + 162); + is(got, circle, "Should hit circle (2)"); + + SimpleTest.finish(); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=676001">Mozilla Bug 676001</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"></div> + <svg xmlns="http://www.w3.org/2000/svg" id="svg" width="500" height="300"> + <line id="line" x1="100" y1="100" x2="600" y2="180" + stroke="red" stroke-width="40"/> + <!-- the circle test points need to be within the mochitest test harness + viewport for test content in order for elementFromPoint to work --> + <circle id="circle" cx="100" cy="150" r="100" + fill="none" stroke="red" stroke-width="40"/> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_stroke-linecap-hit-testing.xhtml b/dom/svg/test/test_stroke-linecap-hit-testing.xhtml new file mode 100644 index 0000000000..7e08e88ad0 --- /dev/null +++ b/dom/svg/test/test_stroke-linecap-hit-testing.xhtml @@ -0,0 +1,45 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=589648 +--> +<head> + <title>Test hit-testing of line caps</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var div = document.getElementById("div"); + var x = div.offsetLeft; + var y = div.offsetTop; + var got, expected; + + got = document.elementFromPoint(5 + x, 5 + y); + expected = document.getElementById("zero-length-square-caps"); + is(got, expected, "Check hit on zero length subpath's square caps"); + + SimpleTest.finish(); +} + +]]> +</script> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500174">Mozilla Bug 500174</a> +<p id="display"></p> +<div id="content"> + + <div width="100%" height="1" id="div"></div> + <svg xmlns="http://www.w3.org/2000/svg" id="svg" width="400" height="300"> + <path id="zero-length-square-caps" stroke="blue" stroke-width="50" + stroke-linecap="square" d="M25,25 L25,25"/> + </svg> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_style_sheet.html b/dom/svg/test/test_style_sheet.html new file mode 100644 index 0000000000..955dda1ac8 --- /dev/null +++ b/dom/svg/test/test_style_sheet.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Test for Bug 1239128</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1239128">Mozilla Bug 1239128</a> +<p id="display"</p> + +<svg> + <style>svg { fill: blue; }</style> +</svg> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +var style = document.querySelector("style"); + +var exceptionThrown = false; +try { + is(style.sheet.cssRules[0].cssText, "svg { fill: blue; }", + "Should get the fill: blue rule back"); +} catch (e) { + exceptionThrown = true; +} + +ok(!exceptionThrown, "Should be able to access data: <style> stylesheet"); +</script> +</pre> diff --git a/dom/svg/test/test_switch.xhtml b/dom/svg/test/test_switch.xhtml new file mode 100644 index 0000000000..d1fa546e26 --- /dev/null +++ b/dom/svg/test/test_switch.xhtml @@ -0,0 +1,99 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=484176 +--> +<head> + <title>Test SVG Switch</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=484176">Mozilla Bug 484176</a> +<p id="display"></p> +<div id="content"></div> + +<iframe id="svg" src="switch-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + <![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var test = 1; + +function checkBounds(element, x, y, w, h) { + var bbox = element.getBBox(); + var name = element.nodeName; + + is(bbox.x, x, test + " " + name + ".getBBox().x"); + is(bbox.y, y, test + " " + name + ".getBBox().y"); + is(bbox.width, w, test + " " + name + ".getBBox().width"); + is(bbox.height, h, test + " " + name + ".getBBox().height"); + ++test; +} + +function checkWidth(element, w) { + var bbox = element.getBBox(); + var name = element.nodeName; + + is(bbox.width, w, test + " " + name + ".getBBox().width"); + ++test; +} + +function run() { + // Set accept_languages to something we know + SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", "en-gb,en,it"]]}, run1); +} + +function run1() { + try { + var doc = $("svg").contentDocument; + var s = doc.getElementById("s"); + var first = doc.getElementById("first"); + var second = doc.getElementById("second"); + var third = doc.getElementById("third"); + + first.setAttribute("systemLanguage", "fr"); + + /* test for an exact match */ + second.setAttribute("systemLanguage", "en-gb"); + checkWidth(s, 50); + + /* test for a close match i.e. the same language prefix */ + second.setAttribute("systemLanguage", "en-us"); + checkWidth(s, 50); + + /* test that we pick the best match */ + second.setAttribute("systemLanguage", "it"); + checkWidth(s, 50); + + /* test that we use the default if nothing matches */ + second.setAttribute("systemLanguage", "fr"); + checkWidth(s, 80); + + /* test we still ignore non-matches */ + second.removeAttribute("systemLanguage"); + third.setAttribute("systemLanguage", "fr"); + checkWidth(s, 50); + + /* check what happens if nothing matches */ + second.setAttribute("systemLanguage", "fr"); + checkWidth(s, 0); + + /* test that we pick the best match */ + first.setAttribute("systemLanguage", "en"); + second.setAttribute("systemLanguage", "en-gb"); + checkWidth(s, 50); + } finally { + SimpleTest.finish(); + } +} + +window.addEventListener("load", run); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_tabindex.html b/dom/svg/test/test_tabindex.html new file mode 100644 index 0000000000..65315420bc --- /dev/null +++ b/dom/svg/test/test_tabindex.html @@ -0,0 +1,103 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SVG tabIndex - Bug 778654</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> +</head> +<body> +<svg xmlns="http://www.w3.org/2000/svg" overflow="visible"> + <foreignObject id="f" x="0" y="0" width="200" height="60" tabindex="0"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <p>Here is a paragraph that requires word wrap</p> + </body> + </foreignObject> + <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabindex="1"/> + <text id="t" x="0" y="200" tabindex="2"> + This is SVG text + </text> + <a xlink:href="#" id="l1" tabindex="3"> + <circle cx="10" cy="230" r="10"/> + </a> + <a id="l2" tabindex="4"> + <circle cx="10" cy="260" r="10"/> + </a> + <rect id="r6" x="0" y="70" width="100" height="100" fill="yellow" tabindex="6"/> + <rect id="r7" x="0" y="70" width="100" height="100" fill="yellow" tabindex="7"/> +</svg> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function main() { + var f = document.getElementById("f"); + var r = document.getElementById("r"); + var t = document.getElementById("t"); + var l1 = document.getElementById("l1"); + var l2 = document.getElementById("l2"); + const isMac = ("nsILocalFileMac" in SpecialPowers.Ci); + + try { + // Step 1: Checking by assigning tabIndex + is(f.tabIndex, 0, "foreignObject initial tabIndex"); + f.tabIndex = 1; + is(f.tabIndex, 1, "foreignObject tabIndex is set to 1"); + + is(r.tabIndex, 1, "rect initial tabIndex"); + r.tabIndex = 2; + is(r.tabIndex, 2, "rect tabIndex is set to 2"); + + is(t.tabIndex, 2, "text initial tabIndex"); + t.tabIndex = 3; + is(t.tabIndex, 3, "text is set to 3"); + + is(l1.tabIndex, 3, "link initial tabIndex"); + l1.tabIndex = 4; + is(l1.tabIndex, 4, "link is set to 4"); + + is(l2.tabIndex, 4, "non-link initial tabIndex"); + l2.tabIndex = 5; + is(l2.tabIndex, 5, "non-link is set to 4"); + + // Step 2: Checking by triggering TAB event + is(document.activeElement.tabIndex, -1, "In the beginning, the active element tabindex is -1"); + + synthesizeKey("KEY_Tab"); + is(document.activeElement.tabIndex, 1, "The active element tabindex is 1"); + + synthesizeKey("KEY_Tab"); + is(document.activeElement.tabIndex, 2, "The active element tabindex is 2"); + + synthesizeKey("KEY_Tab"); + is(document.activeElement.tabIndex, 3, "The active element tabindex is 3"); + + synthesizeKey("KEY_Tab"); + // On Mac, SVG link elements should not be focused. + if (isMac) { + is(document.activeElement.tabIndex, 6, "The active element tabindex is 6"); + } else { + is(document.activeElement.tabIndex, 4, "The active element tabindex is 4"); + } + + synthesizeKey("KEY_Tab"); + // On Mac, SVG link elements should not be focused. + if (isMac) { + is(document.activeElement.tabIndex, 7, "The active element tabindex is 7"); + } else { + is(document.activeElement.tabIndex, 5, "The active element tabindex is 5"); + } + } catch (e) { + ok(false, "Got unexpected exception" + e); + } + + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(main); + +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_tearoff_with_cc.html b/dom/svg/test/test_tearoff_with_cc.html new file mode 100644 index 0000000000..86facf40da --- /dev/null +++ b/dom/svg/test/test_tearoff_with_cc.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1288228 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1288228</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + /** Test for Bug 1288228 **/ + /* Note: the crash in bug 1288228 doesn't happen reliably (and only happens + * after several reloads). So, we reload the iframe 10 times, and then call + * it good if we haven't crashed. + */ + const maxReloads = 10; + let remainingReloads = maxReloads; + + /* The helper-file in the iframe will notify us after it's performed its + * potentially-crash-triggering tweak. At that point, we reload the iframe + * and wait for it to notify again (or we simply finish, if we've completed + * all of the reloads we planned to do). + */ + window.addEventListener("message", reloadIframe); + + function reloadIframe() { + if (--remainingReloads == 0) { + ok(true, "Didn't crash!"); + SimpleTest.finish(); + } else { + var frame = document.getElementById("testIframe"); + frame.setAttribute("src", ""); + frame.setAttribute("src", "tearoff_with_cc_helper.html"); + } + } + SimpleTest.waitForExplicitFinish(); + </script> +</head> +<body onload="reloadIframe()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288228"> + Mozilla Bug 1288228 +</a> +<p id="display"> + <iframe id="testIframe"></iframe> +</p> +</body> +</html> diff --git a/dom/svg/test/test_text.html b/dom/svg/test/test_text.html new file mode 100644 index 0000000000..c1b99a9175 --- /dev/null +++ b/dom/svg/test/test_text.html @@ -0,0 +1,189 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=392233 +--> +<head> + <title>Test for Bug 392233</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=392233">Mozilla Bug 392233</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="text-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTest() { + var doc = $("svg").contentWindow.document; + var text1 = doc.getElementById("text1"); + var text2 = doc.getElementById("text2"); + var text3 = doc.getElementById("text3"); + var text4 = doc.getElementById("text4"); + + var charWidth = text1.getSubStringLength(0, 1); + + function isPoint(pt1, x, y, str) { + is(pt1.x, x, str + " x"); + is(pt1.y, y, str + " y"); + } + + function isPointFuzzy(pt1, x, y, str) { + isfuzzy(pt1.x, x, 0.05, str + " x"); + isfuzzy(pt1.y, y, 0.05, str + " y"); + } + + function ymost(r) { + return r.y + r.height; + } + + function xmost(r) { + return r.x + r.width; + } + + var p = text1.getStartPositionOfChar(0); + + // Simple horizontal string + + is(text1.getNumberOfChars(), 3, "text1 length"); + ok(text1.getComputedTextLength() > 0, "text1 measured length"); + is(text1.getComputedTextLength(), text1.getSubStringLength(0, 3), "text1 substring length"); + isPoint(text1.getStartPositionOfChar(0), 5, 25, "text1 char 0 start offset"); + isPointFuzzy(text1.getStartPositionOfChar(1), 5 + charWidth, 25, "text1 char 1 start offset"); + isPointFuzzy(text1.getStartPositionOfChar(2), 5 + 2 * charWidth, 25, "text1 char 2 start offset"); + isPointFuzzy(text1.getEndPositionOfChar(0), 5 + charWidth, 25, "text1 char 0 end offset"); + isPointFuzzy(text1.getEndPositionOfChar(1), 5 + 2 * charWidth, 25, "text1 char 1 end offset"); + isPointFuzzy(text1.getEndPositionOfChar(2), 5 + 3 * charWidth, 25, "text1 char 2 end offset"); + isfuzzy(text1.getExtentOfChar(0).x, 5, 0.01, "text1 char 0 extent x"); + is(text1.getExtentOfChar(0).width, text1.getSubStringLength(0, 1), "text1 char 0 extent width"); + ok(text1.getExtentOfChar(0).y < 25, "text1 char 0 extent y"); + ok(ymost(text1.getExtentOfChar(0)) > 25, "text1 char 0 extent height"); + isfuzzy(text1.getExtentOfChar(1).x, 5 + charWidth, 0.01, "text1 char 1 extent x"); + is(text1.getExtentOfChar(1).width, text1.getSubStringLength(0, 1), "text1 char 1 extent width"); + is(text1.getExtentOfChar(1).y, text1.getExtentOfChar(0).y, "text1 char 0/1 extent y"); + is(text1.getExtentOfChar(1).height, text1.getExtentOfChar(0).height, "text1 char 0/1 extent height"); + isfuzzy(text1.getExtentOfChar(2).x, 5 + 2 * charWidth, 0.02, "text1 char 2 extent x"); + is(text1.getExtentOfChar(2).width, text1.getSubStringLength(0, 1), "text1 char 2 extent width"); + is(text1.getExtentOfChar(2).y, text1.getExtentOfChar(0).y, "text1 char 0/2 extent y"); + is(text1.getExtentOfChar(2).height, text1.getExtentOfChar(0).height, "text1 char 0/2 extent height"); + is(text1.getRotationOfChar(0), 0, "text1 char 0 rotation"); + is(text1.getRotationOfChar(1), 0, "text1 char 0 rotation"); + is(text1.getRotationOfChar(2), 0, "text1 char 0 rotation"); + p.x = 5 + 0.1; + p.y = 25; + is(text1.getCharNumAtPosition(p), 0, "text1 finding char 0 left edge"); + p.x = 5 + charWidth - 0.1; + is(text1.getCharNumAtPosition(p), 0, "text1 finding char 0 on right"); + p.x = 5 - 0.1; + is(text1.getCharNumAtPosition(p), -1, "text1 finding no char on left"); + p.x = 5 + 0.1; + p.y = text1.getExtentOfChar(0).y - 0.1; + is(text1.getCharNumAtPosition(p), -1, "text1 finding no char on top"); + p.y = text1.getExtentOfChar(0).y + 0.1; + is(text1.getCharNumAtPosition(p), 0, "text1 finding char 0 top edge"); + p.x = 5 + 3 * charWidth - 0.1; + is(text1.getCharNumAtPosition(p), 2, "text1 finding char 2 top edge"); + p.x = 5 + 3 * charWidth + 0.1; + is(text1.getCharNumAtPosition(p), -1, "text1 finding no char on right"); + + // Simple rotated-90 string ... width might change because of hinting + + charWidth = text2.getSubStringLength(0, 1); + + is(text2.getNumberOfChars(), 3, "text2 length"); + ok(text2.getComputedTextLength() > 0, "text2 measured length"); + is(text2.getComputedTextLength(), text2.getSubStringLength(0, 3), "text2 substring length"); + isPointFuzzy(text2.getStartPositionOfChar(0), 100, 125, "text2 char 0 start offset"); + isPointFuzzy(text2.getStartPositionOfChar(1), 100, 125 + charWidth, "text2 char 1 start offset"); + isPointFuzzy(text2.getStartPositionOfChar(2), 100, 125 + 2 * charWidth, "text2 char 2 start offset"); + isPointFuzzy(text2.getEndPositionOfChar(0), 100, 125 + charWidth, "text2 char 0 end offset"); + isPointFuzzy(text2.getEndPositionOfChar(1), 100, 125 + 2 * charWidth, "text2 char 1 end offset"); + isPointFuzzy(text2.getEndPositionOfChar(2), 100, 125 + 3 * charWidth, "text2 char 2 end offset"); + isfuzzy(text2.getExtentOfChar(0).y, 125, 0.01, "text2 char 0 extent y"); + isfuzzy(text2.getExtentOfChar(0).height, charWidth, 0.000001, "text2 char 0 extent height"); + ok(text2.getExtentOfChar(0).width < 100, "text2 char 0 extent x"); + ok(xmost(text2.getExtentOfChar(0)) > 100, "text2 char 0 extent width"); + isfuzzy(text2.getExtentOfChar(1).y, 125 + charWidth, 0.01, "text2 char 1 extent x"); + isfuzzy(text2.getExtentOfChar(1).height, text2.getSubStringLength(0, 1), 0.000001, "text2 char 1 extent width"); + is(text2.getExtentOfChar(1).x, text2.getExtentOfChar(0).x, "text2 char 0/1 extent y"); + is(text2.getExtentOfChar(1).width, text2.getExtentOfChar(0).width, "text2 char 0/1 extent height"); + isfuzzy(text2.getExtentOfChar(2).y, 125 + 2 * charWidth, 0.01, "text2 char 2 extent x"); + isfuzzy(text2.getExtentOfChar(2).height, text2.getSubStringLength(0, 1), 0.000001, "text2 char 2 extent width"); + is(text2.getExtentOfChar(2).x, text2.getExtentOfChar(0).x, "text2 char 0/2 extent y"); + is(text2.getExtentOfChar(2).width, text2.getExtentOfChar(0).width, "text2 char 0/2 extent height"); + is(text2.getRotationOfChar(0), 90, "text2 char 0 rotation"); + is(text2.getRotationOfChar(1), 90, "text2 char 0 rotation"); + is(text2.getRotationOfChar(2), 90, "text2 char 0 rotation"); + p.y = 125 + 0.1; + p.x = 100; + is(text2.getCharNumAtPosition(p), 0, "text2 finding char 0 top edge"); + p.y = 125 + charWidth - 0.1; + is(text2.getCharNumAtPosition(p), 0, "text2 finding char 0 on bottom"); + p.y = 125 - 0.1; + is(text2.getCharNumAtPosition(p), -1, "text2 finding no char on top"); + p.y = 125 + 0.1; + p.x = text2.getExtentOfChar(0).x - 0.1; + is(text2.getCharNumAtPosition(p), -1, "text2 finding no char on left"); + p.x = text2.getExtentOfChar(0).x + 0.1; + is(text2.getCharNumAtPosition(p), 0, "text2 finding char 0 left edge"); + p.y = 125 + 3 * charWidth - 0.1; + is(text2.getCharNumAtPosition(p), 2, "text2 finding char 2 bottom edge"); + p.y = 1225 + 3 * charWidth + 0.1; + is(text2.getCharNumAtPosition(p), -1, "text2 finding no char on bottom"); + + // Text along a thin rectangle path + + charWidth = text3.getSubStringLength(0, 1); + + is(text3.getNumberOfChars(), 26, "text3 length"); + ok(text3.getComputedTextLength() > 0, "text3 measured length"); + is(text3.getComputedTextLength(), text3.getSubStringLength(0, 26), "text3 substring length"); + + // character 12 should be on the bottom side + is(text3.getStartPositionOfChar(12).y, 253, "text3 char 12 start offset"); + is(text3.getEndPositionOfChar(12).y, 253, "text3 char 12 end offset"); + ok(text3.getExtentOfChar(12).y < 253, "text3 char 12 extent y"); + ok(ymost(text3.getExtentOfChar(12)) > 253, "text3 char 12 extent height"); + isfuzzy(text3.getRotationOfChar(12), 180, 0.001, "text3 char 12 rotation"); + p.x = text3.getExtentOfChar(12).x + 0.1; + p.y = ymost(text3.getExtentOfChar(12)) - 0.1; + is(text3.getCharNumAtPosition(p), 12, "text3 finding char 12"); + // This next test is tricky. The glyph for character 3 may overlap from the above + // but character 12 wins because it's the last to render + p.y = text3.getExtentOfChar(12).y + 0.1; + is(text3.getCharNumAtPosition(p), 12, "text3 finding last rendered char"); + + // character 25 should be beyond the end of the path + // Not sure what should happen here. Currently we throw, which seems wrong + // is(text3.getStartPositionOfChar(25).x, 0, "text3 char 25 start offset"); + + // Display:none string + + is(text4.getNumberOfChars(), 0, "text4 length"); + is(text4.getComputedTextLength(), 0, "text4 measured length"); + is(text4.getSubStringLength(0, 3), 0, "text4 substring length"); + p = text1.getStartPositionOfChar(0); + is(text4.getCharNumAtPosition(p), -1, "text4 shouldn't find rendered char"); +} + +function runTests() { + runTest(); + + var doc = $("svg").contentWindow.document; + doc.getElementById("g").setAttribute("transform", "scale(2) rotate(90 200 200)"); + + runTest(); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTests); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_text_2.html b/dom/svg/test/test_text_2.html new file mode 100644 index 0000000000..380de92dde --- /dev/null +++ b/dom/svg/test/test_text_2.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=655877 +--> +<head> + <meta charset=UTF-8> + <title>Test for Bug 655877</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=655877">Mozilla Bug 655877</a> +<p id="display"></p> +<div id="content"> + <svg width="400" height="200"> + <text x="100" y="100" style="font: 16px sans-serif">abc אבג 123 דהו defg</text> + </svg> +</div> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function close(x, y, message) { + if (Math.abs(x - y) < 1e-4) { + ok(true, message); + } else { + // Use is() so that the difference is actually printed in the error message + is(x, y, message); + } +} + +function runTest() { + var text = document.querySelector("text"); + + // there are only 20 addressable characters + is(text.getNumberOfChars(), 20, "getNumberOfChars"); + + var sum, total = text.getComputedTextLength(); + + close(text.getSubStringLength(0, 20), total, "getSubStringLength all"); + + // add the advance of each glyph + sum = 0; + for (var i = 0; i < 20; i++) { + sum += text.getSubStringLength(i, 1); + } + close(sum, total, "sum getSubStringLength 1"); + + // split the text up into three chunks and add them together + close(text.getSubStringLength(0, 6) + + text.getSubStringLength(6, 8) + + text.getSubStringLength(14, 6), total, "sum getSubStringLength 2"); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTest); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_text_dirty.html b/dom/svg/test/test_text_dirty.html new file mode 100644 index 0000000000..dfcef6c811 --- /dev/null +++ b/dom/svg/test/test_text_dirty.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=886230 +--> +<head> + <title>Test for Bug 886230</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=886230">Mozilla Bug 886230</a> +<p id="display"> + <svg> + <mask id="m"><text id="t">x</text></mask> + <rect width="600" height="400" mask="url(#m)"/> + </svg> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +async function runTest() { + var svgText = document.getElementById("t"); + + // Dirty the frames. + document.getElementById("display").style.width = "700px"; + svgText.firstChild.remove(); + + // Paint without flushing layout. If the test fails, we'll trigger + // an assertion. + await SpecialPowers.snapshotWindowWithOptions(window, undefined, undefined, { DRAWWINDOW_DO_NOT_FLUSH: true }); + + ok(true); + SimpleTest.finish(); +} + +window.addEventListener("load", runTest); + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_text_lengthAdjust.html b/dom/svg/test/test_text_lengthAdjust.html new file mode 100644 index 0000000000..21c2454451 --- /dev/null +++ b/dom/svg/test/test_text_lengthAdjust.html @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=569722 +--> +<head> + <meta charset=UTF-8> + <title>Test for Bug 569722</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=569722">Mozilla Bug 569722</a> +<p id="display"></p> +<div id="content"> + <svg width="400" height="200"> + <text x="0" y="100" style="font: 16px sans-serif">aaa</text> + </svg> +</div> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function close(x, y, message) { + ok(Math.abs(x - y) < 1e-4, message); +} + +function runTest() { + var text = document.querySelector("text"); + + // get the original length + var length = text.getComputedTextLength(); + + // get the original glyph positions + var startPositions = [], + endPositions = [], + extents = []; + for (let i = 0; i < 3; i++) { + startPositions.push(text.getStartPositionOfChar(i)); + endPositions.push(text.getEndPositionOfChar(i)); + extents.push(text.getExtentOfChar(i)); + } + + // widths should all be the same + is(extents[0].width, extents[1].width); + is(extents[0].width, extents[2].width); + + var checkCharNumAtPosition = function(x, y, i) { + var p = document.querySelector("svg").createSVGPoint(); + p.x = x; + p.y = y; + is(text.getCharNumAtPosition(p), i, "getCharNumAtPosition(" + i + ")"); + }; + + var checkPositions = function(start, end, width) { + for (let i = 0; i < 3; i++) { + // check their positions + close(text.getStartPositionOfChar(i).x, start[i], "start position of glyph " + i); + close(text.getEndPositionOfChar(i).x, end[i], "end position of glyph " + i); + close(text.getExtentOfChar(i).x, start[i], "left edge of extent of glyph " + i); + close(text.getExtentOfChar(i).width, width, "width of glyph " + i); + checkCharNumAtPosition((start[i] + end[i]) / 2, 100, i); + } + }; + + var w = extents[0].width; + + var doLengthAdjustSpacingTest = function() { + // getComputedTextLength should return the sum of the advances, and since + // we are just changing the positions of the glyphs, it should be the same + // as without a textLength="" attribute + close(text.getComputedTextLength(), length, "getComputedTextLength when lengthAdjust=\"spacing\""); + + // expected start and end positions of the glyphs + var start = [0, 50 - w / 2, 100 - w]; + var end = [w, 50 + w / 2, 100]; + checkPositions(start, end, w); + }; + + // switch to adjust glyph positions, using the default value of lengthAdjust="" + text.setAttribute("textLength", "100"); + doLengthAdjustSpacingTest(); + // then with an explicit lengthAdjust="spacing" + text.setAttribute("lengthAdjust", "spacing"); + doLengthAdjustSpacingTest(); + + // now test with lengthAdjust="spacingAndGlyphs" + text.setAttribute("lengthAdjust", "spacingAndGlyphs"); + + // now that each glyph is stretched, the total advance should be the textLength + close(text.getComputedTextLength(), 100, "getComputedTextLength when lengthAdjust=\"spacingAndGlyphs\""); + + // expected start and end positions of the glyphs + var start = [0, 33.3333, 66.6666]; + var end = [33.3333, 66.6666, 100]; + checkPositions(start, end, 33.3333); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTest); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_text_scaled.html b/dom/svg/test/test_text_scaled.html new file mode 100644 index 0000000000..eb64905157 --- /dev/null +++ b/dom/svg/test/test_text_scaled.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=655877 +--> +<head> + <title>Test for Bug 655877</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=655877">Mozilla Bug 655877</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="text-helper-scaled.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTest() { + var doc = $("svg").contentWindow.document; + var text1 = doc.getElementById("text1"); + var text2 = doc.getElementById("text2"); + + var charWidth = text1.getSubStringLength(0, 1); + + if (navigator.userAgent.indexOf("Linux") > -1 && charWidth == 241) { + // Workaround for a slight difference in 'charWidth' (i.e. the width of + // the 'a' char) on Linux build machines after bug 1342951. The issue + // doesn't reproduce locally on Ubuntu 17.04 so is particularly tricky to + // debug. + charWidth = 240; + } + + var epsilon = 0.001; + + function isClose(a, b, str) { + ok(Math.abs(a - b) < epsilon, str + " - " + b + " should be close to " + a); + } + + function isPointCloseX(pt1, x, y, str) { + isClose(pt1.x, x, str + " x"); + is(pt1.y, y, str + " y"); + } + + function ymost(r) { + return r.y + r.height; + } + + var p = text1.getStartPositionOfChar(0); + + // Simple horizontal string + + is(text1.getNumberOfChars(), 3, "text1 length"); + ok(text1.getComputedTextLength() > 0, "text1 measured length"); + is(text1.getComputedTextLength(), text1.getSubStringLength(0, 3), "text1 substring length"); + isPointCloseX(text1.getStartPositionOfChar(0), 10, 400, "text1 char 0 start offset"); + isPointCloseX(text1.getStartPositionOfChar(1), 10 + charWidth, 400, "text1 char 1 start offset"); + isPointCloseX(text1.getStartPositionOfChar(2), 10 + 2 * charWidth, 400, "text1 char 2 start offset"); + isPointCloseX(text1.getEndPositionOfChar(0), 10 + charWidth, 400, "text1 char 0 end offset"); + isPointCloseX(text1.getEndPositionOfChar(1), 10 + 2 * charWidth, 400, "text1 char 1 end offset"); + isPointCloseX(text1.getEndPositionOfChar(2), 10 + 3 * charWidth, 400, "text1 char 2 end offset"); + is(text1.getExtentOfChar(0).x, 10, "text1 char 0 extent x"); + is(text1.getExtentOfChar(0).width, text1.getSubStringLength(0, 1), "text1 char 0 extent width"); + ok(text1.getExtentOfChar(0).y < 400, "text1 char 0 extent y"); + ok(ymost(text1.getExtentOfChar(0)) > 400, "text1 char 0 extent height"); + isClose(text1.getExtentOfChar(1).x, 10 + charWidth, "text1 char 1 extent x"); + is(text1.getExtentOfChar(1).width, text1.getSubStringLength(0, 1), "text1 char 1 extent width"); + is(text1.getExtentOfChar(1).y, text1.getExtentOfChar(0).y, "text1 char 0/1 extent y"); + is(text1.getExtentOfChar(1).height, text1.getExtentOfChar(0).height, "text1 char 0/1 extent height"); + is(text1.getExtentOfChar(2).x, 10 + 2 * charWidth, "text1 char 2 extent x"); + is(text1.getExtentOfChar(2).width, text1.getSubStringLength(0, 1), "text1 char 2 extent width"); + is(text1.getExtentOfChar(2).y, text1.getExtentOfChar(0).y, "text1 char 0/2 extent y"); + is(text1.getExtentOfChar(2).height, text1.getExtentOfChar(0).height, "text1 char 0/2 extent height"); + is(text1.getRotationOfChar(0), 0, "text1 char 0 rotation"); + is(text1.getRotationOfChar(1), 0, "text1 char 0 rotation"); + is(text1.getRotationOfChar(2), 0, "text1 char 0 rotation"); + p.x = 10 + 0.1; + p.y = 400; + is(text1.getCharNumAtPosition(p), 0, "text1 finding char 0 left edge"); + p.x = 10 + charWidth - 0.1; + is(text1.getCharNumAtPosition(p), 0, "text1 finding char 0 on right"); + p.x = 10 - 0.1; + is(text1.getCharNumAtPosition(p), -1, "text1 finding no char on left"); + p.x = 10 + 0.1; + p.y = text1.getExtentOfChar(0).y - 0.1; + is(text1.getCharNumAtPosition(p), -1, "text1 finding no char on top"); + p.y = text1.getExtentOfChar(0).y + 0.1; + is(text1.getCharNumAtPosition(p), 0, "text1 finding char 0 top edge"); + p.x = 10 + 3 * charWidth - 0.1; + is(text1.getCharNumAtPosition(p), 2, "text1 finding char 2 top edge"); + p.x = 10 + 3 * charWidth + 0.1; + is(text1.getCharNumAtPosition(p), -1, "text1 finding no char on right"); + + // Text along a thin rectangle path + + charWidth = text2.getSubStringLength(0, 1); + + is(text2.getNumberOfChars(), 26, "text2 length"); + ok(text2.getComputedTextLength() > 0, "text2 measured length"); + is(text2.getComputedTextLength(), text2.getSubStringLength(0, 26), "text2 substring length"); + + // character 12 should be on the bottom side + is(text2.getStartPositionOfChar(12).y, 860, "text2 char 12 start offset"); + isfuzzy(text2.getEndPositionOfChar(12).y, 860, 0.001, "text2 char 12 end offset"); + ok(text2.getExtentOfChar(12).y < 860, "text2 char 12 extent y"); + ok(ymost(text2.getExtentOfChar(12)) > 860, "text2 char 12 extent height"); + isfuzzy(text2.getRotationOfChar(12), 180, 0.001, "text2 char 12 rotation"); + p.x = text2.getExtentOfChar(12).x + 0.1; + p.y = ymost(text2.getExtentOfChar(12)) - 0.1; + is(text2.getCharNumAtPosition(p), 12, "text2 finding char 12"); + // This next test is tricky. The glyph for character 3 may overlap from the above + // but character 12 wins because it's the last to render + p.y = text2.getExtentOfChar(12).y + 0.1; + is(text2.getCharNumAtPosition(p), 12, "text2 finding last rendered char"); +} + +function runTests() { + runTest(); + + var doc = $("svg").contentWindow.document; + doc.getElementById("g").setAttribute("transform", "rotate(90 200 200)"); + + runTest(); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTests); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_text_selection.html b/dom/svg/test/test_text_selection.html new file mode 100644 index 0000000000..7160461db9 --- /dev/null +++ b/dom/svg/test/test_text_selection.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=655877 +--> +<head> + <title>Test for Bug 655877</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=655877">Mozilla Bug 655877</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe src="text-helper-selection.svg" width="400" height="300"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +var svg, doc, win, dragstart, dragend; + +function drag(fromX, fromY, toX, toY, show) { + synthesizeMouse(doc.documentElement, fromX, fromY, { type: "mousemove" }, win); + synthesizeMouse(doc.documentElement, fromX, fromY, { type: "mousedown" }, win); + synthesizeMouse(doc.documentElement, toX, toY, { type: "mousemove" }, win); + synthesizeMouse(doc.documentElement, toX, toY, { type: "mouseup" }, win); + + if (show) { + dragstart.setAttribute("cx", fromX); + dragstart.setAttribute("cy", fromY); + dragstart.setAttribute("r", "4"); + dragend.setAttribute("cx", toX); + dragend.setAttribute("cy", toY); + dragend.setAttribute("r", "4"); + } +} + +function click(x, y) { + synthesizeMouse(doc.documentElement, x, y, { type: "mousemove" }, win); + synthesizeMouse(doc.documentElement, x, y, { type: "mousedown" }, win); + synthesizeMouse(doc.documentElement, x, y, { type: "mouseup" }, win); +} + +function selection_is(s, text) { + is(win.getSelection().toString(), s, text); +} + +function deselect() { + // Click outside text (and outside all <rect> elements>) to deselect. + click(15, 15); + selection_is("", "deselecting by clicking outside text"); +} + +function testSelection() { + svg = document.getElementsByTagName("iframe")[0]; + doc = svg.contentDocument; + win = svg.contentWindow; + dragstart = doc.getElementById("dragstart"); + dragend = doc.getElementById("dragend"); + + var text = doc.getElementsByTagName("text"); + + // Drag to select the entire text element. + drag(101, 50, 99 + text[0].getComputedTextLength(), 50); + selection_is("hello there", "selecting entire simple text"); + + // Click within the text to deselect. + click(101, 50); + selection_is("", "deselecting by clicking on text"); + + // Drag to select part of a text element. + drag(101, 50, 99 + text[0].getSubStringLength(0, 5), 50); + selection_is("hello", "selecting part of simple text"); + deselect(); + + // Drag from left of the text to the right of the text to select it. + drag(90, 50, 110 + text[0].getComputedTextLength(), 50); + selection_is("hello there", "selecting entire simple text by dragging around it"); + deselect(); + + // Drag above the text to select part of it. + var bbox1 = text[0].getBBox(); + drag(101 + text[0].getSubStringLength(0, 6), bbox1.y - 10, 101 + text[0].getSubStringLength(0, 9), bbox1.y - 10); + selection_is("the", "selecting part of simple text by dragging above it"); + deselect(); + + // Drag between the first and second texts, but closer to the first. + var bbox2 = text[1].getBBox(); + var mid = (bbox1.y + bbox1.height + bbox2.y) / 2; + drag(101, mid - 10, 99 + text[0].getSubStringLength(0, 2), mid - 10); + selection_is("he", "selecting closer text above"); + deselect(); + + // Drag between the first and second texts, but closer to the second. + drag(101, mid + 10, 99 + text[1].getSubStringLength(0, 2), mid + 10); + selection_is("to", "selecting closer text below"); + deselect(); + + // Drag starting in the first text and ending in the second. + drag(101 + text[0].getSubStringLength(0, 6), 50, 99 + text[1].getSubStringLength(0, 2), 100); + selection_is("there to", "selecting from first to second text"); + deselect(); + + // Select across positioned glyphs. + drag(99 + text[2].getSubStringLength(3, 1), 150, 201, 150); + selection_is("abcd", "selecting across positioned glyphs"); + deselect(); + + // Select bidi text, from the left of the "א" to the left of the "b". + drag(text[3].getExtentOfChar(0).x + 1, 200, text[3].getExtentOfChar(4).x + 1, 200); + selection_is("בגa", "selecting bidi text"); + deselect(); + + // Select transformed text. + drag(101, 250, 99 + text[4].getSubStringLength(0, 6) / 2, 250); + selection_is("squash"); + deselect(); + + SimpleTest.finish(); +} + +function runTest() { + SimpleTest.executeSoon(testSelection); +} + +if (/Android/.test(navigator.userAgent)) { + ok(true, "No need to test text selection with the mouse on Android."); + SimpleTest.finish(); +} else { + window.addEventListener("load", runTest); +} +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_text_update.html b/dom/svg/test/test_text_update.html new file mode 100644 index 0000000000..d4677aaeac --- /dev/null +++ b/dom/svg/test/test_text_update.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Test for Bug 876831</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=876831">Mozilla Bug 876831</a> +<p id="display"</p> + +<!-- + Test that the frame tree will be reflowed after a DOM mutation + and just before an SVG DOM method does its work. + --> + +<svg> + <text>ab</text> +</svg> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +var text = document.querySelector("text"); + +var length = text.getComputedTextLength(); +ok(length > 0, "text.getComputedTextLength() > 0"); + +text.firstChild.nodeValue += "cd"; +ok(text.getComputedTextLength() > length, "text.getComputedTextLength() changes directly after DOM mutation"); + +text.firstChild.remove(); +is(text.getComputedTextLength(), 0, "text.getComputedTextLength() == 0 after removing child"); +</script> +</pre> diff --git a/dom/svg/test/test_transform.xhtml b/dom/svg/test/test_transform.xhtml new file mode 100644 index 0000000000..6a139d3580 --- /dev/null +++ b/dom/svg/test/test_transform.xhtml @@ -0,0 +1,190 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=512636 +--> +<head> + <title>Test SVGTransform behavior</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="matrixUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=512636">Mozilla Bug 512636</a> +<p id="display"></p> +<div id="content"> + + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="1" id="svg"> + <g id="g" transform="translate(10, 20)"/> + </svg> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var g, t, m, m2; + + g = $("g"); + + t = g.transform.baseVal.getItem(0); + m = t.matrix; + + // test that the SVGTransform correctly reflects the translate() + checkTransform(t, SVGTransform.SVG_TRANSFORM_TRANSLATE, + {a: 1, b: 0, c: 0, d: 1, e: 10, f: 20}, + 0, "translate"); + + // set the SVGTransform to be a scale() + t.setScale(2, 3); + + // test that the matrix is live and now reflects the scale() + checkTransform(t, SVGTransform.SVG_TRANSFORM_SCALE, + {a: 2, b: 0, c: 0, d: 3, e: 0, f: 0}, + 0, "scale"); + + // set the SVGTransform to be a matrix() + m2 = createMatrix(1, 2, 3, 4, 5, 6); + t.setMatrix(m2); + + // check that setMatrix() took a copy of m + ok(m != m2, "t.matrix identity"); + + // test that the SVGTransform now reflects the matrix value + checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX, + {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}, + 0, "matrix"); + + m2 = {m11: 6, m12: 5, m21: 4, m22: 3, m41: 2, m42: 1}; + t.setMatrix(m2); + checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX, + {a: 6, b: 5, c: 4, d: 3, e: 2, f: 1}, + 0, "matrix"); + + // set the SVGTransform to be a translate() then convert to a matrix + t.setTranslate(0, 10); + m.a = 2; + + // test that the SVGTransform now reflects the matrix value + checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX, + {a: 2, b: 0, c: 0, d: 1, e: 0, f: 10}, + 0, "matrix"); + + // If ty is not supplied it is assumed to be zero + g.setAttribute("transform", "translate(5)"); + + // test that the SVGTransform now reflects the matrix value + checkTransform(t, SVGTransform.SVG_TRANSFORM_TRANSLATE, + {a: 1, b: 0, c: 0, d: 1, e: 5, f: 0}, + 0, "transform"); + + // set the SVGTransform to be a rotate() + t.setRotate(90, 0, 0); + + // test that the SVGTransform now reflects the matrix value + checkTransform(t, SVGTransform.SVG_TRANSFORM_ROTATE, + {a: Math.cos(Math.PI / 2), b: Math.sin(Math.PI / 2), + c: -Math.sin(Math.PI / 2), d: Math.cos(Math.PI / 2), + e: 0, f: 0}, + 90, "rotate"); + + // set the SVGTransform to be a skewX() + t.setSkewX(45); + + // test that the SVGTransform now reflects the matrix value + checkTransform(t, SVGTransform.SVG_TRANSFORM_SKEWX, + {a: 1, b: 0, + c: Math.tan(Math.PI / 4), d: Math.tan(Math.PI / 4), + e: 0, f: 0}, + 45, "skewX"); + + // set the SVGTransform to be a skewY() + t.setSkewY(45); + + // test that the SVGTransform now reflects the matrix value + checkTransform(t, SVGTransform.SVG_TRANSFORM_SKEWY, + {a: Math.tan(Math.PI / 4), b: Math.tan(Math.PI / 4), + c: 0, d: 1, + e: 0, f: 0}, + 45, "skewY"); + + // check angle is reset after changing type + t.setTranslate(10, 20); + is(t.angle, 0, "Angle not reset after changing to translate type"); + + // check read-only properties + t.angle = 40; + is(t.angle, 0, "t.angle should be read-only"); + t.type = 7; + is(t.type, SVGTransform.SVG_TRANSFORM_TRANSLATE, + "t.type should be read-only"); + t.matrix = m2; + ok(t.matrix != m2 && t.matrix.b == 0, "t.matrix should be read-only"); + + // check transform object identity after manipulation + ok(t === g.transform.baseVal.getItem(0), + "Got different transform objects after manipulation"); + ok(t.matrix === m, + "Got different matrix objects after manipulation"); + + testCreateTransform(); + testMatrixTransform(); + + SimpleTest.finish(); +} + +function testMatrixTransform() { + let svg = $("svg"); + const epsilon = 1 / 65536; + + let point = svg.createSVGPoint(); + point.x = 5; + point.y = 4; + let matrix = createMatrix(2, 0, 0, 2, 10, 10); + let result = point.matrixTransform(matrix); + let expected = DOMPoint.fromPoint(point).matrixTransform(matrix); + isfuzzy(result.x, expected.x, epsilon, "matrix transformation x"); + isfuzzy(result.y, expected.y, epsilon, "matrix transformation y"); + + svg.currentTranslate.x = 5; + svg.currentTranslate.y = 4; + result = svg.currentTranslate.matrixTransform(matrix); + isfuzzy(result.x, expected.x, epsilon, "svg matrix transformation x"); + isfuzzy(result.y, expected.y, epsilon, "svg matrix transformation y"); + svg.currentTranslate.x = 0; + svg.currentTranslate.y = 0; +} + +function testCreateTransform() { + let svg = $("svg"); + let t = svg.createSVGTransform(); + ok(t != svg.createSVGTransform(), + "Got identical objects when creating new transform"); + checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX, + createMatrix(1, 0, 0, 1, 0, 0), 0, "createSVGTransform"); + + let m = createMatrix(1, 2, 3, 4, 5, 6); + t = svg.createSVGTransformFromMatrix(m); + ok(t.matrix != m, + "createSVGTransformFromMatrix should copy matrix not adopt it"); + m.a = 7; // Just to be sure, changing m should not affect t + checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX, + {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}, + 0, "createSVGTransformFromMatrix"); +} + +function checkTransform(transform, type, matrix, angle, forWhat) { + roughCmpMatrix(transform.matrix, matrix, forWhat); + is(transform.type, type, `transform.type for ${forWhat}`); + is(transform.angle, angle, `transform.angle for ${forWhat}`); +} + +window.addEventListener("load", run); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_transformParsing.html b/dom/svg/test/test_transformParsing.html new file mode 100644 index 0000000000..bd6bb61f42 --- /dev/null +++ b/dom/svg/test/test_transformParsing.html @@ -0,0 +1,103 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=946529 +--> +<head> + <meta charset="utf-8"> + <title>Test transform parsing</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946529">Mozilla Bug 946529</a> +<p id="display"></p> +<div id="content" style="display: none"> + <svg width="100%" height="1" id="svg"> + <g id="g"/> + </svg> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// Test cases +checkParseOk("", [ ]); +checkParseOk("matrix(-.7235 .6903 .6903 .7235 -2050 1.14e4)", + [ { type: "matrix", a: -0.7235, b: 0.6903, c: 0.6903, + d: 0.7235, e: -2050, f: 11400 } ]); +checkParseOk("matrix(0e0 1e0 1e1 1e-1 1E+2 -.1e1)", + [ { type: "matrix", a: 0, b: 1, c: 10, + d: 0.1, e: 100, f: -1 } ]); +checkParseOk("matrix(-0e-0 1e+0 0e-5 1e-10 12.3e+4 .12e2)", + [ { type: "matrix", a: 0, b: 1, c: 0, + d: 0.0000000001, e: 123000, f: 12 } ]); + +// Fail cases +checkParseFail("matrix(1e+ 0 0 0 0 0)"); +checkParseFail("matrix(e2 0 0 0 0 0)"); +checkParseFail("matrix(1 e2 0 0 0 0 0)"); +checkParseFail("matrix(1e 2 0 0 0 0 0)"); +checkParseFail("matrix(1e+-2 0 0 0 0 0)"); +checkParseFail("matrix(1e 0 0 0 0 0)"); +checkParseFail("matrix(1e1.1 0 0 0 0 0)"); +checkParseFail("scale(2) matrix(1e1.1 0 0 0 0 0)"); + +function checkParseOk(spec, expected) { + var g = document.getElementById("g"); + + // Clear previous value + g.removeAttribute("transform"); + + g.setAttribute("transform", spec); + + // Check length + var transformList = g.transform.baseVal; + is(transformList.numberOfItems, expected.length, spec + " - length"); + if (transformList.numberOfItems != expected.length) + return; + + // Check each item + for (var i = 0; i < transformList.numberOfItems; i++) { + checkTransform(transformList.getItem(i), expected[i], spec, i); + } +} + +function checkTransform(transform, expected, spec, index) { + var typeMapping = { + "unknown": SVGTransform.SVG_TRANSFORM_UNKNOWN, + "matrix": SVGTransform.SVG_TRANSFORM_MATRIX, + "translate": SVGTransform.SVG_TRANSFORM_TRANSLATE, + "scale": SVGTransform.SVG_TRANSFORM_SCALE, + "rotate": SVGTransform.SVG_TRANSFORM_ROTATE, + "skewx": SVGTransform.SVG_TRANSFORM_SKEWX, + "skewy": SVGTransform.SVG_TRANSFORM_SKEWY, + }; + var name = "Item " + index + " of '" + spec + "'"; + + // Compare type + if (typeof expected.type != "undefined") { + is(transform.type, typeMapping[expected.type], name + " - transform type"); + } + + // Compare angle + if (typeof expected.angle != "undefined") { + is(transform.angle, expected.angle, name + " - angle"); + } + + // Compare matrix values (roughly) + ["a", "b", "c", "d", "e", "f"].forEach(function(item) { + var actual = transform.matrix[item]; + var msg = name + " - matrix:" + item; + const tolerance = 1 / 65535; + ok(Math.abs(actual - expected[item]) < tolerance, + msg + " - got " + actual + ", expected " + expected[item]); + }); +} + +function checkParseFail(spec) { + checkParseOk(spec, []); +} +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_use_with_hsts.html b/dom/svg/test/test_use_with_hsts.html new file mode 100644 index 0000000000..2c82d93569 --- /dev/null +++ b/dom/svg/test/test_use_with_hsts.html @@ -0,0 +1,132 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1247733 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1247733</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247733">Mozilla Bug 1247733</a> +<p id="display"> + <iframe id="myIframe"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"></pre> +<script type="application/javascript"> + /** Test for Bug 1247733 **/ + + /** + * This test ensures that we render the SVG 'use' element correctly, in + * pages that have been upgraded from HTTP to HTTPS using strict transport + * security (HSTS) + * + * Specifically: + * (1) We load a file using HTTPS, in an iframe. The file gets sent + * with a Strict-Transport-Security flag. + * (2) We load the same file again, but now over HTTP (which should get + * upgraded to HTTPS, since we received the Strict-Transport-Security + * flag during the first load). + * (3) After each of the above loads, we take a snapshot of the iframe + * and ensure that it renders as fully lime (which the 'use' element + * is responsible for). If the 'use' element fails to render, the iframe + * will be fully red, and we'll fail an "assertSnapshots" check. + */ + SimpleTest.waitForExplicitFinish(); + + const iframe = document.getElementById("myIframe"); + const iframeWin = iframe.contentWindow; + + // URI for our testcase with 'use' element, via HTTP and HTTPS: + const insecureURI = "http://example.com/tests/dom/svg/test/use-with-hsts-helper.html"; + const secureURI = "https://example.com/tests/dom/svg/test/use-with-hsts-helper.html"; + + // Bookkeeping to be sure receiveMessage is called as many times as we expect: + var numPostMessageCalls = 0; + const expectedNumPostMessageCalls = 2; // (We load the helper file twice.) + + // Helper function, called via postMessage, to check iframe's actual location: + function receiveMessage(event) { + is(event.data, secureURI, "iframe should end up viewing secure URI"); + numPostMessageCalls++; + } + + // Convenience helper which makes |iframe| load the given |uri|. Returns + // a promise that resolves when the load completes. This makes it handy to + // use with 'await', to avoid onload callback hell. + async function LoadIframeAsync(uri) { + return new Promise(resolve => { + iframe.addEventListener("load", resolve, {once: true}); + // Kick off the requested load: + iframe.src = uri; + }); + } + + // MAIN TEST CODE BEGINS HERE. + async function runTest() { + // Capture a snapshot with nothing in the iframe, so we can do a + // sanity-check not-equal comparison against our reference case, to be + // sure we're rendering anything at all: + let blankSnapshot = await snapshotWindow(iframeWin); + + // Load & snapshot a reference case (fully lime): + await LoadIframeAsync("data:text/html,<body style='background:lime'>"); + let refSnapshot = await snapshotWindow(iframeWin); + + // Ensure reference snapshot looks different from blank snapshot: + assertSnapshots(refSnapshot, blankSnapshot, + false /* not equal*/, null /* no fuzz*/, + "refSnapshot", "blankSnapshot"); + + // OK, assuming we've got a valid refSnapshot, we can now proceed to + // capture test screenshots. + + // Register a postMessage handler, so that iframe can report its location: + window.addEventListener("message", receiveMessage); + + // Load & snapshot secure (HTTPS) version of testcase, & check against ref: + await LoadIframeAsync(secureURI); + let secureSnapshot = await snapshotWindow(iframeWin); + assertSnapshots(secureSnapshot, refSnapshot, + true /* equal*/, null /* no fuzz*/, + "secureSnapshot", "refSnapshot"); + + // Load insecure (HTTP) version of testcase (which should get + // automatically upgraded to secure (HTTPS) under the hood): + await LoadIframeAsync(insecureURI); + + // Double-check that iframe is really pointed at insecure URI, to be sure + // we're actually exercising HSTS. (Note that receiveMessage() will make + // sure it's been upgraded to a secure HTTPS URI under the hood.) + is(iframe.src, insecureURI, + "test should've attempted to load insecure HTTP URI, to exercise HSTS"); + + // Capture snapshot of iframe showing upgraded-to-HTTPS version of testcase: + let upgradedSnapshot = await snapshotWindow(iframeWin); + assertSnapshots(upgradedSnapshot, refSnapshot, + true /* equal*/, null /* no fuzz*/, + "upgradedSnapshot", "refSnapshot"); + + // Check that the iframe did actually invoke our postMessage handler (which + // is where we verify that the HSTS upgrade actually happened): + is(numPostMessageCalls, expectedNumPostMessageCalls, + "didn't receive as many messages from child iframe as expected"); + + // We're done! Clear the STS headers that we set, and finish. + SpecialPowers.cleanUpSTSData("http://example.com"); + SimpleTest.finish(); + } + + SpecialPowers.pushPrefEnv( + { 'set': [["security.mixed_content.block_active_content", false]] }, + function() { runTest(); } + ); +</script> +</body> +</html> diff --git a/dom/svg/test/test_valueAsString.xhtml b/dom/svg/test/test_valueAsString.xhtml new file mode 100644 index 0000000000..ab22ae146c --- /dev/null +++ b/dom/svg/test/test_valueAsString.xhtml @@ -0,0 +1,64 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=539697 +--> +<head> + <title>Test valueAsString behavior</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=539697">Mozilla Bug 539697</a> +<p id="display"></p> +<div id="content"> + + <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="1" id="svg"> + <circle id='c' r='1em' display='none'/> + <marker id='m' orient='20rad' display='none'/> + </svg> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var c = document.getElementById("c"); + var m = document.getElementById("m"); + + is(SVGLength.SVG_LENGTHTYPE_EMS, c.r.baseVal.unitType, "unexpected units"); + c.r.baseVal.valueAsString = "2px"; + is(SVGLength.SVG_LENGTHTYPE_PX, c.r.baseVal.unitType, "unexpected units"); + + try { + c.r.baseVal.valueAsString = "rubbish"; + ok(false, "setting a length to rubbish should fail"); + } catch (e) { + is(e.name, "SyntaxError", "syntax error expected"); + is(e.code, DOMException.SYNTAX_ERR, "syntax error expected"); + } + + is(SVGAngle.SVG_ANGLETYPE_RAD, m.orientAngle.baseVal.unitType, "unexpected units"); + m.orientAngle.baseVal.valueAsString = "2grad"; + is(SVGAngle.SVG_ANGLETYPE_GRAD, m.orientAngle.baseVal.unitType, "unexpected units"); + + try { + m.orientAngle.baseVal.valueAsString = "rubbish"; + ok(false, "setting an angle to rubbish should fail"); + } catch (e) { + is(e.name, "SyntaxError", "syntax error expected"); + is(e.code, DOMException.SYNTAX_ERR, "syntax error expected"); + } + + SimpleTest.finish(); +} + +window.addEventListener("load", run); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_valueLeaks.xhtml b/dom/svg/test/test_valueLeaks.xhtml new file mode 100644 index 0000000000..ce816efc1c --- /dev/null +++ b/dom/svg/test/test_valueLeaks.xhtml @@ -0,0 +1,84 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=467671 +--> +<head> + <title>Test for Bug 467671</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=467671">Mozilla Bug 467671</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ + +/** Test for Bug 467671 **/ + +function storeSVGPropertyAsExpando(localName, prop) { + var elem = document.createElementNS("http://www.w3.org/2000/svg", localName); + + elem.addEventListener("click", function() {}); + + var propVal = elem[prop]; + Object.prototype.valueOf[prop + "_expando"] = propVal; + if (propVal instanceof SVGAnimatedAngle || propVal instanceof SVGAnimatedLength || + propVal instanceof SVGAnimatedRect || propVal instanceof SVGAnimatedPreserveAspectRatio) { + Object.prototype.valueOf[prop + "_baseVal_expando"] = propVal.baseVal; + Object.prototype.valueOf[prop + "_animVal_expando"] = propVal.animVal; + } +} + +// class +storeSVGPropertyAsExpando("marker", "class"); + +// angle +storeSVGPropertyAsExpando("marker", "orientAngle"); + +// viewBox +storeSVGPropertyAsExpando("marker", "viewBox"); + +// preserveAspectRatio +storeSVGPropertyAsExpando("marker", "preserveAspectRatio"); + +// boolean +storeSVGPropertyAsExpando("feConvolveMatrix", "preserveAlpha"); + +// enum +storeSVGPropertyAsExpando("feConvolveMatrix", "edgeMode"); + +// special marker enum +storeSVGPropertyAsExpando("marker", "orientType"); + +// integer +storeSVGPropertyAsExpando("feConvolveMatrix", "orderX"); + +// length +storeSVGPropertyAsExpando("feConvolveMatrix", "x"); + +// number +storeSVGPropertyAsExpando("feConvolveMatrix", "divisor"); + +// string +storeSVGPropertyAsExpando("feConvolveMatrix", "in1"); + +var elem1 = document.createElementNS("http://www.w3.org/2000/svg", "switch"); +var elem2 = document.createElementNS("http://www.w3.org/2000/svg", "rect"); +elem1.appendChild(elem2); +document.getElementById("content").appendChild(elem1); + +elem2.addEventListener("click", function() {}); + +Object.prototype.valueOf.expando = elem1; + +ok(true, "SVG shouldn't leak."); + +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_viewBox.html b/dom/svg/test/test_viewBox.html new file mode 100644 index 0000000000..d09f5110ef --- /dev/null +++ b/dom/svg/test/test_viewBox.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1396642 +--> +<head> + <title>Test for Bug 1396642</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1396642">Mozilla Bug 1396642</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<div id="svg"></div> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTest() { + var testsElement = $("svg"); + + // Turn for instance `2.3` into `230` (px). Round to avoid floating point + // issues. + const scale = (number) => Math.round(100 * number); + + const widths = [2, 2.3, 2.5, 2.8]; + const heights = [3, 3.3, 3.5, 3.8]; + + for (const width of widths) { + for (const height of heights) { + const variations = [ + {width, height}, + {width: "auto", height}, + {width, height: "auto"}, + {width: "auto", height: "auto"}, + ]; + + for (const variation of variations) { + const svgWrapperElement = document.createElement("div"); + svgWrapperElement.style.width = + variation.width === "auto" && variation.height === "auto" + ? `${scale(width)}px` + : "auto"; + + const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svgElement.setAttribute("viewBox", `0 0 ${width} ${height}`); + svgElement.style.width = + typeof variation.width === "number" + ? `${scale(variation.width)}px` + : variation.width; + svgElement.style.height = + typeof variation.height === "number" + ? `${scale(variation.height)}px` + : variation.height; + + svgWrapperElement.appendChild(svgElement); + + testsElement.appendChild(svgWrapperElement); + + const rect = svgElement.getBoundingClientRect(); + const actual = { + width: Math.round(rect.width), + height: Math.round(rect.height), + }; + const expected = { + width: scale(width), + height: scale(height), + }; + + isfuzzy(expected.width, actual.width, 0.001, "checking width"); + isfuzzy(expected.height, actual.height, 0.001, "checking height"); + } + } + } + + SimpleTest.finish(); +} + +window.addEventListener("load", runTest); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/test_viewport.html b/dom/svg/test/test_viewport.html new file mode 100644 index 0000000000..66f8926fe0 --- /dev/null +++ b/dom/svg/test/test_viewport.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=483389 +--> +<head> + <title>Test for Bug 483389</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=483389">Mozilla Bug 483389</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<iframe id="svg" src="viewport-helper.svg"></iframe> + +<pre id="test"> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTest() { + var doc = $("svg").contentWindow.document; + + var root = doc.documentElement; + var inner = doc.getElementById("inner"); + var g1 = doc.getElementById("g1"); + var outer = doc.getElementById("outer"); + var g2 = doc.getElementById("g2"); + var g3 = doc.getElementById("g3"); + var sym = doc.getElementById("sym"); + var symbolRect = doc.getElementById("symbolRect"); + + <!-- ownerSVGElement --> + is(root.ownerSVGElement, null, "root.ownerSVGElement"); + is(inner.ownerSVGElement, root, "inner.ownerSVGElement"); + is(g1.ownerSVGElement, inner, "g1.ownerSVGElement"); + is(outer.ownerSVGElement, null, "outer.ownerSVGElement"); + is(g2.ownerSVGElement, outer, "g2.ownerSVGElement"); + is(g3.ownerSVGElement, null, "g3.ownerSVGElement"); + is(symbolRect.ownerSVGElement, root, "symbolRect.ownerSVGElement"); + + <!-- viewportElement --> + is(root.viewportElement, null, "root.viewportElement"); + is(inner.viewportElement, root, "inner.viewportElement"); + is(g1.viewportElement, inner, "g1.viewportElement"); + is(outer.viewportElement, null, "outer.viewportElement"); + is(g2.viewportElement, outer, "g2.viewportElement"); + is(g3.viewportElement, null, "g3.viewportElement"); + is(symbolRect.viewportElement, sym, "symbolRect.viewportElement"); + + SimpleTest.finish(); +} + +window.addEventListener("load", runTest); +</script> +</pre> +</body> +</html> diff --git a/dom/svg/test/text-helper-scaled.svg b/dom/svg/test/text-helper-scaled.svg new file mode 100644 index 0000000000..3d534e3fd3 --- /dev/null +++ b/dom/svg/test/text-helper-scaled.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3000" height="2000"> + <g id="g" style="font: 400px monospace"> + <text id="text1" x="10" y="400">abc</text> + + <path id="MyPath" d="M 5 800 h 2000 v 60 h -2000 z" stroke="red" fill="none"/> + <text id="text2"><textPath xlink:href="#MyPath">abcdefghijklmnopqrstuvwxyz</textPath></text> + </g> +</svg> diff --git a/dom/svg/test/text-helper-selection.svg b/dom/svg/test/text-helper-selection.svg new file mode 100644 index 0000000000..df84a19ac4 --- /dev/null +++ b/dom/svg/test/text-helper-selection.svg @@ -0,0 +1,23 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" + style="font: 24px monospace"> + + <!-- We need these two rects so that getBoundingClientRect of the <svg> does + not just return the region covered by the <text>, which would result in + the synthesizeMouse calls using the wrong positions. We don't use one + big rect because that could interfere with text selection when dragging + outside the bounds of text elements. --> + <rect width="10" height="10" fill="white"/> + <rect x="350" y="250" width="10" height="10" fill="white"/> + + <text x="100" y="50">hello there</text> + <text x="100" y="100">to you all!</text> + <text x="200" y="150">abc<tspan x="100" dy="10 -10">def</tspan></text> + <text x="100" y="200">אבגabc</text> + <text x="100" y="250" transform="scale(0.5,1)translate(100)">squashed</text> + + <!-- These two circles are just used for debugging the test; passing true + as the last argument to drag() will place these circles at the drag + start and end points. --> + <circle id="dragstart" fill="blue"/> + <circle id="dragend" fill="red"/> +</svg> diff --git a/dom/svg/test/text-helper.svg b/dom/svg/test/text-helper.svg new file mode 100644 index 0000000000..1bdff86f26 --- /dev/null +++ b/dom/svg/test/text-helper.svg @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="750" + xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> + <style type="text/css"> +text { font: 20px monospace; } + </style> + +<g id="g"> + <text id="text1" x="5" y="25">abc</text> + + <path id="MyPath2" d="M 100 125 L 100 200" stroke="red" fill="none"/> + <text id="text2"><textPath xlink:href="#MyPath2">abc</textPath></text> + + <path id="MyPath" d="M 5 250 L 105 250 L 105 253 L 5 253 z" stroke="red" fill="none"/> + <text id="text3"><textPath xlink:href="#MyPath">abcdefghijklmnopqrstuvwxyz</textPath></text> + + <text display="none" id="text4" x="5" y="25">abc</text> +</g> +</svg> diff --git a/dom/svg/test/use-with-hsts-helper.html b/dom/svg/test/use-with-hsts-helper.html new file mode 100644 index 0000000000..409dade7c6 --- /dev/null +++ b/dom/svg/test/use-with-hsts-helper.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> + <script> + // Notify parent of our final URI: + window.parent.postMessage(window.location.href, "*"); + </script> + <style> + html, body { + margin: 0; + height: 100%; + } + svg { + display: block; + height: 100%; + } + </style> +</head> +<body> + <svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + version="1.1"> + <defs> + <rect id="limeRect" width="100%" height="100%" fill="lime"/> + </defs> + <rect width="100%" height="100%" fill="red"/> + <use xlink:href="#limeRect"/> + </svg> +</body> +</html> diff --git a/dom/svg/test/use-with-hsts-helper.html^headers^ b/dom/svg/test/use-with-hsts-helper.html^headers^ new file mode 100644 index 0000000000..a46bf65bd9 --- /dev/null +++ b/dom/svg/test/use-with-hsts-helper.html^headers^ @@ -0,0 +1,2 @@ +Cache-Control: no-cache +Strict-Transport-Security: max-age=60 diff --git a/dom/svg/test/viewport-helper.svg b/dom/svg/test/viewport-helper.svg new file mode 100644 index 0000000000..c553b43a1f --- /dev/null +++ b/dom/svg/test/viewport-helper.svg @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="750" + xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> + + <defs> + <symbol id="sym"> + <rect width="0" height="0" id="symbolRect"/> + </symbol> + </defs> + <svg id="inner"> + <g id="g1"> + </g> + </svg> + <foreignObject> + <svg id="outer"> + <g id="g2"> + </g> + </svg> + </foreignObject> + <!-- something invalid --> + <foreignObject> + <g id="g3"> + </g> + </foreignObject> + <use xlink:href="#sym" id="use"/> +</svg> |