summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGTransform.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGTransform.cpp')
-rw-r--r--dom/svg/SVGTransform.cpp371
1 files changed, 371 insertions, 0 deletions
diff --git a/dom/svg/SVGTransform.cpp b/dom/svg/SVGTransform.cpp
new file mode 100644
index 000000000..b73018d1c
--- /dev/null
+++ b/dom/svg/SVGTransform.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/dom/SVGTransform.h"
+#include "mozilla/dom/SVGMatrix.h"
+#include "mozilla/dom/SVGTransformBinding.h"
+#include "nsError.h"
+#include "nsSVGAnimatedTransformList.h"
+#include "nsSVGAttrTearoffTable.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FloatingPoint.h"
+
+namespace {
+ const double kRadPerDegree = 2.0 * M_PI / 360.0;
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+static nsSVGAttrTearoffTable<SVGTransform, SVGMatrix>&
+SVGMatrixTearoffTable()
+{
+ static nsSVGAttrTearoffTable<SVGTransform, 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(SVGTransform)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGTransform)
+ // 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(SVGTransform)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList)
+ SVGMatrix* matrix =
+ SVGMatrixTearoffTable().GetTearoff(tmp);
+ CycleCollectionNoteChild(cb, matrix, "matrix");
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(SVGTransform)
+NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(SVGTransform, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(SVGTransform, Release)
+
+JSObject*
+SVGTransform::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return SVGTransformBinding::Wrap(aCx, this, aGivenProto);
+}
+
+//----------------------------------------------------------------------
+// Helper class: AutoChangeTransformNotifier
+// Stack-based helper class to pair calls to WillChangeTransformList
+// and DidChangeTransformList.
+class MOZ_RAII AutoChangeTransformNotifier
+{
+public:
+ explicit AutoChangeTransformNotifier(SVGTransform* aTransform MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mTransform(aTransform)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ MOZ_ASSERT(mTransform, "Expecting non-null transform");
+ if (mTransform->HasOwner()) {
+ mEmptyOrOldValue =
+ mTransform->Element()->WillChangeTransformList();
+ }
+ }
+
+ ~AutoChangeTransformNotifier()
+ {
+ if (mTransform->HasOwner()) {
+ mTransform->Element()->DidChangeTransformList(mEmptyOrOldValue);
+ if (mTransform->mList->IsAnimating()) {
+ mTransform->Element()->AnimationNeedsResample();
+ }
+ }
+ }
+
+private:
+ SVGTransform* const mTransform;
+ nsAttrValue mEmptyOrOldValue;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+//----------------------------------------------------------------------
+// Ctors:
+
+SVGTransform::SVGTransform(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!");
+}
+
+SVGTransform::SVGTransform()
+ : mList(nullptr)
+ , mListIndex(0)
+ , mIsAnimValItem(false)
+ , mTransform(new nsSVGTransform()) // Default ctor for objects not in a list
+ // initialises to matrix type with identity
+ // matrix
+{
+}
+
+SVGTransform::SVGTransform(const gfxMatrix &aMatrix)
+ : mList(nullptr)
+ , mListIndex(0)
+ , mIsAnimValItem(false)
+ , mTransform(new nsSVGTransform(aMatrix))
+{
+}
+
+SVGTransform::SVGTransform(const nsSVGTransform &aTransform)
+ : mList(nullptr)
+ , mListIndex(0)
+ , mIsAnimValItem(false)
+ , mTransform(new nsSVGTransform(aTransform))
+{
+}
+
+SVGTransform::~SVGTransform()
+{
+ 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
+SVGTransform::Type() const
+{
+ return Transform().Type();
+}
+
+SVGMatrix*
+SVGTransform::GetMatrix()
+{
+ SVGMatrix* wrapper =
+ SVGMatrixTearoffTable().GetTearoff(this);
+ if (!wrapper) {
+ NS_ADDREF(wrapper = new SVGMatrix(*this));
+ SVGMatrixTearoffTable().AddTearoff(this, wrapper);
+ }
+ return wrapper;
+}
+
+float
+SVGTransform::Angle() const
+{
+ return Transform().Angle();
+}
+
+void
+SVGTransform::SetMatrix(SVGMatrix& aMatrix, ErrorResult& rv)
+{
+ if (mIsAnimValItem) {
+ rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+ SetMatrix(aMatrix.GetMatrix());
+}
+
+void
+SVGTransform::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;
+ }
+
+ AutoChangeTransformNotifier notifier(this);
+ Transform().SetTranslate(tx, ty);
+}
+
+void
+SVGTransform::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;
+ }
+ AutoChangeTransformNotifier notifier(this);
+ Transform().SetScale(sx, sy);
+}
+
+void
+SVGTransform::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;
+ }
+ }
+
+ AutoChangeTransformNotifier notifier(this);
+ Transform().SetRotate(angle, cx, cy);
+}
+
+void
+SVGTransform::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;
+ }
+
+ AutoChangeTransformNotifier notifier(this);
+ DebugOnly<nsresult> result = Transform().SetSkewX(angle);
+ MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewX unexpectedly failed");
+}
+
+void
+SVGTransform::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;
+ }
+
+ AutoChangeTransformNotifier notifier(this);
+ DebugOnly<nsresult> result = Transform().SetSkewY(angle);
+ MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewY unexpectedly failed");
+}
+
+//----------------------------------------------------------------------
+// List management methods:
+
+void
+SVGTransform::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
+SVGTransform::RemovingFromList()
+{
+ MOZ_ASSERT(!mTransform,
+ "Item in list also has another non-list value associated with it");
+
+ mTransform = new nsSVGTransform(InternalItem());
+ mList = nullptr;
+ mIsAnimValItem = false;
+}
+
+nsSVGTransform&
+SVGTransform::InternalItem()
+{
+ nsSVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList();
+ return mIsAnimValItem && alist->mAnimVal ?
+ (*alist->mAnimVal)[mListIndex] :
+ alist->mBaseVal[mListIndex];
+}
+
+const nsSVGTransform&
+SVGTransform::InternalItem() const
+{
+ return const_cast<SVGTransform*>(this)->InternalItem();
+}
+
+#ifdef DEBUG
+bool
+SVGTransform::IndexIsValid()
+{
+ nsSVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList();
+ return (mIsAnimValItem &&
+ mListIndex < alist->GetAnimValue().Length()) ||
+ (!mIsAnimValItem &&
+ mListIndex < alist->GetBaseValue().Length());
+}
+#endif // DEBUG
+
+
+//----------------------------------------------------------------------
+// Interface for SVGMatrix's use
+
+void
+SVGTransform::SetMatrix(const gfxMatrix& aMatrix)
+{
+ MOZ_ASSERT(!mIsAnimValItem,
+ "Attempting to modify read-only transform");
+
+ if (Transform().Type() == SVG_TRANSFORM_MATRIX &&
+ nsSVGTransform::MatricesEqual(Matrixgfx(), aMatrix)) {
+ return;
+ }
+
+ AutoChangeTransformNotifier notifier(this);
+ Transform().SetMatrix(aMatrix);
+}
+
+} // namespace dom
+} // namespace mozilla