gecko-dev/layout/svg/nsCSSClipPathInstance.cpp
cku 9d24e031f3 Bug 1305253 - Part 1. Use nsCSSClipPathInstance::ApplyBasicShapeClip when clip-path type is basic-shape or geometry-box. r=mstange
Before this patch, shouldApplyBasicShape will be set as true when the url of a
clip-path is not resolvable, for example:
  clip-path: url("#non-exist-id");

So we call nsCSSClipPathInstance::ApplyBasicShapeClip and early return even if
the clip-path's type is StyleShapeSourceType::URL. This patch aims to correct
this wrong behavior: nsCSSClipPathInstance::ApplyBasicShapeClip shoud be used
only when the type of clip-path is StyleShapeSourceType::Shape or
StyleShapeSourceType::Box.

MozReview-Commit-ID: 1ON4dEY9pva

--HG--
extra : rebase_source : 88e89526f4b57bcbb0a1db585884d578682d118c
2016-10-28 23:56:07 +08:00

378 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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();
}