Bug 1383650 - Resolve SVG geometry metrics from CSS r=longsonr,emilio

This patch makes SVG retrieve metrics from CSS style.

It doesn't handle <svg> element because geometry properties for
outer <svg> element has been partially implemented long ago, it
needs special change.

It doesn't deal with the impact on SMIL.

Differential Revision: https://phabricator.services.mozilla.com/D29992

--HG--
extra : moz-landing-system : lando
This commit is contained in:
violet 2019-05-16 13:21:20 +00:00
Родитель 6002ad9aeb
Коммит b1f320104b
17 изменённых файлов: 238 добавлений и 75 удалений

Просмотреть файл

@ -9,6 +9,7 @@
#include "nsGkAtoms.h"
#include "mozilla/dom/SVGCircleElementBinding.h"
#include "mozilla/dom/SVGLengthBinding.h"
#include "SVGGeometryProperty.h"
NS_IMPL_NS_NEW_SVG_ELEMENT(Circle)
@ -42,6 +43,8 @@ bool SVGCircleElement::IsAttributeMapped(const nsAtom* aAttribute) const {
SVGCircleElementBase::IsAttributeMapped(aAttribute);
}
namespace SVGT = SVGGeometryProperty::Tags;
//----------------------------------------------------------------------
// nsINode methods
@ -66,8 +69,11 @@ already_AddRefed<DOMSVGAnimatedLength> SVGCircleElement::R() {
/* virtual */
bool SVGCircleElement::HasValidDimensions() const {
return mLengthAttributes[ATTR_R].IsExplicitlySet() &&
mLengthAttributes[ATTR_R].GetAnimValInSpecifiedUnits() > 0;
float r;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::R>(this, &r);
return r > 0;
}
SVGElement::LengthAttributesInfo SVGCircleElement::GetLengthInfo() {
@ -82,7 +88,10 @@ bool SVGCircleElement::GetGeometryBounds(
Rect* aBounds, const StrokeOptions& aStrokeOptions,
const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) {
float x, y, r;
GetAnimatedLengthValues(&x, &y, &r, nullptr);
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::R>(this, &x, &y,
&r);
if (r <= 0.f) {
// Rendering of the element is disabled
@ -117,7 +126,9 @@ bool SVGCircleElement::GetGeometryBounds(
already_AddRefed<Path> SVGCircleElement::BuildPath(PathBuilder* aBuilder) {
float x, y, r;
GetAnimatedLengthValues(&x, &y, &r, nullptr);
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::R>(this, &x, &y,
&r);
if (r <= 0.0f) {
return nullptr;

Просмотреть файл

@ -10,6 +10,7 @@
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/RefPtr.h"
#include "SVGGeometryProperty.h"
NS_IMPL_NS_NEW_SVG_ELEMENT(Ellipse)
@ -46,6 +47,8 @@ bool SVGEllipseElement::IsAttributeMapped(const nsAtom* aAttribute) const {
SVGEllipseElementBase::IsAttributeMapped(aAttribute);
}
namespace SVGT = SVGGeometryProperty::Tags;
//----------------------------------------------------------------------
// nsINode methods
@ -75,10 +78,12 @@ already_AddRefed<DOMSVGAnimatedLength> SVGEllipseElement::Ry() {
/* virtual */
bool SVGEllipseElement::HasValidDimensions() const {
return mLengthAttributes[RX].IsExplicitlySet() &&
mLengthAttributes[RX].GetAnimValInSpecifiedUnits() > 0 &&
mLengthAttributes[RY].IsExplicitlySet() &&
mLengthAttributes[RY].GetAnimValInSpecifiedUnits() > 0;
float rx, ry;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Rx, SVGT::Ry>(this, &rx, &ry);
return rx > 0 && ry > 0;
}
SVGElement::LengthAttributesInfo SVGEllipseElement::GetLengthInfo() {
@ -93,7 +98,10 @@ bool SVGEllipseElement::GetGeometryBounds(
Rect* aBounds, const StrokeOptions& aStrokeOptions,
const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) {
float x, y, rx, ry;
GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr);
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::Rx, SVGT::Ry>(
this, &x, &y, &rx, &ry);
if (rx <= 0.f || ry <= 0.f) {
// Rendering of the element is disabled
@ -129,7 +137,10 @@ bool SVGEllipseElement::GetGeometryBounds(
already_AddRefed<Path> SVGEllipseElement::BuildPath(PathBuilder* aBuilder) {
float x, y, rx, ry;
GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr);
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Cx, SVGT::Cy, SVGT::Rx, SVGT::Ry>(
this, &x, &y, &rx, &ry);
if (rx <= 0.0f || ry <= 0.0f) {
return nullptr;

Просмотреть файл

@ -11,6 +11,7 @@
#include "mozilla/dom/SVGDocument.h"
#include "mozilla/dom/SVGForeignObjectElementBinding.h"
#include "mozilla/dom/SVGLengthBinding.h"
#include "SVGGeometryProperty.h"
NS_IMPL_NS_NEW_SVG_ELEMENT(ForeignObject)
@ -40,6 +41,8 @@ SVGForeignObjectElement::SVGForeignObjectElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
: SVGGraphicsElement(std::move(aNodeInfo)) {}
namespace SVGT = SVGGeometryProperty::Tags;
//----------------------------------------------------------------------
// nsINode methods
@ -77,8 +80,16 @@ gfxMatrix SVGForeignObjectElement::PrependLocalTransformsTo(
}
// our 'x' and 'y' attributes:
float x, y;
const_cast<SVGForeignObjectElement*>(this)->GetAnimatedLengthValues(&x, &y,
nullptr);
if (GetPrimaryFrame()) {
SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(this, &x, &y);
} else {
// This function might be called for element in display:none subtree
// (e.g. getScreenCTM), we fall back to use SVG attributes.
const_cast<SVGForeignObjectElement*>(this)->GetAnimatedLengthValues(
&x, &y, nullptr);
}
gfxMatrix toUserSpace = gfxMatrix::Translation(x, y);
if (aWhich == eChildToUserSpace) {
return toUserSpace * aMatrix;
@ -89,10 +100,12 @@ gfxMatrix SVGForeignObjectElement::PrependLocalTransformsTo(
/* virtual */
bool SVGForeignObjectElement::HasValidDimensions() const {
return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() &&
mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 &&
mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() &&
mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0;
float width, height;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>(
const_cast<SVGForeignObjectElement*>(this), &width, &height);
return width > 0 && height > 0;
}
//----------------------------------------------------------------------

Просмотреть файл

@ -0,0 +1,155 @@
/* -*- 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/. */
#ifndef mozilla_dom_SVGGeometryProperty_SVGGeometryProperty_h
#define mozilla_dom_SVGGeometryProperty_SVGGeometryProperty_h
#include "mozilla/dom/SVGElement.h"
#include "SVGAnimatedLength.h"
#include "ComputedStyle.h"
#include "nsIFrame.h"
#include <type_traits>
namespace mozilla {
namespace dom {
namespace SVGGeometryProperty {
namespace ResolverTypes {
struct LengthPercentNoAuto {};
struct LengthPercentRXY {};
struct LengthPercentWidthHeight {};
} // namespace ResolverTypes
namespace Tags {
#define SVGGEOMETRYPROPERTY_GENERATETAG(tagName, resolver, direction, \
styleStruct) \
struct tagName { \
using ResolverType = ResolverTypes::resolver; \
constexpr static auto CtxDirection = SVGContentUtils::direction; \
constexpr static auto Getter = &styleStruct::m##tagName; \
}
SVGGEOMETRYPROPERTY_GENERATETAG(X, LengthPercentNoAuto, X, nsStyleSVGReset);
SVGGEOMETRYPROPERTY_GENERATETAG(Y, LengthPercentNoAuto, Y, nsStyleSVGReset);
SVGGEOMETRYPROPERTY_GENERATETAG(Cx, LengthPercentNoAuto, X, nsStyleSVGReset);
SVGGEOMETRYPROPERTY_GENERATETAG(Cy, LengthPercentNoAuto, Y, nsStyleSVGReset);
SVGGEOMETRYPROPERTY_GENERATETAG(R, LengthPercentNoAuto, XY, nsStyleSVGReset);
SVGGEOMETRYPROPERTY_GENERATETAG(Width, LengthPercentWidthHeight, X,
nsStylePosition);
SVGGEOMETRYPROPERTY_GENERATETAG(Height, LengthPercentWidthHeight, Y,
nsStylePosition);
#undef SVGGEOMETRYPROPERTY_GENERATETAG
struct Ry;
struct Rx {
using ResolverType = ResolverTypes::LengthPercentRXY;
constexpr static auto CtxDirection = SVGContentUtils::X;
constexpr static auto Getter = &nsStyleSVGReset::mRx;
using CounterPart = Ry;
};
struct Ry {
using ResolverType = ResolverTypes::LengthPercentRXY;
constexpr static auto CtxDirection = SVGContentUtils::Y;
constexpr static auto Getter = &nsStyleSVGReset::mRy;
using CounterPart = Rx;
};
} // namespace Tags
namespace details {
template <class T>
using AlwaysFloat = float;
using CtxDirectionType = decltype(SVGContentUtils::X);
template <CtxDirectionType CTD>
float ResolvePureLengthPercentage(SVGElement* aElement,
const LengthPercentage& aLP) {
return aLP.ResolveToCSSPixelsWith(
[&] { return CSSCoord{SVGElementMetrics(aElement).GetAxisLength(CTD)}; });
}
template <class Tag>
float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
ResolverTypes::LengthPercentNoAuto) {
auto const& value = aStyle.StyleSVGReset()->*Tag::Getter;
return ResolvePureLengthPercentage<Tag::CtxDirection>(aElement, value);
}
template <class Tag>
float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
ResolverTypes::LengthPercentWidthHeight) {
static_assert(
std::is_same<Tag, Tags::Width>{} || std::is_same<Tag, Tags::Height>{},
"Wrong tag");
auto const& value = aStyle.StylePosition()->*Tag::Getter;
if (value.IsLengthPercentage()) {
return ResolvePureLengthPercentage<Tag::CtxDirection>(
aElement, value.AsLengthPercentage());
}
// |auto| and |max-content| etc. are treated as 0.
return 0.f;
}
template <class Tag>
float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement,
ResolverTypes::LengthPercentRXY) {
static_assert(std::is_same<Tag, Tags::Rx>{} || std::is_same<Tag, Tags::Ry>{},
"Wrong tag");
auto const& value = aStyle.StyleSVGReset()->*Tag::Getter;
if (value.IsLengthPercentage()) {
return ResolvePureLengthPercentage<Tag::CtxDirection>(
aElement, value.AsLengthPercentage());
}
MOZ_ASSERT(value.IsAuto());
using Rother = typename Tag::CounterPart;
auto const& valueOther = aStyle.StyleSVGReset()->*Rother::Getter;
if (valueOther.IsAuto()) {
// Per SVG2, |Rx|, |Ry| resolve to 0 if both are |auto|
return 0.f;
}
// If |Rx| is auto while |Ry| not, |Rx| gets the value of |Ry|.
return ResolvePureLengthPercentage<Rother::CtxDirection>(
aElement, valueOther.AsLengthPercentage());
}
} // namespace details
template <class Tag>
float ResolveWith(const ComputedStyle& aStyle, const SVGElement* aElement) {
// TODO: There are a lot of utilities lacking const-ness in dom/svg.
// We should fix that problem and remove this `const_cast`.
return details::ResolveImpl<Tag>(aStyle, const_cast<SVGElement*>(aElement),
typename Tag::ResolverType{});
}
// To add support for new properties, or to handle special cases for
// existing properties, you can add a new tag in |Tags| and |ResolverTypes|
// namespace, then implement the behavior in |details::ResolveImpl|.
template <class... Tags>
bool ResolveAll(const SVGElement* aElement,
details::AlwaysFloat<Tags>*... aRes) {
if (nsIFrame const* f = aElement->GetPrimaryFrame()) {
using dummy = int[];
(void)dummy{0, (*aRes = ResolveWith<Tags>(*f->Style(), aElement), 0)...};
return true;
}
return false;
}
} // namespace SVGGeometryProperty
} // namespace dom
} // namespace mozilla
#endif

Просмотреть файл

@ -5,13 +5,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/SVGRectElement.h"
#include "nsGkAtoms.h"
#include "mozilla/dom/SVGLengthBinding.h"
#include "mozilla/dom/SVGRectElementBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/gfx/PathHelpers.h"
#include "nsGkAtoms.h"
#include "SVGGeometryProperty.h"
#include <algorithm>
NS_IMPL_NS_NEW_SVG_ELEMENT(Rect)
@ -54,6 +55,8 @@ bool SVGRectElement::IsAttributeMapped(const nsAtom* aAttribute) const {
SVGRectElementBase::IsAttributeMapped(aAttribute);
}
namespace SVGT = SVGGeometryProperty::Tags;
//----------------------------------------------------------------------
// nsINode methods
@ -90,10 +93,13 @@ already_AddRefed<DOMSVGAnimatedLength> SVGRectElement::Ry() {
/* virtual */
bool SVGRectElement::HasValidDimensions() const {
return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() &&
mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 &&
mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() &&
mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0;
float width, height;
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::Width, SVGT::Height>(this, &width,
&height);
return width > 0 && height > 0;
}
SVGElement::LengthAttributesInfo SVGRectElement::GetLengthInfo() {
@ -110,8 +116,11 @@ bool SVGRectElement::GetGeometryBounds(Rect* aBounds,
const Matrix* aToNonScalingStrokeSpace) {
Rect rect;
Float rx, ry;
GetAnimatedLengthValues(&rect.x, &rect.y, &rect.width, &rect.height, &rx, &ry,
nullptr);
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
SVGT::Rx, SVGT::Ry>(
this, &rect.x, &rect.y, &rect.width, &rect.height, &rx, &ry);
if (rect.IsEmpty()) {
// Rendering of the element disabled
@ -160,7 +169,11 @@ bool SVGRectElement::GetGeometryBounds(Rect* aBounds,
void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) {
float x, y, width, height, rx, ry;
GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
SVGT::Rx, SVGT::Ry>(this, &x, &y, &width,
&height, &rx, &ry);
if (width <= 0 || height <= 0) {
aSimplePath->Reset();
@ -180,7 +193,11 @@ void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) {
already_AddRefed<Path> SVGRectElement::BuildPath(PathBuilder* aBuilder) {
float x, y, width, height, rx, ry;
GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
MOZ_ASSERT(GetPrimaryFrame());
SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height,
SVGT::Rx, SVGT::Ry>(this, &x, &y, &width,
&height, &rx, &ry);
if (width <= 0 || height <= 0) {
return nullptr;
@ -198,18 +215,6 @@ already_AddRefed<Path> SVGRectElement::BuildPath(PathBuilder* aBuilder) {
aBuilder->LineTo(r.BottomLeft());
aBuilder->Close();
} else {
// If either the 'rx' or the 'ry' attribute isn't set, then we have to
// set it to the value of the other:
bool hasRx = mLengthAttributes[ATTR_RX].IsExplicitlySet();
bool hasRy = mLengthAttributes[ATTR_RY].IsExplicitlySet();
MOZ_ASSERT(hasRx || hasRy);
if (hasRx && !hasRy) {
ry = rx;
} else if (hasRy && !hasRx) {
rx = ry;
}
// Clamp rx and ry to half the rect's width and height respectively:
rx = std::min(rx, width / 2);
ry = std::min(ry, height / 2);

Просмотреть файл

@ -1,2 +0,0 @@
[image-embedding-svg-with-viewport-units-inline-style.svg]
expected: FAIL

Просмотреть файл

@ -1,2 +0,0 @@
[image-embedding-svg-with-viewport-units.svg]
expected: FAIL

Просмотреть файл

@ -1,2 +0,0 @@
[percentage.svg]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[ellipse-01.svg]
expected:
if (not (os == "win")): FAIL
if (os == "win"): FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[ellipse-02.svg]
expected:
if (not (os == "win")): FAIL
if (os == "win"): FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[ellipse-03.svg]
expected:
if (not (os == "win")): FAIL
if (os == "win"): FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[ellipse-05.svg]
expected:
if (not (os == "win")): FAIL
if (os == "win"): FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[ellipse-06.svg]
expected:
if (not (os == "win")): FAIL
if (os == "win"): FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[ellipse-07.svg]
expected:
if (not (os == "win")): FAIL
if (os == "win"): FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[ellipse-08.svg]
expected:
if (not (os == "win")): FAIL
if (os == "win"): FAIL

Просмотреть файл

@ -21,6 +21,7 @@
<rect style="height: 0" width="10"/>
<rect style="height: -10px"/>
<rect style="height: -10px" width="10"/>
<rect style="width: calc(-10px); height: calc(-10px)"/>
</g>
<g transform="translate(150, 50)">
@ -29,22 +30,22 @@
<circle r="-10"/>
<circle style="r: 0"/>
<circle style="r: -10px"/>
<circle style="r: calc(-10px)"/>
</g>
<g transform="translate(250, 50)">
<ellipse/>
<ellipse rx="0"/>
<ellipse rx="0" ry="10"/>
<ellipse rx="-10" ry="10"/>
<ellipse ry="0"/>
<ellipse ry="0" rx="10"/>
<ellipse ry="-10" rx="10"/>
<ellipse style="rx: 0"/>
<ellipse style="rx: -10px"/>
<ellipse style="rx: 0" ry="10"/>
<ellipse style="ry: 0"/>
<ellipse style="ry: -10px"/>
<ellipse style="ry: 0" rx="10"/>
<ellipse style="rx: calc(-10px); ry: calc(-10px)"/>
</g>
</g>
</svg>

До

Ширина:  |  Высота:  |  Размер: 1.6 KiB

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Просмотреть файл

@ -22,6 +22,7 @@
<rect style="height: 0" width="10"/>
<rect style="height: -10px"/>
<rect style="height: -10px" width="10"/>
<rect style="width: calc(-10px); height: calc(-10px)"/>
</g>
<g transform="translate(150, 50)">
@ -30,22 +31,22 @@
<circle r="-10"/>
<circle style="r: 0"/>
<circle style="r: -10px"/>
<circle style="r: calc(-10px)"/>
</g>
<g transform="translate(250, 50)">
<ellipse/>
<ellipse rx="0"/>
<ellipse rx="0" ry="10"/>
<ellipse rx="-10" ry="10"/>
<ellipse ry="0"/>
<ellipse ry="0" rx="10"/>
<ellipse ry="-10" rx="10"/>
<ellipse style="rx: 0"/>
<ellipse style="rx: -10px"/>
<ellipse style="rx: 0" ry="10"/>
<ellipse style="ry: 0"/>
<ellipse style="ry: -10px"/>
<ellipse style="ry: 0" rx="10"/>
<ellipse style="rx: calc(-10px); ry: calc(-10px)"/>
</g>
</g>
<script><![CDATA[

До

Ширина:  |  Высота:  |  Размер: 2.3 KiB

После

Ширина:  |  Высота:  |  Размер: 2.4 KiB