float nsSVGGeometryFrame::GetStrokeWidth() { nsSVGElement *ctx = static_cast<nsSVGElement*> (mContent->IsNodeOfType(nsINode::eTEXT) ? mContent->GetParent() : mContent); return nsSVGUtils::CoordToFloat(PresContext(), ctx, GetStyleSVG()->mStrokeWidth); }
void nsSVGPathGeometryFrame::Render(nsSVGRenderState *aContext) { gfxContext *gfx = aContext->GetGfxContext(); PRUint16 renderMode = aContext->GetRenderMode(); switch (GetStyleSVG()->mShapeRendering) { case NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED: case NS_STYLE_SHAPE_RENDERING_CRISPEDGES: gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); break; default: gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE); break; } /* save/restore the state so we don't screw up the xform */ gfx->Save(); GeneratePath(gfx); if (renderMode != nsSVGRenderState::NORMAL) { gfx->Restore(); if (GetClipRule() == NS_STYLE_FILL_RULE_EVENODD) gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); else gfx->SetFillRule(gfxContext::FILL_RULE_WINDING); if (renderMode == nsSVGRenderState::CLIP_MASK) { gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); gfx->Fill(); gfx->NewPath(); } return; } if (SetupCairoFill(gfx)) { gfx->Fill(); } if (SetupCairoStroke(gfx)) { gfx->Stroke(); } gfx->NewPath(); gfx->Restore(); }
PRUint16 nsSVGGeometryFrame::GetHitTestFlags() { PRUint16 flags = 0; switch(GetStyleVisibility()->mPointerEvents) { case NS_STYLE_POINTER_EVENTS_NONE: break; case NS_STYLE_POINTER_EVENTS_AUTO: case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: if (GetStyleVisibility()->IsVisible()) { if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_FILL; if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_STROKE; if (GetStyleSVG()->mStrokeOpacity > 0) flags |= SVG_HIT_TEST_CHECK_MRECT; } break; case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: if (GetStyleVisibility()->IsVisible()) { flags |= SVG_HIT_TEST_FILL; } break; case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: if (GetStyleVisibility()->IsVisible()) { flags |= SVG_HIT_TEST_STROKE; } break; case NS_STYLE_POINTER_EVENTS_VISIBLE: if (GetStyleVisibility()->IsVisible()) { flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; } break; case NS_STYLE_POINTER_EVENTS_PAINTED: if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_FILL; if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_STROKE; if (GetStyleSVG()->mStrokeOpacity) flags |= SVG_HIT_TEST_CHECK_MRECT; break; case NS_STYLE_POINTER_EVENTS_FILL: flags |= SVG_HIT_TEST_FILL; break; case NS_STYLE_POINTER_EVENTS_STROKE: flags |= SVG_HIT_TEST_STROKE; break; case NS_STYLE_POINTER_EVENTS_ALL: flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; break; default: NS_ERROR("not reached"); break; } return flags; }
PRUint16 nsSVGGeometryFrame::GetHittestMask() { PRUint16 mask = 0; switch(GetStyleVisibility()->mPointerEvents) { case NS_STYLE_POINTER_EVENTS_NONE: break; case NS_STYLE_POINTER_EVENTS_AUTO: case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: if (GetStyleVisibility()->IsVisible()) { if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_FILL; if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_STROKE; if (GetStyleSVG()->mStrokeOpacity > 0) mask |= HITTEST_MASK_CHECK_MRECT; } break; case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_FILL; } break; case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_STROKE; } break; case NS_STYLE_POINTER_EVENTS_VISIBLE: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_FILL | HITTEST_MASK_STROKE; } break; case NS_STYLE_POINTER_EVENTS_PAINTED: if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_FILL; if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) mask |= HITTEST_MASK_STROKE; if (GetStyleSVG()->mStrokeOpacity) mask |= HITTEST_MASK_CHECK_MRECT; break; case NS_STYLE_POINTER_EVENTS_FILL: mask |= HITTEST_MASK_FILL; break; case NS_STYLE_POINTER_EVENTS_STROKE: mask |= HITTEST_MASK_STROKE; break; case NS_STYLE_POINTER_EVENTS_ALL: mask |= HITTEST_MASK_FILL | HITTEST_MASK_STROKE; break; default: NS_ERROR("not reached"); break; } return mask; }
NS_IMETHODIMP nsSVGPathGeometryFrame::UpdateCoveredRegion() { mRect.Empty(); gfxContext context(nsSVGUtils::GetThebesComputationalSurface()); GeneratePath(&context); context.IdentityMatrix(); gfxRect extent = context.GetUserPathExtent(); // Be careful when replacing the following logic to get the fill and stroke // extents independently (instead of computing the stroke extents from the // path extents). You may think that you can just use the stroke extents if // there is both a fill and a stroke. In reality it's necessary to calculate // both the fill and stroke extents, and take the union of the two. 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. if (HasStroke()) { SetupCairoStrokeGeometry(&context); if (extent.Width() <= 0 && extent.Height() <= 0) { // If 'extent' is empty, its position will not be set. Although // GetUserStrokeExtent gets the extents wrong we can still use it // to get the device space position of zero length stroked paths. extent = context.GetUserStrokeExtent(); extent.pos.x += extent.size.width / 2; extent.pos.y += extent.size.height / 2; extent.size.width = 0; extent.size.height = 0; } extent = nsSVGUtils::PathExtentsToMaxStrokeExtents(extent, this); } else if (GetStyleSVG()->mFill.mType == eStyleSVGPaintType_None) { extent = gfxRect(0, 0, 0, 0); } if (!extent.IsEmpty()) { mRect = nsSVGUtils::ToAppPixelRect(PresContext(), extent); } // Add in markers mRect = GetCoveredRegion(); return NS_OK; }
nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) { PRUint16 fillRule, hitTestFlags; if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { hitTestFlags = SVG_HIT_TEST_FILL; fillRule = GetClipRule(); } else { hitTestFlags = GetHitTestFlags(); // XXX once bug 614732 is fixed, aPoint won't need any conversion in order // to compare it with mRect. gfxMatrix canvasTM = GetCanvasTM(); if (canvasTM.IsSingular()) { return nsnull; } nsPoint point = nsSVGUtils::TransformOuterSVGPointToChildFrame(aPoint, canvasTM, PresContext()); if (!hitTestFlags || ((hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) && !mRect.Contains(point))) return nsnull; fillRule = GetStyleSVG()->mFillRule; } bool isHit = false; nsRefPtr<gfxContext> context = new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); GeneratePath(context); gfxPoint userSpacePoint = context->DeviceToUser(gfxPoint(PresContext()->AppUnitsToGfxUnits(aPoint.x), PresContext()->AppUnitsToGfxUnits(aPoint.y))); if (fillRule == NS_STYLE_FILL_RULE_EVENODD) context->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); else context->SetFillRule(gfxContext::FILL_RULE_WINDING); if (hitTestFlags & SVG_HIT_TEST_FILL) isHit = context->PointInFill(userSpacePoint); if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) { SetupCairoStrokeHitGeometry(context); isHit = context->PointInStroke(userSpacePoint); } if (isHit && nsSVGUtils::HitTestClip(this, aPoint)) return this; return nsnull; }
nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) { PRUint16 fillRule, mask; // check if we're a clipPath - cheaper than IsClipChild(), and we shouldn't // get in here for other nondisplay children if (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) { NS_ASSERTION(IsClipChild(), "should be in clipPath but we're not"); mask = HITTEST_MASK_FILL; fillRule = GetClipRule(); } else { mask = GetHittestMask(); if (!mask || (!(mask & HITTEST_MASK_FORCE_TEST) && !mRect.Contains(aPoint))) return nsnull; fillRule = GetStyleSVG()->mFillRule; } PRBool isHit = PR_FALSE; gfxContext context(nsSVGUtils::GetThebesComputationalSurface()); GeneratePath(&context); gfxPoint userSpacePoint = context.DeviceToUser(gfxPoint(PresContext()->AppUnitsToGfxUnits(aPoint.x), PresContext()->AppUnitsToGfxUnits(aPoint.y))); if (fillRule == NS_STYLE_FILL_RULE_EVENODD) context.SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); else context.SetFillRule(gfxContext::FILL_RULE_WINDING); if (mask & HITTEST_MASK_FILL) isHit = context.PointInFill(userSpacePoint); if (!isHit && (mask & HITTEST_MASK_STROKE)) { SetupCairoStrokeHitGeometry(&context); isHit = context.PointInStroke(userSpacePoint); } if (isHit && nsSVGUtils::HitTestClip(this, aPoint)) return this; return nsnull; }
PRBool nsSVGGeometryFrame::SetupCairoStroke(gfxContext *aContext) { if (!HasStroke()) { return PR_FALSE; } SetupCairoStrokeHitGeometry(aContext); const nsStyleSVG* style = GetStyleSVG(); float opacity = MaybeOptimizeOpacity(style->mStrokeOpacity); nsSVGPaintServerFrame *ps = GetPaintServer(&style->mStroke, nsSVGEffects::StrokeProperty()); if (ps && ps->SetupPaintServer(aContext, this, opacity)) return PR_TRUE; // On failure, use the fallback colour in case we have an // objectBoundingBox where the width or height of the object is zero. // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox SetupFallbackOrPaintColor(aContext, GetStyleContext(), &nsStyleSVG::mStroke, opacity); return PR_TRUE; }
void nsSVGGeometryFrame::SetupCairoStrokeGeometry(gfxContext *aContext) { float width = GetStrokeWidth(); if (width <= 0) return; aContext->SetLineWidth(width); const nsStyleSVG* style = GetStyleSVG(); switch (style->mStrokeLinecap) { case NS_STYLE_STROKE_LINECAP_BUTT: aContext->SetLineCap(gfxContext::LINE_CAP_BUTT); break; case NS_STYLE_STROKE_LINECAP_ROUND: aContext->SetLineCap(gfxContext::LINE_CAP_ROUND); break; case NS_STYLE_STROKE_LINECAP_SQUARE: aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE); break; } aContext->SetMiterLimit(style->mStrokeMiterlimit); switch (style->mStrokeLinejoin) { case NS_STYLE_STROKE_LINEJOIN_MITER: aContext->SetLineJoin(gfxContext::LINE_JOIN_MITER); break; case NS_STYLE_STROKE_LINEJOIN_ROUND: aContext->SetLineJoin(gfxContext::LINE_JOIN_ROUND); break; case NS_STYLE_STROKE_LINEJOIN_BEVEL: aContext->SetLineJoin(gfxContext::LINE_JOIN_BEVEL); break; } }
already_AddRefed<gfxPattern> nsSVGMaskFrame::ComputeMaskAlpha(nsRenderingContext *aContext, nsIFrame* aParent, const gfxMatrix &aMatrix, float aOpacity) { // If the flag is set when we get here, it means this mask frame // has already been used painting the current mask, and the document // has a mask reference loop. if (mInUse) { NS_WARNING("Mask loop detected!"); return nullptr; } AutoMaskReferencer maskRef(this); nsSVGMaskElement *mask = static_cast<nsSVGMaskElement*>(mContent); uint16_t units = mask->mEnumAttributes[nsSVGMaskElement::MASKUNITS].GetAnimValue(); gfxRect bbox; if (units == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { bbox = nsSVGUtils::GetBBox(aParent); } gfxRect maskArea = nsSVGUtils::GetRelativeRect(units, &mask->mLengthAttributes[nsSVGMaskElement::X], bbox, aParent); gfxContext *gfx = aContext->ThebesContext(); // Get the clip extents in device space: gfx->Save(); nsSVGUtils::SetClipRect(gfx, aMatrix, maskArea); gfx->IdentityMatrix(); gfxRect clipExtents = gfx->GetClipExtents(); clipExtents.RoundOut(); gfx->Restore(); bool resultOverflows; gfxIntSize surfaceSize = nsSVGUtils::ConvertToSurfaceSize(gfxSize(clipExtents.Width(), clipExtents.Height()), &resultOverflows); // 0 disables mask, < 0 is an error if (surfaceSize.width <= 0 || surfaceSize.height <= 0) return nullptr; if (resultOverflows) return nullptr; nsRefPtr<gfxImageSurface> image = new gfxImageSurface(surfaceSize, gfxASurface::ImageFormatARGB32); if (!image || image->CairoStatus()) return nullptr; // We would like to use gfxImageSurface::SetDeviceOffset() to position // 'image'. However, we need to set the same matrix on the temporary context // and pattern that we create below as is currently set on 'gfx'. // Unfortunately, any device offset set by SetDeviceOffset() is affected by // the transform passed to the SetMatrix() calls, so to avoid that we account // for the device offset in the transform rather than use SetDeviceOffset(). gfxMatrix matrix = gfx->CurrentMatrix() * gfxMatrix().Translate(-clipExtents.TopLeft()); nsRenderingContext tmpCtx; tmpCtx.Init(this->PresContext()->DeviceContext(), image); tmpCtx.ThebesContext()->SetMatrix(matrix); mMaskParent = aParent; if (mMaskParentMatrix) { *mMaskParentMatrix = aMatrix; } else { mMaskParentMatrix = new gfxMatrix(aMatrix); } for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { // The CTM of each frame referencing us can be different nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); if (SVGFrame) { SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); } nsSVGUtils::PaintFrameWithEffects(&tmpCtx, nullptr, kid); } uint8_t *data = image->Data(); int32_t stride = image->Stride(); nsIntRect rect(0, 0, surfaceSize.width, surfaceSize.height); nsSVGUtils::UnPremultiplyImageDataAlpha(data, stride, rect); if (GetStyleSVG()->mColorInterpolation == NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) { nsSVGUtils::ConvertImageDataToLinearRGB(data, stride, rect); } for (int32_t y = 0; y < surfaceSize.height; y++) for (int32_t x = 0; x < surfaceSize.width; x++) { uint8_t *pixel = data + stride * y + 4 * x; /* linearRGB -> intensity */ uint8_t alpha = static_cast<uint8_t> ((pixel[GFX_ARGB32_OFFSET_R] * 0.2125 + pixel[GFX_ARGB32_OFFSET_G] * 0.7154 + pixel[GFX_ARGB32_OFFSET_B] * 0.0721) * (pixel[GFX_ARGB32_OFFSET_A] / 255.0) * aOpacity); memset(pixel, alpha, 4); } gfxPattern *retval = new gfxPattern(image); retval->SetMatrix(matrix); NS_IF_ADDREF(retval); return retval; }
PRUint16 nsSVGGeometryFrame::GetClipRule() { return GetStyleSVG()->mClipRule; }
already_AddRefed<gfxPattern> nsSVGMaskFrame::ComputeMaskAlpha(nsSVGRenderState *aContext, nsIFrame* aParent, const gfxMatrix &aMatrix, float aOpacity) { // If the flag is set when we get here, it means this mask frame // has already been used painting the current mask, and the document // has a mask reference loop. if (mInUse) { NS_WARNING("Mask loop detected!"); return nsnull; } AutoMaskReferencer maskRef(this); nsSVGMaskElement *mask = static_cast<nsSVGMaskElement*>(mContent); PRUint16 units = mask->mEnumAttributes[nsSVGMaskElement::MASKUNITS].GetAnimValue(); gfxRect bbox; if (units == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { bbox = nsSVGUtils::GetBBox(aParent); } gfxRect maskArea = nsSVGUtils::GetRelativeRect(units, &mask->mLengthAttributes[nsSVGMaskElement::X], bbox, aParent); gfxContext *gfx = aContext->GetGfxContext(); gfx->Save(); nsSVGUtils::SetClipRect(gfx, aMatrix, maskArea); gfxRect clipExtents = gfx->GetClipExtents(); clipExtents.RoundOut(); gfx->Restore(); #ifdef DEBUG_tor fprintf(stderr, "clip extent: %f,%f %fx%f\n", clipExtents.X(), clipExtents.Y(), clipExtents.Width(), clipExtents.Height()); #endif bool resultOverflows; gfxIntSize surfaceSize = nsSVGUtils::ConvertToSurfaceSize(gfxSize(clipExtents.Width(), clipExtents.Height()), &resultOverflows); // 0 disables mask, < 0 is an error if (surfaceSize.width <= 0 || surfaceSize.height <= 0) return nsnull; if (resultOverflows) return nsnull; nsRefPtr<gfxImageSurface> image = new gfxImageSurface(surfaceSize, gfxASurface::ImageFormatARGB32); if (!image || image->CairoStatus()) return nsnull; image->SetDeviceOffset(-clipExtents.TopLeft()); nsSVGRenderState tmpState(image); mMaskParent = aParent; if (mMaskParentMatrix) { *mMaskParentMatrix = aMatrix; } else { mMaskParentMatrix = new gfxMatrix(aMatrix); } for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { // The CTM of each frame referencing us can be different nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); if (SVGFrame) { SVGFrame->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION | nsISVGChildFrame::TRANSFORM_CHANGED); } nsSVGUtils::PaintFrameWithEffects(&tmpState, nsnull, kid); } PRUint8 *data = image->Data(); PRInt32 stride = image->Stride(); nsIntRect rect(0, 0, surfaceSize.width, surfaceSize.height); nsSVGUtils::UnPremultiplyImageDataAlpha(data, stride, rect); if (GetStyleSVG()->mColorInterpolation == NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) { nsSVGUtils::ConvertImageDataToLinearRGB(data, stride, rect); } for (PRInt32 y = 0; y < surfaceSize.height; y++) for (PRInt32 x = 0; x < surfaceSize.width; x++) { PRUint8 *pixel = data + stride * y + 4 * x; /* linearRGB -> intensity */ PRUint8 alpha = static_cast<PRUint8> ((pixel[GFX_ARGB32_OFFSET_R] * 0.2125 + pixel[GFX_ARGB32_OFFSET_G] * 0.7154 + pixel[GFX_ARGB32_OFFSET_B] * 0.0721) * (pixel[GFX_ARGB32_OFFSET_A] / 255.0) * aOpacity); memset(pixel, alpha, 4); } gfxPattern *retval = new gfxPattern(image); NS_IF_ADDREF(retval); return retval; }
gfxRect nsSVGPathGeometryFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace, PRUint32 aFlags) { if (aToBBoxUserspace.IsSingular()) { // XXX ReportToConsole return gfxRect(0.0, 0.0, 0.0, 0.0); } nsRefPtr<gfxContext> context = new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); GeneratePath(context, &aToBBoxUserspace); context->IdentityMatrix(); gfxRect bbox; // Be careful when replacing the following logic to get the fill and stroke // extents independently (instead of computing the stroke extents from the // path extents). You may think that you can just use the stroke extents if // there is both a fill and a stroke. In reality it's necessary to calculate // both the fill and stroke extents, and take the union of the two. 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. gfxRect pathExtents = context->GetUserPathExtent(); // Account for fill: if ((aFlags & nsSVGUtils::eBBoxIncludeFill) != 0 && ((aFlags & nsSVGUtils::eBBoxIgnoreFillIfNone) == 0 || GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None)) { bbox = pathExtents; } // Account for stroke: if ((aFlags & nsSVGUtils::eBBoxIncludeStroke) != 0 && ((aFlags & nsSVGUtils::eBBoxIgnoreStrokeIfNone) == 0 || HasStroke())) { // We can't use context->GetUserStrokeExtent() since it doesn't work for // device space extents. Instead we approximate the stroke extents from // pathExtents using PathExtentsToMaxStrokeExtents. if (pathExtents.Width() <= 0 && pathExtents.Height() <= 0) { // We have a zero length path, but it may still have non-empty stroke // bounds depending on the value of stroke-linecap. We need to fix up // pathExtents before it can be used with PathExtentsToMaxStrokeExtents // though, because if pathExtents is empty, its position will not have // been set. Happily we can use context->GetUserStrokeExtent() to find // the center point of the extents even though it gets the extents wrong. SetupCairoStrokeGeometry(context); pathExtents.MoveTo(context->GetUserStrokeExtent().Center()); pathExtents.SizeTo(0, 0); } bbox = bbox.Union(nsSVGUtils::PathExtentsToMaxStrokeExtents(pathExtents, this, aToBBoxUserspace)); } // Account for markers: if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 && static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) { float strokeWidth = GetStrokeWidth(); MarkerProperties properties = GetMarkerProperties(this); if (properties.MarkersExist()) { nsTArray<nsSVGMark> marks; static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks); PRUint32 num = marks.Length(); if (num) { nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame(); if (frame) { gfxRect mbbox = frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this, &marks[0], strokeWidth); bbox.UnionRect(bbox, mbbox); } frame = properties.GetMarkerMidFrame(); if (frame) { for (PRUint32 i = 1; i < num - 1; i++) { gfxRect mbbox = frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this, &marks[i], strokeWidth); bbox.UnionRect(bbox, mbbox); } } frame = properties.GetMarkerEndFrame(); if (frame) { gfxRect mbbox = frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this, &marks[num-1], strokeWidth); bbox.UnionRect(bbox, mbbox); } } } } return bbox; }