gecko/dom/svg/SVGTransform.cpp

372 lines
9.7 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 {
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