Bug 923193, part 4 - Implement support for the 'transform-origin' property in SVG. r=heycam

This commit is contained in:
Jonathan Watt 2015-05-24 22:40:37 +01:00
parent ef7f0a9bfe
commit e218dd339f
9 changed files with 185 additions and 43 deletions

View File

@ -4652,17 +4652,23 @@ nsDisplayTransform::GetDeltaToTransformOrigin(const nsIFrame* aFrame,
* a distance, it's already computed for us!
*/
const nsStyleDisplay* display = aFrame->StyleDisplay();
// We don't use aBoundsOverride for SVG since we need to account for
// refBox.X/Y(). This happens to work because ReflowSVG sets the frame's
// mRect before calling FinishAndStoreOverflow so we don't need the override.
TransformReferenceBox refBox;
if (aBoundsOverride) {
if (aBoundsOverride &&
!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
refBox.Init(aBoundsOverride->Size());
} else {
refBox.Init(aFrame);
}
/* Allows us to access dimension getters by index. */
float coords[3];
float coords[2];
TransformReferenceBox::DimensionGetter dimensionGetter[] =
{ &TransformReferenceBox::Width, &TransformReferenceBox::Height };
TransformReferenceBox::DimensionGetter offsetGetter[] =
{ &TransformReferenceBox::X, &TransformReferenceBox::Y };
for (uint8_t index = 0; index < 2; ++index) {
/* If the -moz-transform-origin specifies a percentage, take the percentage
@ -4684,20 +4690,19 @@ nsDisplayTransform::GetDeltaToTransformOrigin(const nsIFrame* aFrame,
coords[index] =
NSAppUnitsToFloatPixels(coord.GetCoordValue(), aAppUnitsPerPixel);
}
if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) &&
coord.GetUnit() != eStyleUnit_Percent) {
// <length> values represent offsets from the origin of the SVG element's
// user space, not the top left of its bounds, so we must adjust for that:
nscoord offset =
(index == 0) ? aFrame->GetPosition().x : aFrame->GetPosition().y;
coords[index] -= NSAppUnitsToFloatPixels(offset, aAppUnitsPerPixel);
if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
// SVG frames (unlike other frames) have a reference box that can be (and
// typically is) offset from the TopLeft() of the frame. We need to
// account for that here.
coords[index] +=
NSAppUnitsToFloatPixels((refBox.*offsetGetter[index])(), aAppUnitsPerPixel);
}
}
coords[2] = NSAppUnitsToFloatPixels(display->mTransformOrigin[2].GetCoordValue(),
aAppUnitsPerPixel);
return Point3D(coords[0], coords[1], coords[2]);
return Point3D(coords[0], coords[1],
NSAppUnitsToFloatPixels(display->mTransformOrigin[2].GetCoordValue(),
aAppUnitsPerPixel));
}
/* Returns the delta specified by the -moz-perspective-origin property.
@ -4847,8 +4852,12 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp
// Get the underlying transform matrix:
// We don't use aBoundsOverride for SVG since we need to account for
// refBox.X/Y(). This happens to work because ReflowSVG sets the frame's
// mRect before calling FinishAndStoreOverflow so we don't need the override.
TransformReferenceBox refBox;
if (aBoundsOverride) {
if (aBoundsOverride &&
(!frame || !(frame->GetStateBits() & NS_FRAME_SVG_LAYOUT))) {
refBox.Init(aBoundsOverride->Size());
} else {
refBox.Init(frame);
@ -4862,6 +4871,8 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp
Matrix svgTransform, transformFromSVGParent;
bool hasSVGTransforms =
frame && frame->IsSVGTransformed(&svgTransform, &transformFromSVGParent);
bool hasTransformFromSVGParent =
hasSVGTransforms && !transformFromSVGParent.IsIdentity();
/* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */
if (aProperties.mTransformList) {
result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList->mHead,
@ -4877,15 +4888,6 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp
result = gfx3DMatrix::From2D(ThebesMatrix(svgTransform));
}
if (hasSVGTransforms && !transformFromSVGParent.IsIdentity()) {
// Correct the translation components for zoom:
float pixelsPerCSSPx = frame->PresContext()->AppUnitsPerCSSPixel() /
aAppUnitsPerPixel;
transformFromSVGParent._31 *= pixelsPerCSSPx;
transformFromSVGParent._32 *= pixelsPerCSSPx;
result = result * gfx3DMatrix::From2D(ThebesMatrix(transformFromSVGParent));
}
if (aProperties.mChildPerspective > 0.0) {
gfx3DMatrix perspective;
perspective._34 =
@ -4907,17 +4909,52 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp
Point3D roundedOrigin(hasSVGTransforms ? newOrigin.x : NS_round(newOrigin.x),
hasSVGTransforms ? newOrigin.y : NS_round(newOrigin.y),
0);
Point3D offsetBetweenOrigins = roundedOrigin + aProperties.mToTransformOrigin;
if (aOffsetByOrigin) {
// We can fold the final translation by roundedOrigin into the first matrix
// basis change translation. This is more stable against variation due to
// insufficient floating point precision than reversing the translation
// afterwards.
result.Translate(-aProperties.mToTransformOrigin);
result.TranslatePost(offsetBetweenOrigins);
if (!hasSVGTransforms || !hasTransformFromSVGParent) {
// This is a simplification of the following |else| block, the
// simplification being possible because we don't need to apply
// mToTransformOrigin between two transforms.
Point3D offsets = roundedOrigin + aProperties.mToTransformOrigin;
if (aOffsetByOrigin) {
// We can fold the final translation by roundedOrigin into the first matrix
// basis change translation. This is more stable against variation due to
// insufficient floating point precision than reversing the translation
// afterwards.
result.Translate(-aProperties.mToTransformOrigin);
result.TranslatePost(offsets);
} else {
result.ChangeBasis(offsets);
}
} else {
result.ChangeBasis(offsetBetweenOrigins);
Point3D refBoxOffset(NSAppUnitsToFloatPixels(refBox.X(), aAppUnitsPerPixel),
NSAppUnitsToFloatPixels(refBox.Y(), aAppUnitsPerPixel),
0);
// We have both a transform and children-only transform. The
// 'transform-origin' must apply between the two, so we need to apply it
// now before we apply transformFromSVGParent. Since mToTransformOrigin is
// relative to the frame's TopLeft(), we need to convert it to SVG user
// space by subtracting refBoxOffset. (Then after applying
// transformFromSVGParent we have to reapply refBoxOffset below.)
result.ChangeBasis(aProperties.mToTransformOrigin - refBoxOffset);
// Now apply the children-only transforms, converting the translation
// components to device pixels:
float pixelsPerCSSPx =
frame->PresContext()->AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
transformFromSVGParent._31 *= pixelsPerCSSPx;
transformFromSVGParent._32 *= pixelsPerCSSPx;
result = result * gfx3DMatrix::From2D(ThebesMatrix(transformFromSVGParent));
// Similar to the code in the |if| block above, but since we've accounted
// for mToTransformOrigin so we don't include that. We also need to reapply
// refBoxOffset.
Point3D offsets = roundedOrigin + refBoxOffset;
if (aOffsetByOrigin) {
result.Translate(-refBoxOffset);
result.TranslatePost(offsets);
} else {
result.ChangeBasis(offsets);
}
}
if (frame && frame->Preserves3D()) {

View File

@ -140,6 +140,7 @@ typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
/* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled;
/* static */ bool nsLayoutUtils::sCSSVariablesEnabled;
/* static */ bool nsLayoutUtils::sInterruptibleReflowEnabled;
/* static */ bool nsLayoutUtils::sSVGTransformOriginEnabled;
static ViewID sScrollIdCounter = FrameMetrics::START_SCROLL_ID;
@ -7051,6 +7052,8 @@ nsLayoutUtils::Initialize()
"layout.css.variables.enabled");
Preferences::AddBoolVarCache(&sInterruptibleReflowEnabled,
"layout.interruptible-reflow.enabled");
Preferences::AddBoolVarCache(&sSVGTransformOriginEnabled,
"svg.transform-origin.enabled");
Preferences::RegisterCallback(GridEnabledPrefChangeCallback,
GRID_ENABLED_PREF_NAME);

View File

@ -2305,6 +2305,10 @@ public:
return sFontSizeInflationDisabledInMasterProcess;
}
static bool SVGTransformOriginEnabled() {
return sSVGTransformOriginEnabled;
}
/**
* See comment above "font.size.inflation.mappingIntercept" in
* modules/libpref/src/init/all.js .
@ -2657,6 +2661,7 @@ private:
static bool sInvalidationDebuggingIsEnabled;
static bool sCSSVariablesEnabled;
static bool sInterruptibleReflowEnabled;
static bool sSVGTransformOriginEnabled;
/**
* Helper function for LogTestDataForPaint().

View File

@ -9,8 +9,10 @@
#include "nsStyleTransformMatrix.h"
#include "nsCSSValue.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsRuleNode.h"
#include "nsSVGUtils.h"
#include "nsCSSKeywords.h"
#include "mozilla/StyleAnimationValue.h"
#include "gfxMatrix.h"
@ -48,9 +50,44 @@ TransformReferenceBox::EnsureDimensionsAreCached()
mIsCached = true;
if (mFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
// TODO: SVG needs to define what percentage translations resolve against.
mWidth = 0;
mHeight = 0;
if (!nsLayoutUtils::SVGTransformOriginEnabled()) {
mX = -mFrame->GetPosition().x;
mY = -mFrame->GetPosition().y;
mWidth = 0;
mHeight = 0;
} else
if (mFrame->StyleDisplay()->mTransformBox ==
NS_STYLE_TRANSFORM_BOX_FILL_BOX) {
// Percentages in transforms resolve against the SVG bbox, and the
// transform is relative to the top-left of the SVG bbox.
gfxRect bbox = nsSVGUtils::GetBBox(const_cast<nsIFrame*>(mFrame));
nsRect bboxInAppUnits =
nsLayoutUtils::RoundGfxRectToAppRect(bbox,
mFrame->PresContext()->AppUnitsPerCSSPixel());
// The mRect of an SVG nsIFrame is its user space bounds *including*
// stroke and markers, whereas bboxInAppUnits is its user space bounds
// including fill only. We need to note the offset of the reference box
// from the frame's mRect in mX/mY.
mX = bboxInAppUnits.x - mFrame->GetPosition().x;
mY = bboxInAppUnits.y - mFrame->GetPosition().y;
mWidth = bboxInAppUnits.width;
mHeight = bboxInAppUnits.height;
} else {
// The value 'border-box' is treated as 'view-box' for SVG content.
MOZ_ASSERT(mFrame->StyleDisplay()->mTransformBox ==
NS_STYLE_TRANSFORM_BOX_VIEW_BOX ||
mFrame->StyleDisplay()->mTransformBox ==
NS_STYLE_TRANSFORM_BOX_BORDER_BOX,
"Unexpected value for 'transform-box'");
// Percentages in transforms resolve against the width/height of the
// nearest viewport (or it's viewBox if one is applied), and the
// transform is relative to {0,0} in current user space.
mX = -mFrame->GetPosition().x;
mY = -mFrame->GetPosition().y;
Size contextSize = nsSVGUtils::GetContextSize(mFrame);
mWidth = nsPresContext::CSSPixelsToAppUnits(contextSize.width);
mHeight = nsPresContext::CSSPixelsToAppUnits(contextSize.height);
}
return;
}
@ -78,6 +115,8 @@ TransformReferenceBox::EnsureDimensionsAreCached()
}
#endif
mX = 0;
mY = 0;
mWidth = rect.Width();
mHeight = rect.Height();
}
@ -87,6 +126,8 @@ TransformReferenceBox::Init(const nsSize& aDimensions)
{
MOZ_ASSERT(!mFrame && !mIsCached);
mX = 0;
mY = 0;
mWidth = aDimensions.width;
mHeight = aDimensions.height;
mIsCached = true;

View File

@ -79,11 +79,28 @@ namespace nsStyleTransformMatrix {
void Init(const nsSize& aDimensions);
/**
* The offset of the reference box from the nsIFrame's TopLeft(). This
* is non-zero only in the case of SVG content. If we can successfully
* implement UNIFIED_CONTINUATIONS at some point in the future then it
* may also be non-zero for non-SVG content.
*/
nscoord X() {
EnsureDimensionsAreCached();
return mX;
}
nscoord Y() {
EnsureDimensionsAreCached();
return mY;
}
/**
* The size of the reference box.
*/
nscoord Width() {
EnsureDimensionsAreCached();
return mWidth;
}
nscoord Height() {
EnsureDimensionsAreCached();
return mHeight;
@ -98,7 +115,7 @@ namespace nsStyleTransformMatrix {
void EnsureDimensionsAreCached();
const nsIFrame* mFrame;
nscoord mWidth, mHeight;
nscoord mX, mY, mWidth, mHeight;
bool mIsCached;
};

View File

@ -3936,14 +3936,16 @@ SVGTextFrame::ReflowSVG()
nsSVGEffects::UpdateEffects(this);
}
// Now unset the various reflow bits. Do this before calling
// FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph
// positions (to resolve transform-origin).
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
nsOverflowAreas overflowAreas(overflow, overflow);
FinishAndStoreOverflow(overflowAreas, mRect.Size());
// Now unset the various reflow bits:
mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
// XXX nsSVGContainerFrame::ReflowSVG only looks at its nsISVGChildFrame
// children, and calls ConsiderChildOverflow on them. Does it matter
// that ConsiderChildOverflow won't be called on our children?
@ -3977,10 +3979,22 @@ SVGTextFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace,
uint32_t aFlags)
{
NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame");
SVGBBox bbox;
nsIFrame* kid = GetFirstPrincipalChild();
if (kid && NS_SUBTREE_DIRTY(kid)) {
// Return an empty bbox if our kid's subtree is dirty. This may be called
// in that situation, e.g. when we're building a display list after an
// interrupted reflow. This can also be called during reflow before we've
// been reflowed, e.g. if an earlier sibling is calling FinishAndStoreOverflow and
// needs our parent's perspective matrix, which depends on the SVG bbox
// contribution of this frame. In the latter situation, when all siblings have
// been reflowed, the parent will compute its perspective and rerun
// FinishAndStoreOverflow for all its children.
return bbox;
}
UpdateGlyphPositioning();
SVGBBox bbox;
nsPresContext* presContext = PresContext();
TextRenderedRunIterator it(this);

View File

@ -298,6 +298,22 @@ nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame)
}
}
Size
nsSVGUtils::GetContextSize(const nsIFrame* aFrame)
{
Size size;
MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
const nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent());
SVGSVGElement* ctx = element->GetCtx();
if (ctx) {
size.width = ctx->GetLength(SVGContentUtils::X);
size.height = ctx->GetLength(SVGContentUtils::Y);
}
return size;
}
float
nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength)
{

View File

@ -184,6 +184,7 @@ public:
typedef mozilla::gfx::AntialiasMode AntialiasMode;
typedef mozilla::gfx::FillRule FillRule;
typedef mozilla::gfx::GeneralPattern GeneralPattern;
typedef mozilla::gfx::Size Size;
static void Init();
@ -250,6 +251,14 @@ public:
*/
static void NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame);
/**
* Percentage lengths in SVG are resolved against the width/height of the
* nearest viewport (or its viewBox, if set). This helper returns the size
* of this "context" for the given frame so that percentage values can be
* resolved.
*/
static Size GetContextSize(const nsIFrame* aFrame);
/* Computes the input length in terms of object space coordinates.
Input: rect - bounding box
length - length to be converted

View File

@ -51,7 +51,7 @@ foreignObject {
text-indent: 0;
}
/* Set |transform-origin:0% 0%;| for all SVG elements except outer-<svg>,
/* Set |transform-origin:0 0;| for all SVG elements except outer-<svg>,
noting that 'svg' as a child of 'foreignObject' counts as outer-<svg>.
*/
*:not(svg),