mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
372 lines
9.7 KiB
C++
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
|