diff options
Diffstat (limited to 'layout/svg/nsCSSClipPathInstance.cpp')
-rw-r--r-- | layout/svg/nsCSSClipPathInstance.cpp | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/layout/svg/nsCSSClipPathInstance.cpp b/layout/svg/nsCSSClipPathInstance.cpp new file mode 100644 index 000000000..828b10eac --- /dev/null +++ b/layout/svg/nsCSSClipPathInstance.cpp @@ -0,0 +1,377 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// Main header first: +#include "nsCSSClipPathInstance.h" + +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "nsCSSRendering.h" +#include "nsIFrame.h" +#include "nsRenderingContext.h" +#include "nsRuleNode.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +/* static*/ void +nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext, + nsIFrame* aFrame) +{ + auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; + +#ifdef DEBUG + StyleShapeSourceType type = clipPathStyle.GetType(); + MOZ_ASSERT(type == StyleShapeSourceType::Shape || + type == StyleShapeSourceType::Box, + "This function is used with basic-shape and geometry-box only."); +#endif + + nsCSSClipPathInstance instance(aFrame, clipPathStyle); + + aContext.NewPath(); + RefPtr<Path> path = instance.CreateClipPath(aContext.GetDrawTarget()); + aContext.SetPath(path); + aContext.Clip(); +} + +/* static*/ bool +nsCSSClipPathInstance::HitTestBasicShapeClip(nsIFrame* aFrame, + const gfxPoint& aPoint) +{ + auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath; + StyleShapeSourceType type = clipPathStyle.GetType(); + MOZ_ASSERT(type != StyleShapeSourceType::None, "unexpected none value"); + // In the future nsCSSClipPathInstance may handle <clipPath> references as + // well. For the time being return early. + if (type == StyleShapeSourceType::URL) { + return false; + } + + nsCSSClipPathInstance instance(aFrame, clipPathStyle); + + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr<Path> path = instance.CreateClipPath(drawTarget); + float pixelRatio = float(nsPresContext::AppUnitsPerCSSPixel()) / + aFrame->PresContext()->AppUnitsPerDevPixel(); + return path->ContainsPoint(ToPoint(aPoint) * pixelRatio, Matrix()); +} + +nsRect +nsCSSClipPathInstance::ComputeSVGReferenceRect() +{ + MOZ_ASSERT(mTargetFrame->GetContent()->IsSVGElement()); + nsRect r; + + // For SVG elements without associated CSS layout box, the used value for + // content-box, padding-box, border-box and margin-box is fill-box. + switch (mClipPathStyle.GetReferenceBox()) { + case StyleClipPathGeometryBox::Stroke: { + // XXX Bug 1299876 + // The size of srtoke-box is not correct if this graphic element has + // specific stroke-linejoin or stroke-linecap. + gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame, + nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeStroke); + r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, + nsPresContext::AppUnitsPerCSSPixel()); + break; + } + case StyleClipPathGeometryBox::View: { + nsIContent* content = mTargetFrame->GetContent(); + nsSVGElement* element = static_cast<nsSVGElement*>(content); + SVGSVGElement* svgElement = element->GetCtx(); + MOZ_ASSERT(svgElement); + + if (svgElement && svgElement->HasViewBoxRect()) { + // If a ‘viewBox‘ attribute is specified for the SVG viewport creating + // element: + // 1. The reference box is positioned at the origin of the coordinate + // system established by the ‘viewBox‘ attribute. + // 2. The dimension of the reference box is set to the width and height + // values of the ‘viewBox‘ attribute. + nsSVGViewBox* viewBox = svgElement->GetViewBox(); + const nsSVGViewBoxRect& value = viewBox->GetAnimValue(); + r = nsRect(nsPresContext::CSSPixelsToAppUnits(value.x), + nsPresContext::CSSPixelsToAppUnits(value.y), + nsPresContext::CSSPixelsToAppUnits(value.width), + nsPresContext::CSSPixelsToAppUnits(value.height)); + } else { + // No viewBox is specified, uses the nearest SVG viewport as reference + // box. + svgFloatSize viewportSize = svgElement->GetViewportSize(); + r = nsRect(0, 0, + nsPresContext::CSSPixelsToAppUnits(viewportSize.width), + nsPresContext::CSSPixelsToAppUnits(viewportSize.height)); + } + + break; + } + case StyleClipPathGeometryBox::NoBox: + case StyleClipPathGeometryBox::Border: + case StyleClipPathGeometryBox::Content: + case StyleClipPathGeometryBox::Padding: + case StyleClipPathGeometryBox::Margin: + case StyleClipPathGeometryBox::Fill: { + gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame, + nsSVGUtils::eBBoxIncludeFill); + r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, + nsPresContext::AppUnitsPerCSSPixel()); + break; + } + default:{ + MOZ_ASSERT_UNREACHABLE("unknown StyleClipPathGeometryBox type"); + gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame, + nsSVGUtils::eBBoxIncludeFill); + r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, + nsPresContext::AppUnitsPerCSSPixel()); + break; + } + } + + return r; +} + +nsRect +nsCSSClipPathInstance::ComputeHTMLReferenceRect() +{ + nsRect r; + + // For elements with associated CSS layout box, the used value for fill-box, + // stroke-box and view-box is border-box. + switch (mClipPathStyle.GetReferenceBox()) { + case StyleClipPathGeometryBox::Content: + r = mTargetFrame->GetContentRectRelativeToSelf(); + break; + case StyleClipPathGeometryBox::Padding: + r = mTargetFrame->GetPaddingRectRelativeToSelf(); + break; + case StyleClipPathGeometryBox::Margin: + r = mTargetFrame->GetMarginRectRelativeToSelf(); + break; + case StyleClipPathGeometryBox::NoBox: + case StyleClipPathGeometryBox::Border: + case StyleClipPathGeometryBox::Fill: + case StyleClipPathGeometryBox::Stroke: + case StyleClipPathGeometryBox::View: + r = mTargetFrame->GetRectRelativeToSelf(); + break; + default: + MOZ_ASSERT_UNREACHABLE("unknown StyleClipPathGeometryBox type"); + r = mTargetFrame->GetRectRelativeToSelf(); + break; + } + + return r; +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPath(DrawTarget* aDrawTarget) +{ + // We use ComputeSVGReferenceRect for all SVG elements, except <svg> + // element, which does have an associated CSS layout box. In this case we + // should still use ComputeHTMLReferenceRect for region computing. + nsRect r = mTargetFrame->IsFrameOfType(nsIFrame::eSVG) && + (mTargetFrame->GetType() != nsGkAtoms::svgOuterSVGFrame) + ? ComputeSVGReferenceRect() : ComputeHTMLReferenceRect(); + + if (mClipPathStyle.GetType() != StyleShapeSourceType::Shape) { + // TODO Clip to border-radius/reference box if no shape + // was specified. + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + return builder->Finish(); + } + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + r = ToAppUnits(r.ToNearestPixels(appUnitsPerDevPixel), appUnitsPerDevPixel); + + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + switch (basicShape->GetShapeType()) { + case StyleBasicShapeType::Circle: + return CreateClipPathCircle(aDrawTarget, r); + case StyleBasicShapeType::Ellipse: + return CreateClipPathEllipse(aDrawTarget, r); + case StyleBasicShapeType::Polygon: + return CreateClipPathPolygon(aDrawTarget, r); + case StyleBasicShapeType::Inset: + return CreateClipPathInset(aDrawTarget, r); + break; + default: + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected shape type"); + } + // Return an empty Path: + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + return builder->Finish(); +} + +static void +EnumerationToLength(nscoord& aCoord, int32_t aType, + nscoord aCenter, nscoord aPosMin, nscoord aPosMax) +{ + nscoord dist1 = abs(aPosMin - aCenter); + nscoord dist2 = abs(aPosMax - aCenter); + switch (aType) { + case NS_RADIUS_FARTHEST_SIDE: + aCoord = dist1 > dist2 ? dist1 : dist2; + break; + case NS_RADIUS_CLOSEST_SIDE: + aCoord = dist1 > dist2 ? dist2 : dist1; + break; + default: + NS_NOTREACHED("unknown keyword"); + break; + } +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathCircle(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nsPoint topLeft, anchor; + nsSize size = nsSize(aRefBox.width, aRefBox.height); + nsImageRenderer::ComputeObjectAnchorPoint(basicShape->GetPosition(), + size, size, + &topLeft, &anchor); + Point center = Point(anchor.x + aRefBox.x, anchor.y + aRefBox.y); + + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() == 1, "wrong number of arguments"); + float referenceLength = sqrt((aRefBox.width * aRefBox.width + + aRefBox.height * aRefBox.height) / 2.0); + nscoord r = 0; + if (coords[0].GetUnit() == eStyleUnit_Enumerated) { + nscoord horizontal, vertical; + EnumerationToLength(horizontal, coords[0].GetIntValue(), + center.x, aRefBox.x, aRefBox.x + aRefBox.width); + EnumerationToLength(vertical, coords[0].GetIntValue(), + center.y, aRefBox.y, aRefBox.y + aRefBox.height); + if (coords[0].GetIntValue() == NS_RADIUS_FARTHEST_SIDE) { + r = horizontal > vertical ? horizontal : vertical; + } else { + r = horizontal < vertical ? horizontal : vertical; + } + } else { + r = nsRuleNode::ComputeCoordPercentCalc(coords[0], referenceLength); + } + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + builder->Arc(center / appUnitsPerDevPixel, r / appUnitsPerDevPixel, + 0, Float(2 * M_PI)); + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathEllipse(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nsPoint topLeft, anchor; + nsSize size = nsSize(aRefBox.width, aRefBox.height); + nsImageRenderer::ComputeObjectAnchorPoint(basicShape->GetPosition(), + size, size, + &topLeft, &anchor); + Point center = Point(anchor.x + aRefBox.x, anchor.y + aRefBox.y); + + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() == 2, "wrong number of arguments"); + nscoord rx = 0, ry = 0; + if (coords[0].GetUnit() == eStyleUnit_Enumerated) { + EnumerationToLength(rx, coords[0].GetIntValue(), + center.x, aRefBox.x, aRefBox.x + aRefBox.width); + } else { + rx = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width); + } + if (coords[1].GetUnit() == eStyleUnit_Enumerated) { + EnumerationToLength(ry, coords[1].GetIntValue(), + center.y, aRefBox.y, aRefBox.y + aRefBox.height); + } else { + ry = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height); + } + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + EllipseToBezier(builder.get(), + center / appUnitsPerDevPixel, + Size(rx, ry) / appUnitsPerDevPixel); + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathPolygon(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() % 2 == 0 && + coords.Length() >= 2, "wrong number of arguments"); + + FillRule fillRule = basicShape->GetFillRule() == StyleFillRule::Nonzero ? + FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD; + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(fillRule); + + nscoord x = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width); + nscoord y = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height); + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + builder->MoveTo(Point(aRefBox.x + x, aRefBox.y + y) / appUnitsPerDevPixel); + for (size_t i = 2; i < coords.Length(); i += 2) { + x = nsRuleNode::ComputeCoordPercentCalc(coords[i], aRefBox.width); + y = nsRuleNode::ComputeCoordPercentCalc(coords[i + 1], aRefBox.height); + builder->LineTo(Point(aRefBox.x + x, aRefBox.y + y) / appUnitsPerDevPixel); + } + builder->Close(); + return builder->Finish(); +} + +already_AddRefed<Path> +nsCSSClipPathInstance::CreateClipPathInset(DrawTarget* aDrawTarget, + const nsRect& aRefBox) +{ + StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape(); + const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + MOZ_ASSERT(coords.Length() == 4, "wrong number of arguments"); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + nscoord appUnitsPerDevPixel = + mTargetFrame->PresContext()->AppUnitsPerDevPixel(); + + nsMargin inset(nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.height), + nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.width), + nsRuleNode::ComputeCoordPercentCalc(coords[2], aRefBox.height), + nsRuleNode::ComputeCoordPercentCalc(coords[3], aRefBox.width)); + + nsRect insetRect(aRefBox); + insetRect.Deflate(inset); + const Rect insetRectPixels = NSRectToRect(insetRect, appUnitsPerDevPixel); + const nsStyleCorners& radius = basicShape->GetRadius(); + + nscoord appUnitsRadii[8]; + + if (nsIFrame::ComputeBorderRadii(radius, insetRect.Size(), aRefBox.Size(), + Sides(), appUnitsRadii)) { + RectCornerRadii corners; + nsCSSRendering::ComputePixelRadii(appUnitsRadii, + appUnitsPerDevPixel, &corners); + + AppendRoundedRectToPath(builder, insetRectPixels, corners, true); + } else { + AppendRectToPath(builder, insetRectPixels, true); + } + return builder->Finish(); +} |