michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Main header first: michael@0: #include "nsSVGPathGeometryFrame.h" michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "gfxContext.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxSVGGlyphs.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGIntegrationUtils.h" michael@0: #include "nsSVGMarkerFrame.h" michael@0: #include "nsSVGPathGeometryElement.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "SVGAnimatedTransformList.h" michael@0: #include "SVGGraphicsElement.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation michael@0: michael@0: nsIFrame* michael@0: NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, michael@0: nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsSVGPathGeometryFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSVGPathGeometryFrame) michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsQueryFrame methods michael@0: michael@0: NS_QUERYFRAME_HEAD(nsSVGPathGeometryFrame) michael@0: NS_QUERYFRAME_ENTRY(nsISVGChildFrame) michael@0: NS_QUERYFRAME_ENTRY(nsSVGPathGeometryFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsSVGPathGeometryFrameBase) michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Display list item: michael@0: michael@0: class nsDisplaySVGPathGeometry : public nsDisplayItem { michael@0: public: michael@0: nsDisplaySVGPathGeometry(nsDisplayListBuilder* aBuilder, michael@0: nsSVGPathGeometryFrame* aFrame) michael@0: : nsDisplayItem(aBuilder, aFrame) michael@0: { michael@0: MOZ_COUNT_CTOR(nsDisplaySVGPathGeometry); michael@0: NS_ABORT_IF_FALSE(aFrame, "Must have a frame!"); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplaySVGPathGeometry() { michael@0: MOZ_COUNT_DTOR(nsDisplaySVGPathGeometry); michael@0: } michael@0: #endif michael@0: michael@0: NS_DISPLAY_DECL_NAME("nsDisplaySVGPathGeometry", TYPE_SVG_PATH_GEOMETRY) michael@0: michael@0: virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, michael@0: HitTestState* aState, nsTArray *aOutFrames); michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx); michael@0: }; michael@0: michael@0: void michael@0: nsDisplaySVGPathGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, michael@0: HitTestState* aState, nsTArray *aOutFrames) michael@0: { michael@0: nsSVGPathGeometryFrame *frame = static_cast(mFrame); michael@0: nsPoint pointRelativeToReferenceFrame = aRect.Center(); michael@0: // ToReferenceFrame() includes frame->GetPosition(), our user space position. michael@0: nsPoint userSpacePt = pointRelativeToReferenceFrame - michael@0: (ToReferenceFrame() - frame->GetPosition()); michael@0: if (frame->GetFrameForPoint(userSpacePt)) { michael@0: aOutFrames->AppendElement(frame); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDisplaySVGPathGeometry::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: // ToReferenceFrame includes our mRect offset, but painting takes michael@0: // account of that too. To avoid double counting, we subtract that michael@0: // here. michael@0: nsPoint offset = ToReferenceFrame() - mFrame->GetPosition(); michael@0: michael@0: aCtx->PushState(); michael@0: aCtx->Translate(offset); michael@0: static_cast(mFrame)->PaintSVG(aCtx, nullptr); michael@0: aCtx->PopState(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsIFrame methods michael@0: michael@0: void michael@0: nsSVGPathGeometryFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); michael@0: nsSVGPathGeometryFrameBase::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGPathGeometryFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: // We don't invalidate for transform changes (the layers code does that). michael@0: // Also note that SVGTransformableElement::GetAttributeChangeHint will michael@0: // return nsChangeHint_UpdateOverflow for "transform" attribute changes michael@0: // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. michael@0: michael@0: if (aNameSpaceID == kNameSpaceID_None && michael@0: (static_cast michael@0: (mContent)->AttributeDefinesGeometry(aAttribute))) { michael@0: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: nsSVGUtils::ScheduleReflowSVG(this); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) michael@0: { michael@0: nsSVGPathGeometryFrameBase::DidSetStyleContext(aOldStyleContext); michael@0: michael@0: if (aOldStyleContext) { michael@0: float oldOpacity = aOldStyleContext->PeekStyleDisplay()->mOpacity; michael@0: float newOpacity = StyleDisplay()->mOpacity; michael@0: if (newOpacity != oldOpacity && michael@0: nsSVGUtils::CanOptimizeOpacity(this)) { michael@0: // nsIFrame::BuildDisplayListForStackingContext() is not going to create an michael@0: // nsDisplayOpacity display list item, so DLBI won't invalidate for us. michael@0: InvalidateFrame(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIAtom * michael@0: nsSVGPathGeometryFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::svgPathGeometryFrame; michael@0: } michael@0: michael@0: bool michael@0: nsSVGPathGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, michael@0: gfx::Matrix *aFromParentTransform) const michael@0: { michael@0: bool foundTransform = false; michael@0: michael@0: // Check if our parent has children-only transforms: michael@0: nsIFrame *parent = GetParent(); michael@0: if (parent && michael@0: parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { michael@0: foundTransform = static_cast(parent)-> michael@0: HasChildrenOnlyTransform(aFromParentTransform); michael@0: } michael@0: michael@0: nsSVGElement *content = static_cast(mContent); michael@0: nsSVGAnimatedTransformList* transformList = michael@0: content->GetAnimatedTransformList(); michael@0: if ((transformList && transformList->HasTransform()) || michael@0: content->GetAnimateMotionTransform()) { michael@0: if (aOwnTransform) { michael@0: *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), michael@0: nsSVGElement::eUserSpaceToParent)); michael@0: } michael@0: foundTransform = true; michael@0: } michael@0: return foundTransform; michael@0: } michael@0: michael@0: void michael@0: nsSVGPathGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (!static_cast(mContent)->HasValidDimensions()) { michael@0: return; michael@0: } michael@0: aLists.Content()->AppendNewToTop( michael@0: new (aBuilder) nsDisplaySVGPathGeometry(aBuilder, this)); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsISVGChildFrame methods michael@0: michael@0: nsresult michael@0: nsSVGPathGeometryFrame::PaintSVG(nsRenderingContext *aContext, michael@0: const nsIntRect *aDirtyRect, michael@0: nsIFrame* aTransformRoot) michael@0: { michael@0: if (!StyleVisibility()->IsVisible()) michael@0: return NS_OK; michael@0: michael@0: uint32_t paintOrder = StyleSVG()->mPaintOrder; michael@0: if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) { michael@0: Render(aContext, eRenderFill | eRenderStroke, aTransformRoot); michael@0: PaintMarkers(aContext); michael@0: } else { michael@0: while (paintOrder) { michael@0: uint32_t component = michael@0: paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1); michael@0: switch (component) { michael@0: case NS_STYLE_PAINT_ORDER_FILL: michael@0: Render(aContext, eRenderFill, aTransformRoot); michael@0: break; michael@0: case NS_STYLE_PAINT_ORDER_STROKE: michael@0: Render(aContext, eRenderStroke, aTransformRoot); michael@0: break; michael@0: case NS_STYLE_PAINT_ORDER_MARKERS: michael@0: PaintMarkers(aContext); michael@0: break; michael@0: } michael@0: paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) michael@0: { michael@0: gfxMatrix canvasTM = GetCanvasTM(FOR_HIT_TESTING); michael@0: if (canvasTM.IsSingular()) { michael@0: return nullptr; michael@0: } michael@0: uint16_t fillRule, hitTestFlags; michael@0: if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { michael@0: hitTestFlags = SVG_HIT_TEST_FILL; michael@0: fillRule = StyleSVG()->mClipRule; michael@0: } else { michael@0: hitTestFlags = GetHitTestFlags(); michael@0: // XXX once bug 614732 is fixed, aPoint won't need any conversion in order michael@0: // to compare it with mRect. michael@0: nsPoint point = michael@0: nsSVGUtils::TransformOuterSVGPointToChildFrame(aPoint, canvasTM, PresContext()); michael@0: if (!hitTestFlags || ((hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) && michael@0: !mRect.Contains(point))) michael@0: return nullptr; michael@0: fillRule = StyleSVG()->mFillRule; michael@0: } michael@0: michael@0: bool isHit = false; michael@0: michael@0: nsRefPtr tmpCtx = michael@0: new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); michael@0: michael@0: GeneratePath(tmpCtx, ToMatrix(canvasTM)); michael@0: gfxPoint userSpacePoint = michael@0: tmpCtx->DeviceToUser(gfxPoint(PresContext()->AppUnitsToGfxUnits(aPoint.x), michael@0: PresContext()->AppUnitsToGfxUnits(aPoint.y))); michael@0: michael@0: if (fillRule == NS_STYLE_FILL_RULE_EVENODD) michael@0: tmpCtx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); michael@0: else michael@0: tmpCtx->SetFillRule(gfxContext::FILL_RULE_WINDING); michael@0: michael@0: if (hitTestFlags & SVG_HIT_TEST_FILL) michael@0: isHit = tmpCtx->PointInFill(userSpacePoint); michael@0: if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) { michael@0: nsSVGUtils::SetupCairoStrokeGeometry(this, tmpCtx); michael@0: // tmpCtx's matrix may have transformed by SetupCairoStrokeGeometry michael@0: // if there is a non-scaling stroke. We need to transform userSpacePoint michael@0: // so that everything is using the same co-ordinate system. michael@0: userSpacePoint = michael@0: nsSVGUtils::GetStrokeTransform(this).Invert().Transform(userSpacePoint); michael@0: isHit = tmpCtx->PointInStroke(userSpacePoint); michael@0: } michael@0: michael@0: if (isHit && nsSVGUtils::HitTestClip(this, aPoint)) michael@0: return this; michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRect michael@0: nsSVGPathGeometryFrame::GetCoveredRegion() michael@0: { michael@0: return nsSVGUtils::TransformFrameRectToOuterSVG( michael@0: mRect, GetCanvasTM(FOR_OUTERSVG_TM), PresContext()); michael@0: } michael@0: michael@0: void michael@0: nsSVGPathGeometryFrame::ReflowSVG() michael@0: { michael@0: NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), michael@0: "This call is probably a wasteful mistake"); michael@0: michael@0: NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), michael@0: "ReflowSVG mechanism not designed for this"); michael@0: michael@0: if (!nsSVGUtils::NeedsReflowSVG(this)) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t flags = nsSVGUtils::eBBoxIncludeFill | michael@0: nsSVGUtils::eBBoxIncludeStroke | michael@0: nsSVGUtils::eBBoxIncludeMarkers; michael@0: // Our "visual" overflow rect needs to be valid for building display lists michael@0: // for hit testing, which means that for certain values of 'pointer-events' michael@0: // it needs to include the geometry of the fill or stroke even when the fill/ michael@0: // stroke don't actually render (e.g. when stroke="none" or michael@0: // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'. michael@0: uint16_t hitTestFlags = GetHitTestFlags(); michael@0: if ((hitTestFlags & SVG_HIT_TEST_FILL)) { michael@0: flags |= nsSVGUtils::eBBoxIncludeFillGeometry; michael@0: } michael@0: if ((hitTestFlags & SVG_HIT_TEST_STROKE)) { michael@0: flags |= nsSVGUtils::eBBoxIncludeStrokeGeometry; michael@0: } michael@0: michael@0: // We'd like to just pass the identity matrix to GetBBoxContribution, but if michael@0: // this frame's user space size is _very_ large/small then the extents we michael@0: // obtain below might have overflowed or otherwise be broken. This would michael@0: // cause us to end up with a broken mRect and visual overflow rect and break michael@0: // painting of this frame. This is particularly noticeable if the transforms michael@0: // between us and our nsSVGOuterSVGFrame scale this frame to a reasonable michael@0: // size. To avoid this we sadly have to do extra work to account for the michael@0: // transforms between us and our nsSVGOuterSVGFrame, even though the michael@0: // overwhelming number of SVGs will never have this problem. michael@0: // XXX Will Azure eventually save us from having to do this? michael@0: gfxSize scaleFactors = GetCanvasTM(FOR_OUTERSVG_TM).ScaleFactors(true); michael@0: bool applyScaling = fabs(scaleFactors.width) >= 1e-6 && michael@0: fabs(scaleFactors.height) >= 1e-6; michael@0: gfx::Matrix scaling; michael@0: if (applyScaling) { michael@0: scaling.Scale(scaleFactors.width, scaleFactors.height); michael@0: } michael@0: gfxRect extent = GetBBoxContribution(scaling, flags).ToThebesRect(); michael@0: if (applyScaling) { michael@0: extent.Scale(1 / scaleFactors.width, 1 / scaleFactors.height); michael@0: } michael@0: mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, michael@0: PresContext()->AppUnitsPerCSSPixel()); michael@0: michael@0: if (mState & NS_FRAME_FIRST_REFLOW) { michael@0: // Make sure we have our filter property (if any) before calling michael@0: // FinishAndStoreOverflow (subsequent filter changes are handled off michael@0: // nsChangeHint_UpdateEffects): michael@0: nsSVGEffects::UpdateEffects(this); michael@0: } michael@0: michael@0: nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); michael@0: nsOverflowAreas overflowAreas(overflow, overflow); michael@0: FinishAndStoreOverflow(overflowAreas, mRect.Size()); michael@0: michael@0: mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: michael@0: // Invalidate, but only if this is not our first reflow (since if it is our michael@0: // first reflow then we haven't had our first paint yet). michael@0: if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { michael@0: InvalidateFrame(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSVGPathGeometryFrame::NotifySVGChanged(uint32_t aFlags) michael@0: { michael@0: NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), michael@0: "Invalidation logic may need adjusting"); michael@0: michael@0: // Changes to our ancestors may affect how we render when we are rendered as michael@0: // part of our ancestor (specifically, if our coordinate context changes size michael@0: // and we have percentage lengths defining our geometry, then we need to be michael@0: // reflowed). However, ancestor changes cannot affect how we render when we michael@0: // are rendered as part of any rendering observers that we may have. michael@0: // Therefore no need to notify rendering observers here. michael@0: michael@0: // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls michael@0: // for the stroke properties examined below. Checking HasStroke() is not michael@0: // enough, since what we care about is whether we include the stroke in our michael@0: // overflow rects or not, and we sometimes deliberately include stroke michael@0: // when it's not visible. See the complexities of GetBBoxContribution. michael@0: michael@0: if (aFlags & COORD_CONTEXT_CHANGED) { michael@0: // Stroke currently contributes to our mRect, which is why we have to take michael@0: // account of stroke-width here. Note that we do not need to take account michael@0: // of stroke-dashoffset since, although that can have a percentage value michael@0: // that is resolved against our coordinate context, it does not affect our michael@0: // mRect. michael@0: if (static_cast(mContent)->GeometryDependsOnCoordCtx() || michael@0: StyleSVG()->mStrokeWidth.HasPercent()) { michael@0: nsSVGUtils::ScheduleReflowSVG(this); michael@0: } michael@0: } michael@0: michael@0: if ((aFlags & TRANSFORM_CHANGED) && michael@0: StyleSVGReset()->mVectorEffect == michael@0: NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) { michael@0: // Stroke currently contributes to our mRect, and our stroke depends on michael@0: // the transform to our outer- if |vector-effect:non-scaling-stroke|. michael@0: nsSVGUtils::ScheduleReflowSVG(this); michael@0: } michael@0: } michael@0: michael@0: SVGBBox michael@0: nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace, michael@0: uint32_t aFlags) michael@0: { michael@0: SVGBBox bbox; michael@0: michael@0: if (aToBBoxUserspace.IsSingular()) { michael@0: // XXX ReportToConsole michael@0: return bbox; michael@0: } michael@0: michael@0: nsRefPtr tmpCtx = michael@0: new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); michael@0: michael@0: GeneratePath(tmpCtx, aToBBoxUserspace); michael@0: tmpCtx->IdentityMatrix(); michael@0: michael@0: // Be careful when replacing the following logic to get the fill and stroke michael@0: // extents independently (instead of computing the stroke extents from the michael@0: // path extents). You may think that you can just use the stroke extents if michael@0: // there is both a fill and a stroke. In reality it's necessary to calculate michael@0: // both the fill and stroke extents, and take the union of the two. There are michael@0: // two reasons for this: michael@0: // michael@0: // # Due to stroke dashing, in certain cases the fill extents could actually michael@0: // extend outside the stroke extents. michael@0: // # If the stroke is very thin, cairo won't paint any stroke, and so the michael@0: // stroke bounds that it will return will be empty. michael@0: michael@0: gfxRect pathExtents = tmpCtx->GetUserPathExtent(); michael@0: michael@0: // Account for fill: michael@0: if ((aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) || michael@0: ((aFlags & nsSVGUtils::eBBoxIncludeFill) && michael@0: StyleSVG()->mFill.mType != eStyleSVGPaintType_None)) { michael@0: bbox = pathExtents; michael@0: } michael@0: michael@0: // Account for stroke: michael@0: if ((aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) || michael@0: ((aFlags & nsSVGUtils::eBBoxIncludeStroke) && michael@0: nsSVGUtils::HasStroke(this))) { michael@0: // We can't use tmpCtx->GetUserStrokeExtent() since it doesn't work for michael@0: // device space extents. Instead we approximate the stroke extents from michael@0: // pathExtents using PathExtentsToMaxStrokeExtents. michael@0: if (pathExtents.Width() <= 0 && pathExtents.Height() <= 0) { michael@0: // We have a zero length path, but it may still have non-empty stroke michael@0: // bounds depending on the value of stroke-linecap. We need to fix up michael@0: // pathExtents before it can be used with PathExtentsToMaxStrokeExtents michael@0: // though, because if pathExtents is empty, its position will not have michael@0: // been set. Happily we can use tmpCtx->GetUserStrokeExtent() to find michael@0: // the center point of the extents even though it gets the extents wrong. michael@0: nsSVGUtils::SetupCairoStrokeBBoxGeometry(this, tmpCtx); michael@0: pathExtents.MoveTo(tmpCtx->GetUserStrokeExtent().Center()); michael@0: pathExtents.SizeTo(0, 0); michael@0: } michael@0: bbox.UnionEdges(nsSVGUtils::PathExtentsToMaxStrokeExtents(pathExtents, michael@0: this, michael@0: ThebesMatrix(aToBBoxUserspace))); michael@0: } michael@0: michael@0: // Account for markers: michael@0: if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 && michael@0: static_cast(mContent)->IsMarkable()) { michael@0: michael@0: float strokeWidth = nsSVGUtils::GetStrokeWidth(this); michael@0: MarkerProperties properties = GetMarkerProperties(this); michael@0: michael@0: if (properties.MarkersExist()) { michael@0: nsTArray marks; michael@0: static_cast(mContent)->GetMarkPoints(&marks); michael@0: uint32_t num = marks.Length(); michael@0: michael@0: // These are in the same order as the nsSVGMark::Type constants. michael@0: nsSVGMarkerFrame* markerFrames[] = { michael@0: properties.GetMarkerStartFrame(), michael@0: properties.GetMarkerMidFrame(), michael@0: properties.GetMarkerEndFrame(), michael@0: }; michael@0: PR_STATIC_ASSERT(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount); michael@0: michael@0: for (uint32_t i = 0; i < num; i++) { michael@0: nsSVGMark& mark = marks[i]; michael@0: nsSVGMarkerFrame* frame = markerFrames[mark.type]; michael@0: if (frame) { michael@0: SVGBBox mbbox = michael@0: frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this, michael@0: &marks[i], strokeWidth); michael@0: bbox.UnionEdges(mbbox); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return bbox; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsSVGPathGeometryFrame methods: michael@0: michael@0: gfxMatrix michael@0: nsSVGPathGeometryFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) michael@0: { michael@0: if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) { michael@0: if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) || michael@0: (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) { michael@0: return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(mParent, "null parent"); michael@0: michael@0: nsSVGContainerFrame *parent = static_cast(mParent); michael@0: dom::SVGGraphicsElement *content = static_cast(mContent); michael@0: michael@0: return content->PrependLocalTransformsTo( michael@0: this == aTransformRoot ? gfxMatrix() : michael@0: parent->GetCanvasTM(aFor, aTransformRoot)); michael@0: } michael@0: michael@0: nsSVGPathGeometryFrame::MarkerProperties michael@0: nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame) michael@0: { michael@0: NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation"); michael@0: michael@0: MarkerProperties result; michael@0: const nsStyleSVG *style = aFrame->StyleSVG(); michael@0: result.mMarkerStart = michael@0: nsSVGEffects::GetMarkerProperty(style->mMarkerStart, aFrame, michael@0: nsSVGEffects::MarkerBeginProperty()); michael@0: result.mMarkerMid = michael@0: nsSVGEffects::GetMarkerProperty(style->mMarkerMid, aFrame, michael@0: nsSVGEffects::MarkerMiddleProperty()); michael@0: result.mMarkerEnd = michael@0: nsSVGEffects::GetMarkerProperty(style->mMarkerEnd, aFrame, michael@0: nsSVGEffects::MarkerEndProperty()); michael@0: return result; michael@0: } michael@0: michael@0: nsSVGMarkerFrame * michael@0: nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame() michael@0: { michael@0: if (!mMarkerStart) michael@0: return nullptr; michael@0: return static_cast michael@0: (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); michael@0: } michael@0: michael@0: nsSVGMarkerFrame * michael@0: nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame() michael@0: { michael@0: if (!mMarkerMid) michael@0: return nullptr; michael@0: return static_cast michael@0: (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); michael@0: } michael@0: michael@0: nsSVGMarkerFrame * michael@0: nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame() michael@0: { michael@0: if (!mMarkerEnd) michael@0: return nullptr; michael@0: return static_cast michael@0: (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); michael@0: } michael@0: michael@0: void michael@0: nsSVGPathGeometryFrame::Render(nsRenderingContext *aContext, michael@0: uint32_t aRenderComponents, michael@0: nsIFrame* aTransformRoot) michael@0: { michael@0: gfxContext *gfx = aContext->ThebesContext(); michael@0: michael@0: uint16_t renderMode = SVGAutoRenderState::GetRenderMode(aContext); michael@0: michael@0: switch (StyleSVG()->mShapeRendering) { michael@0: case NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED: michael@0: case NS_STYLE_SHAPE_RENDERING_CRISPEDGES: michael@0: gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); michael@0: break; michael@0: default: michael@0: gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE); michael@0: break; michael@0: } michael@0: michael@0: if (renderMode != SVGAutoRenderState::NORMAL) { michael@0: NS_ABORT_IF_FALSE(renderMode == SVGAutoRenderState::CLIP || michael@0: renderMode == SVGAutoRenderState::CLIP_MASK, michael@0: "Unknown render mode"); michael@0: michael@0: // In the case that |renderMode == SVGAutoRenderState::CLIP| then we don't michael@0: // use the path we generate here until further up the call stack when michael@0: // nsSVGClipPathFrame::Clip calls gfxContext::Clip. That's a problem for michael@0: // Moz2D which emits paths in user space (unlike cairo which emits paths in michael@0: // device space). gfxContext has hacks to deal with code changing the michael@0: // transform then using the current path when it is backed by Moz2D, but michael@0: // Moz2D itself does not since that would fundamentally go against its API. michael@0: // Therefore we do not want to Save()/Restore() the gfxContext here in the michael@0: // SVGAutoRenderState::CLIP case since that would block us from killing off michael@0: // gfxContext and using Moz2D directly. Not bothering to Save()/Restore() michael@0: // is actually okay, since we know that doesn't matter in the michael@0: // SVGAutoRenderState::CLIP case (at least for the current implementation). michael@0: gfxContextMatrixAutoSaveRestore autoSaveRestore; michael@0: // For now revent back to doing the save even for CLIP to fix bug 959128. michael@0: // Undo in bug 987193. michael@0: //if (renderMode != SVGAutoRenderState::CLIP) { michael@0: autoSaveRestore.SetContext(gfx); michael@0: //} michael@0: michael@0: GeneratePath(gfx, ToMatrix(GetCanvasTM(FOR_PAINTING, aTransformRoot))); michael@0: michael@0: // We used to call gfx->Restore() here, since for the michael@0: // SVGAutoRenderState::CLIP case it is important to leave the fill rule michael@0: // that we set below untouched so that the value is still set when return michael@0: // to gfxContext::Clip() further up the call stack. Since we no longer michael@0: // call gfx->Save() in the SVGAutoRenderState::CLIP case we don't need to michael@0: // worry that autoSaveRestore will delay the Restore() call for the michael@0: // CLIP_MASK case until we exit this function. michael@0: michael@0: gfxContext::FillRule oldFillRull = gfx->CurrentFillRule(); michael@0: michael@0: if (StyleSVG()->mClipRule == NS_STYLE_FILL_RULE_EVENODD) michael@0: gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); michael@0: else michael@0: gfx->SetFillRule(gfxContext::FILL_RULE_WINDING); michael@0: michael@0: if (renderMode == SVGAutoRenderState::CLIP_MASK) { michael@0: gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); michael@0: gfx->Fill(); michael@0: gfx->SetFillRule(oldFillRull); // restore, but only for CLIP_MASK michael@0: gfx->NewPath(); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: gfxContextAutoSaveRestore autoSaveRestore(gfx); michael@0: michael@0: GeneratePath(gfx, ToMatrix(GetCanvasTM(FOR_PAINTING, aTransformRoot))); michael@0: michael@0: gfxTextContextPaint *contextPaint = michael@0: (gfxTextContextPaint*)aContext->GetUserData(&gfxTextContextPaint::sUserDataKey); michael@0: michael@0: if ((aRenderComponents & eRenderFill) && michael@0: nsSVGUtils::SetupCairoFillPaint(this, gfx, contextPaint)) { michael@0: gfx->Fill(); michael@0: } michael@0: michael@0: if ((aRenderComponents & eRenderStroke) && michael@0: nsSVGUtils::SetupCairoStroke(this, gfx, contextPaint)) { michael@0: gfx->Stroke(); michael@0: } michael@0: michael@0: gfx->NewPath(); michael@0: } michael@0: michael@0: void michael@0: nsSVGPathGeometryFrame::GeneratePath(gfxContext* aContext, michael@0: const Matrix &aTransform) michael@0: { michael@0: if (aTransform.IsSingular()) { michael@0: aContext->IdentityMatrix(); michael@0: aContext->NewPath(); michael@0: return; michael@0: } michael@0: michael@0: aContext->MultiplyAndNudgeToIntegers(ThebesMatrix(aTransform)); michael@0: michael@0: // Hack to let SVGPathData::ConstructPath know if we have square caps: michael@0: const nsStyleSVG* style = StyleSVG(); michael@0: if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) { michael@0: aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE); michael@0: } michael@0: michael@0: aContext->NewPath(); michael@0: static_cast(mContent)->ConstructPath(aContext); michael@0: } michael@0: michael@0: void michael@0: nsSVGPathGeometryFrame::PaintMarkers(nsRenderingContext* aContext) michael@0: { michael@0: gfxTextContextPaint *contextPaint = michael@0: (gfxTextContextPaint*)aContext->GetUserData(&gfxTextContextPaint::sUserDataKey); michael@0: michael@0: if (static_cast(mContent)->IsMarkable()) { michael@0: MarkerProperties properties = GetMarkerProperties(this); michael@0: michael@0: if (properties.MarkersExist()) { michael@0: float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint); michael@0: michael@0: nsTArray marks; michael@0: static_cast michael@0: (mContent)->GetMarkPoints(&marks); michael@0: michael@0: uint32_t num = marks.Length(); michael@0: if (num) { michael@0: // These are in the same order as the nsSVGMark::Type constants. michael@0: nsSVGMarkerFrame* markerFrames[] = { michael@0: properties.GetMarkerStartFrame(), michael@0: properties.GetMarkerMidFrame(), michael@0: properties.GetMarkerEndFrame(), michael@0: }; michael@0: PR_STATIC_ASSERT(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount); michael@0: michael@0: for (uint32_t i = 0; i < num; i++) { michael@0: nsSVGMark& mark = marks[i]; michael@0: nsSVGMarkerFrame* frame = markerFrames[mark.type]; michael@0: if (frame) { michael@0: frame->PaintMark(aContext, this, &mark, strokeWidth); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint16_t michael@0: nsSVGPathGeometryFrame::GetHitTestFlags() michael@0: { michael@0: return nsSVGUtils::GetGeometryHitTestFlags(this); michael@0: }