summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGGeometryProperty.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/svg/SVGGeometryProperty.h280
1 files changed, 280 insertions, 0 deletions
diff --git a/dom/svg/SVGGeometryProperty.h b/dom/svg/SVGGeometryProperty.h
new file mode 100644
index 0000000000..9349942acb
--- /dev/null
+++ b/dom/svg/SVGGeometryProperty.h
@@ -0,0 +1,280 @@
+/* -*- 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 DOM_SVG_SVGGEOMETRYPROPERTY_H_
+#define DOM_SVG_SVGGEOMETRYPROPERTY_H_
+
+#include "mozilla/SVGImageFrame.h"
+#include "mozilla/dom/SVGElement.h"
+#include "ComputedStyle.h"
+#include "SVGAnimatedLength.h"
+#include "nsComputedDOMStyle.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+#include <type_traits>
+
+namespace mozilla::dom::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);
+
+#undef SVGGEOMETRYPROPERTY_GENERATETAG
+
+struct Height;
+struct Width {
+ using ResolverType = ResolverTypes::LengthPercentWidthHeight;
+ constexpr static auto CtxDirection = SVGContentUtils::X;
+ constexpr static auto Getter = &nsStylePosition::mWidth;
+ constexpr static auto SizeGetter = &gfx::Size::width;
+ static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) {
+ return aAspectRatio.Inverted();
+ }
+ constexpr static uint32_t DefaultObjectSize = 300;
+ using CounterPart = Height;
+};
+struct Height {
+ using ResolverType = ResolverTypes::LengthPercentWidthHeight;
+ constexpr static auto CtxDirection = SVGContentUtils::Y;
+ constexpr static auto Getter = &nsStylePosition::mHeight;
+ constexpr static auto SizeGetter = &gfx::Size::height;
+ static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) {
+ return aAspectRatio;
+ }
+ constexpr static uint32_t DefaultObjectSize = 150;
+ using CounterPart = Width;
+};
+
+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 dummy = int[];
+
+using CtxDirectionType = decltype(SVGContentUtils::X);
+
+template <CtxDirectionType CTD>
+float ResolvePureLengthPercentage(const SVGElement* aElement,
+ const LengthPercentage& aLP) {
+ return aLP.ResolveToCSSPixelsWith(
+ [&] { return CSSCoord{SVGElementMetrics(aElement).GetAxisLength(CTD)}; });
+}
+
+template <class Tag>
+float ResolveImpl(ComputedStyle const& aStyle, const 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, const 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());
+ }
+
+ if (aElement->IsSVGElement(nsGkAtoms::image)) {
+ // It's not clear per SVG2 spec what should be done for values other
+ // than |auto| (e.g. |max-content|). We treat them as nonsense, thus
+ // using the initial value behavior, i.e. |auto|.
+ // The following procedure follows the Default Sizing Algorithm as
+ // specified in:
+ // https://svgwg.org/svg2-draft/embedded.html#ImageElement
+
+ SVGImageFrame* imgf = do_QueryFrame(aElement->GetPrimaryFrame());
+ if (!imgf) {
+ return 0.f;
+ }
+
+ using Other = typename Tag::CounterPart;
+ auto const& valueOther = aStyle.StylePosition()->*Other::Getter;
+
+ gfx::Size intrinsicImageSize;
+ AspectRatio aspectRatio;
+ if (!imgf->GetIntrinsicImageDimensions(intrinsicImageSize, aspectRatio)) {
+ // No image container, just return 0.
+ return 0.f;
+ }
+
+ if (valueOther.IsLengthPercentage()) {
+ // We are |auto|, but the other side has specifed length.
+ float lengthOther = ResolvePureLengthPercentage<Other::CtxDirection>(
+ aElement, valueOther.AsLengthPercentage());
+
+ if (aspectRatio) {
+ // Preserve aspect ratio if it's present.
+ return Other::AspectRatioRelative(aspectRatio)
+ .ApplyToFloat(lengthOther);
+ }
+
+ float intrinsicLength = intrinsicImageSize.*Tag::SizeGetter;
+ if (intrinsicLength >= 0) {
+ // Use the intrinsic length if it's present.
+ return intrinsicLength;
+ }
+
+ // No specified size, no aspect ratio, no intrinsic length,
+ // then use default size.
+ return Tag::DefaultObjectSize;
+ }
+
+ // |width| and |height| are both |auto|
+ if (intrinsicImageSize.*Tag::SizeGetter >= 0) {
+ return intrinsicImageSize.*Tag::SizeGetter;
+ }
+
+ if (intrinsicImageSize.*Other::SizeGetter >= 0 && aspectRatio) {
+ return Other::AspectRatioRelative(aspectRatio)
+ .ApplyTo(intrinsicImageSize.*Other::SizeGetter);
+ }
+
+ if (aspectRatio) {
+ // Resolve as a contain constraint against the default object size.
+ auto defaultAspectRatioRelative =
+ AspectRatio{float(Other::DefaultObjectSize) / Tag::DefaultObjectSize};
+ auto aspectRatioRelative = Tag::AspectRatioRelative(aspectRatio);
+
+ if (defaultAspectRatioRelative < aspectRatioRelative) {
+ // Using default length in our side and the intrinsic aspect ratio,
+ // the other side cannot be contained.
+ return aspectRatioRelative.Inverted().ApplyTo(Other::DefaultObjectSize);
+ }
+
+ return Tag::DefaultObjectSize;
+ }
+
+ return Tag::DefaultObjectSize;
+ }
+
+ // For other elements, |auto| and |max-content| etc. are treated as 0.
+ return 0.f;
+}
+
+template <class Tag>
+float ResolveImpl(ComputedStyle const& aStyle, const 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) {
+ return details::ResolveImpl<Tag>(aStyle, aElement,
+ typename Tag::ResolverType{});
+}
+
+template <class Func>
+bool DoForComputedStyle(const Element* aElement, Func aFunc) {
+ if (!aElement) {
+ return false;
+ }
+ if (const nsIFrame* f = aElement->GetPrimaryFrame()) {
+ aFunc(f->Style());
+ return true;
+ }
+
+ if (RefPtr<const ComputedStyle> computedStyle =
+ nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) {
+ aFunc(computedStyle.get());
+ return true;
+ }
+
+ return false;
+}
+
+#define SVGGEOMETRYPROPERTY_EVAL_ALL(expr) \
+ (void)details::dummy { 0, (static_cast<void>(expr), 0)... }
+
+// 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) {
+ bool res = DoForComputedStyle(aElement, [&](auto const* style) {
+ SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = ResolveWith<Tags>(*style, aElement));
+ });
+
+ if (res) {
+ return true;
+ }
+
+ SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = 0);
+ return false;
+}
+
+#undef SVGGEOMETRYPROPERTY_EVAL_ALL
+
+nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit);
+nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement,
+ uint8_t aAttrEnum);
+
+bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp);
+bool ElementMapsLengthsToStyle(SVGElement const* aElement);
+
+} // namespace mozilla::dom::SVGGeometryProperty
+
+#endif // DOM_SVG_SVGGEOMETRYPROPERTY_H_