gecko-dev/dom/svg/SVGTransform.cpp

319 строки
9.9 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 {
using namespace SVGTransform_Binding;
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_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 SVGTransform_Binding::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);
// Null check mTransform->mList, since DidChangeTransformList can run
// script, potentially removing mTransform from its list.
if (mTransform->mList && 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