Quellcode-Bibliothek SVGGeometryFrame.cpp
Sprache: C
/* -*- 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/. */
// Main header first: #include"SVGGeometryFrame.h"
nsresult SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType) { // We don't invalidate for transform changes (the layers code does that). // Also note that SVGTransformableElement::GetAttributeChangeHint will // return nsChangeHint_UpdateOverflow for "transform" attribute changes // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
/* virtual */ void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
nsIFrame::DidSetComputedStyle(aOldComputedStyle); if (StyleSVGReset()->HasNonScalingStroke() &&
(!aOldComputedStyle ||
!aOldComputedStyle->StyleSVGReset()->HasNonScalingStroke())) {
SVGUtils::UpdateNonScalingStrokeStateBit(this);
} auto* element = static_cast<SVGGeometryElement*>(GetContent()); if (!aOldComputedStyle) {
element->ClearAnyCachedPath(); return;
}
constauto* oldStyleSVG = aOldComputedStyle->StyleSVG(); if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) { if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap &&
element->IsSVGElement(nsGkAtoms::path)) { // If the stroke-linecap changes to or from "butt" then our element // needs to update its cached Moz2D Path, since SVGPathData::BuildPath // decides whether or not to insert little lines into the path for zero // length subpaths base on that property.
element->ClearAnyCachedPath();
} elseif (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) { // Moz2D Path objects are fill-rule specific. // For clipPath we use clip-rule as the path's fill-rule.
element->ClearAnyCachedPath();
}
} elseif (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) { // Moz2D Path objects are fill-rule specific.
element->ClearAnyCachedPath();
}
}
if (StyleDisplay()->CalcTransformPropertyDifference(
*aOldComputedStyle->StyleDisplay())) {
NotifySVGChanged(TRANSFORM_CHANGED);
}
if (element->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle)) {
element->ClearAnyCachedPath();
SVGObserverUtils::InvalidateRenderingObservers(this);
}
}
// Matrix to the geometry's user space:
gfxMatrix newMatrix =
aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers(); if (newMatrix.IsSingular()) { return;
}
uint32_t paintOrder = StyleSVG()->mPaintOrder; if (!paintOrder) {
Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams);
PaintMarkers(aContext, aTransform, aImgParams);
} else { while (paintOrder) { auto component = StylePaintOrder(paintOrder & kPaintOrderMask); switch (component) { case StylePaintOrder::Fill:
Render(&aContext, eRenderFill, newMatrix, aImgParams); break; case StylePaintOrder::Stroke:
Render(&aContext, eRenderStroke, newMatrix, aImgParams); break; case StylePaintOrder::Markers:
PaintMarkers(aContext, aTransform, aImgParams); break; default:
MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?"); case StylePaintOrder::Normal: break;
}
paintOrder >>= kPaintOrderShift;
}
}
}
// Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit- // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing // so that we get more consistent/backwards compatible results?
RefPtr<DrawTarget> drawTarget =
gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
RefPtr<Path> path = content->GetOrBuildPath(drawTarget, fillRule); if (!path) { return nullptr; // no path, so we don't paint anything that can be hit
}
if (hitTestFlags & SVG_HIT_TEST_FILL) {
isHit = path->ContainsPoint(ToPoint(aPoint), {});
} if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
Point point = ToPoint(aPoint);
SVGContentUtils::AutoStrokeOptions stroke;
SVGContentUtils::GetStrokeOptions(&stroke, content, Style(), nullptr);
gfxMatrix userToOuterSVG; if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { // We need to transform the path back into the appropriate ancestor // coordinate system in order for non-scaled stroke to be correct. // Naturally we also need to transform the point into the same // coordinate system in order to hit-test against the path.
point = ToMatrix(userToOuterSVG).TransformPoint(point);
Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule);
}
isHit = path->StrokeContainsPoint(stroke, point, {});
}
if (isHit && SVGUtils::HitTestClip(this, aPoint)) { returnthis;
}
return nullptr;
}
void SVGGeometryFrame::ReflowSVG() {
NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), "This call is probably a wasteful mistake");
MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), "ReflowSVG mechanism not designed for this");
if (!SVGUtils::NeedsReflowSVG(this)) { return;
}
uint32_t flags = SVGUtils::eBBoxIncludeFill | SVGUtils::eBBoxIncludeStroke |
SVGUtils::eBBoxIncludeMarkers; // Our "visual" overflow rect needs to be valid for building display lists // for hit testing, which means that for certain values of 'pointer-events' // it needs to include the geometry of the fill or stroke even when the fill/ // stroke don't actually render (e.g. when stroke="none" or // stroke-opacity="0"). GetGeometryHitTestFlags() accounts for // 'pointer-events'.
uint16_t hitTestFlags = SVGUtils::GetGeometryHitTestFlags(this); if (hitTestFlags & SVG_HIT_TEST_FILL) {
flags |= SVGUtils::eBBoxIncludeFillGeometry;
} if (hitTestFlags & SVG_HIT_TEST_STROKE) {
flags |= SVGUtils::eBBoxIncludeStrokeGeometry;
}
if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { // Make sure we have our filter property (if any) before calling // FinishAndStoreOverflow (subsequent filter changes are handled off // nsChangeHint_UpdateEffects):
SVGObserverUtils::UpdateEffects(this);
}
// Invalidate, but only if this is not our first reflow (since if it is our // first reflow then we haven't had our first paint yet). if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
InvalidateFrame();
}
}
void SVGGeometryFrame::NotifySVGChanged(uint32_t aFlags) {
MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), "Invalidation logic may need adjusting");
// Changes to our ancestors may affect how we render when we are rendered as // part of our ancestor (specifically, if our coordinate context changes size // and we have percentage lengths defining our geometry, then we need to be // reflowed). However, ancestor changes cannot affect how we render when we // are rendered as part of any rendering observers that we may have. // Therefore no need to notify rendering observers here.
// Don't try to be too smart trying to avoid the ScheduleReflowSVG calls // for the stroke properties examined below. Checking HasStroke() is not // enough, since what we care about is whether we include the stroke in our // overflow rects or not, and we sometimes deliberately include stroke // when it's not visible. See the complexities of GetBBoxContribution.
if (aFlags & COORD_CONTEXT_CHANGED) { auto* geom = static_cast<SVGGeometryElement*>(GetContent()); // Stroke currently contributes to our mRect, which is why we have to take // account of stroke-width here. Note that we do not need to take account // of stroke-dashoffset since, although that can have a percentage value // that is resolved against our coordinate context, it does not affect our // mRect. constauto& strokeWidth = StyleSVG()->mStrokeWidth; if (geom->GeometryDependsOnCoordCtx() ||
(strokeWidth.IsLengthPercentage() &&
strokeWidth.AsLengthPercentage().HasPercent())) {
geom->ClearAnyCachedPath();
SVGUtils::ScheduleReflowSVG(this);
}
}
if ((aFlags & TRANSFORM_CHANGED) && StyleSVGReset()->HasNonScalingStroke()) { // Stroke currently contributes to our mRect, and our stroke depends on // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
SVGUtils::ScheduleReflowSVG(this);
}
}
constbool getStroke =
((aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) ||
((aFlags & SVGUtils::eBBoxIncludeStroke) &&
SVGUtils::HasStroke(this))) && // If this frame has non-scaling-stroke and we would like to compute its // stroke, it may cause a potential cyclical dependency if the caller is // for transform. In this case, we have to fall back to fill-box, so make // |getStroke| be false. // https://github.com/w3c/csswg-drafts/issues/9640 // // Note: // 1. We don't care about the computation of the markers below in this // function because we know the callers don't set // SVGUtils::eBBoxIncludeMarkers. // See nsStyleTransformMatrix::GetSVGBox() and // MotionPathUtils::GetRayContainReferenceSize() for more details. // 2. We have to break the dependency here *again* because the geometry // frame may be in the subtree of a SVGContainerFrame, which may not // set non-scaling-stroke.
!(StyleSVGReset()->HasNonScalingStroke() &&
(aFlags & SVGUtils::eAvoidCycleIfNonScalingStroke));
SVGContentUtils::AutoStrokeOptions strokeOptions; if (getStroke) {
SVGContentUtils::GetStrokeOptions(&strokeOptions, element, Style(), nullptr,
SVGContentUtils::eIgnoreStrokeDashing);
} else { // Override the default line width of 1.f so that when we call // GetGeometryBounds below the result doesn't include stroke bounds.
strokeOptions.mLineWidth = 0.f;
}
if (gotSimpleBounds) {
bbox = simpleBounds;
} else { // Get the bounds using a Moz2D Path object (more expensive):
RefPtr<DrawTarget> tmpDT;
tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
// Account for fill: if (getFill && !getStroke) {
Rect pathBBoxExtents = pathInBBoxSpace->GetBounds(); if (!pathBBoxExtents.IsFinite()) { // This can happen in the case that we only have a move-to command in // the path commands, in which case we know nothing gets rendered. return bbox;
}
bbox = pathBBoxExtents;
}
// Account for stroke: if (getStroke) { // Be careful when replacing the following logic to get the fill and // stroke extents independently. // You may think that you can just use the stroke extents if // there is both a fill and a stroke. In reality it may be necessary to // calculate both the fill and stroke extents. // There are two reasons for this: // // # Due to stroke dashing, in certain cases the fill extents could // actually extend outside the stroke extents. // # If the stroke is very thin, cairo won't paint any stroke, and so the // stroke bounds that it will return will be empty.
Rect strokeBBoxExtents; if (StaticPrefs::svg_Moz2D_strokeBounds_enabled()) {
gfxMatrix userToOuterSVG; if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
outerSVGToUser.Invert();
Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
RefPtr<PathBuilder> builder =
pathInUserSpace->TransformedCopyToBuilder(
ToMatrix(userToOuterSVG));
RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
strokeBBoxExtents = pathInOuterSVGSpace->GetStrokedBounds(
strokeOptions, outerSVGToBBox);
} else {
strokeBBoxExtents = pathInUserSpace->GetStrokedBounds(
strokeOptions, aToBBoxUserspace);
} if (strokeBBoxExtents.IsEmpty() && getFill) {
strokeBBoxExtents = pathInBBoxSpace->GetBounds(); if (!strokeBBoxExtents.IsFinite()) { return bbox;
}
}
} else {
Rect pathBBoxExtents = pathInBBoxSpace->GetBounds(); if (!pathBBoxExtents.IsFinite()) { return bbox;
}
strokeBBoxExtents = ToRect(SVGUtils::PathExtentsToMaxStrokeExtents(
ThebesRect(pathBBoxExtents), this, ThebesMatrix(aToBBoxUserspace)));
}
MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
bbox.UnionEdges(strokeBBoxExtents);
}
}
// Account for markers: if ((aFlags & SVGUtils::eBBoxIncludeMarkers) && element->IsMarkable()) {
SVGMarkerFrame* markerFrames[SVGMark::eTypeCount]; if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
nsTArray<SVGMark> marks;
element->GetMarkPoints(&marks); if (uint32_t num = marks.Length()) { float strokeWidth = SVGUtils::GetStrokeWidth(this); for (uint32_t i = 0; i < num; i++) { const SVGMark& mark = marks[i];
SVGMarkerFrame* frame = markerFrames[mark.type]; if (frame) {
SVGBBox mbbox = frame->GetMarkBBoxContribution(
aToBBoxUserspace, aFlags, this, mark, strokeWidth);
MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
bbox.UnionEdges(mbbox);
}
}
}
}
}
// We wait as late as possible before setting the transform so that we don't // set it unnecessarily if we return early (it's an expensive operation for // some backends).
gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
aContext->SetMatrixDouble(aTransform);
if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { // We don't complicate this code with GetAsSimplePath since the cost of // masking will dwarf Path creation overhead anyway.
RefPtr<Path> path = element->GetOrBuildPath(drawTarget, fillRule); if (path) {
ColorPattern white(ToDeviceColor(sRGBColor(1.0f, 1.0f, 1.0f, 1.0f)));
drawTarget->Fill(path, white,
DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode));
} return;
}
if ((aRenderComponents & eRenderStroke) &&
SVGUtils::HasStroke(this, contextPaint)) { // Account for vector-effect:non-scaling-stroke:
gfxMatrix userToOuterSVG; if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { // A simple Rect can't be transformed with rotate/skew, so let's switch // to using a real path: if (!path) {
path = element->GetOrBuildPath(drawTarget, fillRule); if (!path) { return;
}
simplePath.Reset();
} // We need to transform the path back into the appropriate ancestor // coordinate system, and paint it it that coordinate system, in order // for non-scaled stroke to paint correctly.
gfxMatrix outerSVGToUser = userToOuterSVG;
outerSVGToUser.Invert();
aContext->Multiply(outerSVGToUser);
Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule);
}
GeneralPattern strokePattern;
SVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, aImgParams,
contextPaint);
if (strokePattern.GetPattern()) {
SVGContentUtils::AutoStrokeOptions strokeOptions;
SVGContentUtils::GetStrokeOptions(&strokeOptions, static_cast<SVGElement*>(GetContent()),
Style(), contextPaint); // GetStrokeOptions may set the line width to zero as an optimization if (strokeOptions.mLineWidth <= 0) { return;
}
DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode); if (simplePath.IsRect()) {
drawTarget->StrokeRect(simplePath.AsRect(), strokePattern,
strokeOptions, drawOptions);
} elseif (simplePath.IsLine()) {
drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(),
strokePattern, strokeOptions, drawOptions);
} else {
drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
}
}
}
}
bool SVGGeometryFrame::IsInvisible() const { if (!StyleVisibility()->IsVisible()) { returntrue;
}
// Anything below will round to zero later down the pipeline.
constexpr float opacity_threshold = 1.0 / 128.0;
if (StyleEffects()->mOpacity <= opacity_threshold &&
SVGUtils::CanOptimizeOpacity(this)) { returntrue;
}
if (!aDryRun) { auto appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); float scale = (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx;
auto rect = simplePath.AsRect();
rect.Scale(scale);
auto offset = LayoutDevicePoint::FromAppUnits(
aItem->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx);
rect.MoveBy(offset.x, offset.y);
auto wrRect = wr::ToLayoutRect(rect);
SVGContextPaint* contextPaint =
SVGContextPaint::GetContextPaint(GetContent()); // At the moment this code path doesn't support strokes so it fine to // combine the rectangle's opacity (which has to be applied on the result) // of (filling + stroking) with the fill opacity.
auto color = wr::ToColorF(
ToDeviceColor(StyleSVG()->mFill.kind.AsColor().CalcColor(this)));
color.a *= opacity;
aBuilder.PushRect(wrRect, wrRect, !aItem->BackfaceIsHidden(), true, false,
color);
}
returntrue;
}
void SVGGeometryFrame::PaintMarkers(gfxContext& aContext, const gfxMatrix& aTransform,
imgDrawingParams& aImgParams) { auto* element = static_cast<SVGGeometryElement*>(GetContent()); if (!element->IsMarkable()) { return;
}
SVGMarkerFrame* markerFrames[SVGMark::eTypeCount]; if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) { return;
}
nsTArray<SVGMark> marks;
element->GetMarkPoints(&marks); if (marks.IsEmpty()) { return;
} float strokeWidth = GetStrokeWidthForMarkers(); for (const SVGMark& mark : marks) { if (auto* frame = markerFrames[mark.type]) {
frame->PaintMark(aContext, aTransform, this, mark, strokeWidth,
aImgParams);
}
}
}
float SVGGeometryFrame::GetStrokeWidthForMarkers() { float strokeWidth = SVGUtils::GetStrokeWidth( this, SVGContextPaint::GetContextPaint(GetContent()));
gfxMatrix userToOuterSVG; if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { // We're not interested in any translation here so we can treat this as // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us // sx and sy values as the X and Y scales. The value we want is the XY // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) / // sqrt(2). If we use the formulae from // https://scicomp.stackexchange.com/a/14103, we discover that the // normalised hypotenuse is simply the square root of the sum of the squares // of all the 2D matrix elements divided by sqrt(2). // // Note that this may need adjusting to support 3D transforms properly.
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.55Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.