Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | // Main header first: |
michael@0 | 7 | // This is also necessary to ensure our definition of M_SQRT1_2 is picked up |
michael@0 | 8 | #include "nsSVGUtils.h" |
michael@0 | 9 | #include <algorithm> |
michael@0 | 10 | |
michael@0 | 11 | // Keep others in (case-insensitive) order: |
michael@0 | 12 | #include "gfx2DGlue.h" |
michael@0 | 13 | #include "gfxContext.h" |
michael@0 | 14 | #include "gfxMatrix.h" |
michael@0 | 15 | #include "gfxPlatform.h" |
michael@0 | 16 | #include "gfxRect.h" |
michael@0 | 17 | #include "gfxUtils.h" |
michael@0 | 18 | #include "mozilla/gfx/2D.h" |
michael@0 | 19 | #include "mozilla/Preferences.h" |
michael@0 | 20 | #include "nsCSSFrameConstructor.h" |
michael@0 | 21 | #include "nsDisplayList.h" |
michael@0 | 22 | #include "nsFilterInstance.h" |
michael@0 | 23 | #include "nsFrameList.h" |
michael@0 | 24 | #include "nsGkAtoms.h" |
michael@0 | 25 | #include "nsIContent.h" |
michael@0 | 26 | #include "nsIDocument.h" |
michael@0 | 27 | #include "nsIFrame.h" |
michael@0 | 28 | #include "nsIPresShell.h" |
michael@0 | 29 | #include "nsISVGChildFrame.h" |
michael@0 | 30 | #include "nsPresContext.h" |
michael@0 | 31 | #include "nsRenderingContext.h" |
michael@0 | 32 | #include "nsStyleCoord.h" |
michael@0 | 33 | #include "nsStyleStruct.h" |
michael@0 | 34 | #include "nsSVGClipPathFrame.h" |
michael@0 | 35 | #include "nsSVGContainerFrame.h" |
michael@0 | 36 | #include "nsSVGEffects.h" |
michael@0 | 37 | #include "nsSVGFilterPaintCallback.h" |
michael@0 | 38 | #include "nsSVGForeignObjectFrame.h" |
michael@0 | 39 | #include "gfxSVGGlyphs.h" |
michael@0 | 40 | #include "nsSVGInnerSVGFrame.h" |
michael@0 | 41 | #include "nsSVGIntegrationUtils.h" |
michael@0 | 42 | #include "nsSVGLength2.h" |
michael@0 | 43 | #include "nsSVGMaskFrame.h" |
michael@0 | 44 | #include "nsSVGOuterSVGFrame.h" |
michael@0 | 45 | #include "mozilla/dom/SVGPathElement.h" |
michael@0 | 46 | #include "nsSVGPathGeometryElement.h" |
michael@0 | 47 | #include "nsSVGPathGeometryFrame.h" |
michael@0 | 48 | #include "nsSVGPaintServerFrame.h" |
michael@0 | 49 | #include "mozilla/dom/SVGSVGElement.h" |
michael@0 | 50 | #include "nsTextFrame.h" |
michael@0 | 51 | #include "SVGContentUtils.h" |
michael@0 | 52 | #include "mozilla/unused.h" |
michael@0 | 53 | |
michael@0 | 54 | using namespace mozilla; |
michael@0 | 55 | using namespace mozilla::dom; |
michael@0 | 56 | using namespace mozilla::gfx; |
michael@0 | 57 | |
michael@0 | 58 | static bool sSVGDisplayListHitTestingEnabled; |
michael@0 | 59 | static bool sSVGDisplayListPaintingEnabled; |
michael@0 | 60 | |
michael@0 | 61 | bool |
michael@0 | 62 | NS_SVGDisplayListHitTestingEnabled() |
michael@0 | 63 | { |
michael@0 | 64 | return sSVGDisplayListHitTestingEnabled; |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | bool |
michael@0 | 68 | NS_SVGDisplayListPaintingEnabled() |
michael@0 | 69 | { |
michael@0 | 70 | return sSVGDisplayListPaintingEnabled; |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | // we only take the address of this: |
michael@0 | 74 | static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey; |
michael@0 | 75 | |
michael@0 | 76 | SVGAutoRenderState::SVGAutoRenderState(nsRenderingContext *aContext, |
michael@0 | 77 | RenderMode aMode |
michael@0 | 78 | MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
michael@0 | 79 | : mContext(aContext) |
michael@0 | 80 | , mOriginalRenderState(nullptr) |
michael@0 | 81 | , mMode(aMode) |
michael@0 | 82 | , mPaintingToWindow(false) |
michael@0 | 83 | { |
michael@0 | 84 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
michael@0 | 85 | mOriginalRenderState = aContext->RemoveUserData(&sSVGAutoRenderStateKey); |
michael@0 | 86 | // We always remove ourselves from aContext before it dies, so |
michael@0 | 87 | // passing nullptr as the destroy function is okay. |
michael@0 | 88 | aContext->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); |
michael@0 | 89 | } |
michael@0 | 90 | |
michael@0 | 91 | SVGAutoRenderState::~SVGAutoRenderState() |
michael@0 | 92 | { |
michael@0 | 93 | mContext->RemoveUserData(&sSVGAutoRenderStateKey); |
michael@0 | 94 | if (mOriginalRenderState) { |
michael@0 | 95 | mContext->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState, nullptr); |
michael@0 | 96 | } |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | void |
michael@0 | 100 | SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) |
michael@0 | 101 | { |
michael@0 | 102 | mPaintingToWindow = aPaintingToWindow; |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | /* static */ SVGAutoRenderState::RenderMode |
michael@0 | 106 | SVGAutoRenderState::GetRenderMode(nsRenderingContext *aContext) |
michael@0 | 107 | { |
michael@0 | 108 | void *state = aContext->GetUserData(&sSVGAutoRenderStateKey); |
michael@0 | 109 | if (state) { |
michael@0 | 110 | return static_cast<SVGAutoRenderState*>(state)->mMode; |
michael@0 | 111 | } |
michael@0 | 112 | return NORMAL; |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | /* static */ bool |
michael@0 | 116 | SVGAutoRenderState::IsPaintingToWindow(nsRenderingContext *aContext) |
michael@0 | 117 | { |
michael@0 | 118 | void *state = aContext->GetUserData(&sSVGAutoRenderStateKey); |
michael@0 | 119 | if (state) { |
michael@0 | 120 | return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow; |
michael@0 | 121 | } |
michael@0 | 122 | return false; |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | void |
michael@0 | 126 | nsSVGUtils::Init() |
michael@0 | 127 | { |
michael@0 | 128 | Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled, |
michael@0 | 129 | "svg.display-lists.hit-testing.enabled"); |
michael@0 | 130 | |
michael@0 | 131 | Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled, |
michael@0 | 132 | "svg.display-lists.painting.enabled"); |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | nsSVGDisplayContainerFrame* |
michael@0 | 136 | nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame) |
michael@0 | 137 | { |
michael@0 | 138 | NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); |
michael@0 | 139 | if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { |
michael@0 | 140 | return nullptr; |
michael@0 | 141 | } |
michael@0 | 142 | while ((aFrame = aFrame->GetParent())) { |
michael@0 | 143 | NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); |
michael@0 | 144 | if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame || |
michael@0 | 145 | aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { |
michael@0 | 146 | return do_QueryFrame(aFrame); |
michael@0 | 147 | } |
michael@0 | 148 | } |
michael@0 | 149 | NS_NOTREACHED("This is not reached. It's only needed to compile."); |
michael@0 | 150 | return nullptr; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | nsRect |
michael@0 | 154 | nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame, |
michael@0 | 155 | const nsRect &aPreFilterRect) |
michael@0 | 156 | { |
michael@0 | 157 | NS_ABORT_IF_FALSE(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT, |
michael@0 | 158 | "Called on invalid frame type"); |
michael@0 | 159 | |
michael@0 | 160 | nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); |
michael@0 | 161 | if (!property || !property->ReferencesValidResources()) { |
michael@0 | 162 | return aPreFilterRect; |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect); |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | bool |
michael@0 | 169 | nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame) |
michael@0 | 170 | { |
michael@0 | 171 | return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); |
michael@0 | 172 | } |
michael@0 | 173 | |
michael@0 | 174 | bool |
michael@0 | 175 | nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) |
michael@0 | 176 | { |
michael@0 | 177 | nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); |
michael@0 | 178 | do { |
michael@0 | 179 | if (outer->IsCallingReflowSVG()) { |
michael@0 | 180 | return true; |
michael@0 | 181 | } |
michael@0 | 182 | outer = GetOuterSVGFrame(outer->GetParent()); |
michael@0 | 183 | } while (outer); |
michael@0 | 184 | return false; |
michael@0 | 185 | } |
michael@0 | 186 | |
michael@0 | 187 | void |
michael@0 | 188 | nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame) |
michael@0 | 189 | { |
michael@0 | 190 | NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG), |
michael@0 | 191 | "Passed bad frame!"); |
michael@0 | 192 | |
michael@0 | 193 | // If this is triggered, the callers should be fixed to call us before |
michael@0 | 194 | // ReflowSVG is called. If we try to mark dirty bits on frames while we're |
michael@0 | 195 | // in the process of removing them, things will get messed up. |
michael@0 | 196 | NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame), |
michael@0 | 197 | "Do not call under nsISVGChildFrame::ReflowSVG!"); |
michael@0 | 198 | |
michael@0 | 199 | // We don't call nsSVGEffects::InvalidateRenderingObservers here because |
michael@0 | 200 | // we should only be called under InvalidateAndScheduleReflowSVG (which |
michael@0 | 201 | // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames |
michael@0 | 202 | // (at which point the frame has no observers). |
michael@0 | 203 | |
michael@0 | 204 | if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) { |
michael@0 | 205 | return; |
michael@0 | 206 | } |
michael@0 | 207 | |
michael@0 | 208 | if (aFrame->GetStateBits() & |
michael@0 | 209 | (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { |
michael@0 | 210 | // Nothing to do if we're already dirty, or if the outer-<svg> |
michael@0 | 211 | // hasn't yet had its initial reflow. |
michael@0 | 212 | return; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | nsSVGOuterSVGFrame *outerSVGFrame = nullptr; |
michael@0 | 216 | |
michael@0 | 217 | // We must not add dirty bits to the nsSVGOuterSVGFrame or else |
michael@0 | 218 | // PresShell::FrameNeedsReflow won't work when we pass it in below. |
michael@0 | 219 | if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { |
michael@0 | 220 | outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(aFrame); |
michael@0 | 221 | } else { |
michael@0 | 222 | aFrame->AddStateBits(NS_FRAME_IS_DIRTY); |
michael@0 | 223 | |
michael@0 | 224 | nsIFrame *f = aFrame->GetParent(); |
michael@0 | 225 | while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { |
michael@0 | 226 | if (f->GetStateBits() & |
michael@0 | 227 | (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { |
michael@0 | 228 | return; |
michael@0 | 229 | } |
michael@0 | 230 | f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
michael@0 | 231 | f = f->GetParent(); |
michael@0 | 232 | NS_ABORT_IF_FALSE(f->IsFrameOfType(nsIFrame::eSVG), |
michael@0 | 233 | "NS_STATE_IS_OUTER_SVG check above not valid!"); |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f); |
michael@0 | 237 | |
michael@0 | 238 | NS_ABORT_IF_FALSE(outerSVGFrame && |
michael@0 | 239 | outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame, |
michael@0 | 240 | "Did not find nsSVGOuterSVGFrame!"); |
michael@0 | 241 | } |
michael@0 | 242 | |
michael@0 | 243 | if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) { |
michael@0 | 244 | // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no |
michael@0 | 245 | // need to call PresShell::FrameNeedsReflow, since we have an |
michael@0 | 246 | // nsSVGOuterSVGFrame::DidReflow call pending. |
michael@0 | 247 | return; |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | nsFrameState dirtyBit = |
michael@0 | 251 | (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN); |
michael@0 | 252 | |
michael@0 | 253 | aFrame->PresContext()->PresShell()->FrameNeedsReflow( |
michael@0 | 254 | outerSVGFrame, nsIPresShell::eResize, dirtyBit); |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | bool |
michael@0 | 258 | nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame) |
michael@0 | 259 | { |
michael@0 | 260 | NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG), |
michael@0 | 261 | "SVG uses bits differently!"); |
michael@0 | 262 | |
michael@0 | 263 | // The flags we test here may change, hence why we have this separate |
michael@0 | 264 | // function. |
michael@0 | 265 | return NS_SUBTREE_DIRTY(aFrame); |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | void |
michael@0 | 269 | nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame) |
michael@0 | 270 | { |
michael@0 | 271 | NS_ABORT_IF_FALSE(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG), |
michael@0 | 272 | "Not expecting to be called on the outer SVG Frame"); |
michael@0 | 273 | |
michael@0 | 274 | aFrame = aFrame->GetParent(); |
michael@0 | 275 | |
michael@0 | 276 | while (aFrame) { |
michael@0 | 277 | if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) |
michael@0 | 278 | return; |
michael@0 | 279 | |
michael@0 | 280 | nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); |
michael@0 | 281 | if (property) { |
michael@0 | 282 | property->Invalidate(); |
michael@0 | 283 | } |
michael@0 | 284 | aFrame = aFrame->GetParent(); |
michael@0 | 285 | } |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | float |
michael@0 | 289 | nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength) |
michael@0 | 290 | { |
michael@0 | 291 | float axis; |
michael@0 | 292 | |
michael@0 | 293 | switch (aLength->GetCtxType()) { |
michael@0 | 294 | case SVGContentUtils::X: |
michael@0 | 295 | axis = aRect.Width(); |
michael@0 | 296 | break; |
michael@0 | 297 | case SVGContentUtils::Y: |
michael@0 | 298 | axis = aRect.Height(); |
michael@0 | 299 | break; |
michael@0 | 300 | case SVGContentUtils::XY: |
michael@0 | 301 | axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( |
michael@0 | 302 | aRect.Width(), aRect.Height())); |
michael@0 | 303 | break; |
michael@0 | 304 | default: |
michael@0 | 305 | NS_NOTREACHED("unexpected ctx type"); |
michael@0 | 306 | axis = 0.0f; |
michael@0 | 307 | break; |
michael@0 | 308 | } |
michael@0 | 309 | if (aLength->IsPercentage()) { |
michael@0 | 310 | // Multiply first to avoid precision errors: |
michael@0 | 311 | return axis * aLength->GetAnimValInSpecifiedUnits() / 100; |
michael@0 | 312 | } |
michael@0 | 313 | return aLength->GetAnimValue(static_cast<SVGSVGElement*>(nullptr)) * axis; |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | float |
michael@0 | 317 | nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength) |
michael@0 | 318 | { |
michael@0 | 319 | return aLength->GetAnimValue(aSVGElement); |
michael@0 | 320 | } |
michael@0 | 321 | |
michael@0 | 322 | float |
michael@0 | 323 | nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength) |
michael@0 | 324 | { |
michael@0 | 325 | return aLength->GetAnimValue(aNonSVGContext); |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | nsSVGOuterSVGFrame * |
michael@0 | 329 | nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame) |
michael@0 | 330 | { |
michael@0 | 331 | while (aFrame) { |
michael@0 | 332 | if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { |
michael@0 | 333 | return static_cast<nsSVGOuterSVGFrame*>(aFrame); |
michael@0 | 334 | } |
michael@0 | 335 | aFrame = aFrame->GetParent(); |
michael@0 | 336 | } |
michael@0 | 337 | |
michael@0 | 338 | return nullptr; |
michael@0 | 339 | } |
michael@0 | 340 | |
michael@0 | 341 | nsIFrame* |
michael@0 | 342 | nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect) |
michael@0 | 343 | { |
michael@0 | 344 | nsISVGChildFrame* svg = do_QueryFrame(aFrame); |
michael@0 | 345 | if (!svg) |
michael@0 | 346 | return nullptr; |
michael@0 | 347 | *aRect = (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ? |
michael@0 | 348 | nsRect(0, 0, 0, 0) : svg->GetCoveredRegion(); |
michael@0 | 349 | return GetOuterSVGFrame(aFrame); |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | gfxMatrix |
michael@0 | 353 | nsSVGUtils::GetCanvasTM(nsIFrame *aFrame, uint32_t aFor, |
michael@0 | 354 | nsIFrame* aTransformRoot) |
michael@0 | 355 | { |
michael@0 | 356 | // XXX yuck, we really need a common interface for GetCanvasTM |
michael@0 | 357 | |
michael@0 | 358 | if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { |
michael@0 | 359 | return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) && |
michael@0 | 363 | !aTransformRoot) { |
michael@0 | 364 | if ((aFor == nsISVGChildFrame::FOR_PAINTING && |
michael@0 | 365 | NS_SVGDisplayListPaintingEnabled()) || |
michael@0 | 366 | (aFor == nsISVGChildFrame::FOR_HIT_TESTING && |
michael@0 | 367 | NS_SVGDisplayListHitTestingEnabled())) { |
michael@0 | 368 | return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); |
michael@0 | 369 | } |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | nsIAtom* type = aFrame->GetType(); |
michael@0 | 373 | if (type == nsGkAtoms::svgForeignObjectFrame) { |
michael@0 | 374 | return static_cast<nsSVGForeignObjectFrame*>(aFrame)-> |
michael@0 | 375 | GetCanvasTM(aFor, aTransformRoot); |
michael@0 | 376 | } |
michael@0 | 377 | if (type == nsGkAtoms::svgOuterSVGFrame) { |
michael@0 | 378 | return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame); |
michael@0 | 382 | if (containerFrame) { |
michael@0 | 383 | return containerFrame->GetCanvasTM(aFor, aTransformRoot); |
michael@0 | 384 | } |
michael@0 | 385 | |
michael@0 | 386 | return static_cast<nsSVGPathGeometryFrame*>(aFrame)-> |
michael@0 | 387 | GetCanvasTM(aFor, aTransformRoot); |
michael@0 | 388 | } |
michael@0 | 389 | |
michael@0 | 390 | gfxMatrix |
michael@0 | 391 | nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame, uint32_t aFor) |
michael@0 | 392 | { |
michael@0 | 393 | NS_ASSERTION(aFor == nsISVGChildFrame::FOR_OUTERSVG_TM, |
michael@0 | 394 | "Unexpected aFor?"); |
michael@0 | 395 | |
michael@0 | 396 | nsISVGChildFrame* svgFrame = do_QueryFrame(aFrame); |
michael@0 | 397 | NS_ASSERTION(svgFrame, "bad frame"); |
michael@0 | 398 | |
michael@0 | 399 | gfxMatrix tm; |
michael@0 | 400 | if (svgFrame) { |
michael@0 | 401 | nsSVGElement *content = static_cast<nsSVGElement*>(aFrame->GetContent()); |
michael@0 | 402 | tm = content->PrependLocalTransformsTo( |
michael@0 | 403 | GetCanvasTM(aFrame->GetParent(), aFor), |
michael@0 | 404 | nsSVGElement::eUserSpaceToParent); |
michael@0 | 405 | } |
michael@0 | 406 | return tm; |
michael@0 | 407 | } |
michael@0 | 408 | |
michael@0 | 409 | void |
michael@0 | 410 | nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags) |
michael@0 | 411 | { |
michael@0 | 412 | nsIFrame *kid = aFrame->GetFirstPrincipalChild(); |
michael@0 | 413 | |
michael@0 | 414 | while (kid) { |
michael@0 | 415 | nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); |
michael@0 | 416 | if (SVGFrame) { |
michael@0 | 417 | SVGFrame->NotifySVGChanged(aFlags); |
michael@0 | 418 | } else { |
michael@0 | 419 | NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsSVGText(), |
michael@0 | 420 | "SVG frame expected"); |
michael@0 | 421 | // recurse into the children of container frames e.g. <clipPath>, <mask> |
michael@0 | 422 | // in case they have child frames with transformation matrices |
michael@0 | 423 | if (kid->IsFrameOfType(nsIFrame::eSVG)) { |
michael@0 | 424 | NotifyChildrenOfSVGChange(kid, aFlags); |
michael@0 | 425 | } |
michael@0 | 426 | } |
michael@0 | 427 | kid = kid->GetNextSibling(); |
michael@0 | 428 | } |
michael@0 | 429 | } |
michael@0 | 430 | |
michael@0 | 431 | // ************************************************************ |
michael@0 | 432 | |
michael@0 | 433 | class SVGPaintCallback : public nsSVGFilterPaintCallback |
michael@0 | 434 | { |
michael@0 | 435 | public: |
michael@0 | 436 | virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget, |
michael@0 | 437 | const nsIntRect* aDirtyRect, |
michael@0 | 438 | nsIFrame* aTransformRoot) MOZ_OVERRIDE |
michael@0 | 439 | { |
michael@0 | 440 | nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget); |
michael@0 | 441 | NS_ASSERTION(svgChildFrame, "Expected SVG frame here"); |
michael@0 | 442 | |
michael@0 | 443 | nsIntRect* dirtyRect = nullptr; |
michael@0 | 444 | nsIntRect tmpDirtyRect; |
michael@0 | 445 | |
michael@0 | 446 | // aDirtyRect is in user-space pixels, we need to convert to |
michael@0 | 447 | // outer-SVG-frame-relative device pixels. |
michael@0 | 448 | if (aDirtyRect) { |
michael@0 | 449 | gfxMatrix userToDeviceSpace = |
michael@0 | 450 | nsSVGUtils::GetCanvasTM(aTarget, nsISVGChildFrame::FOR_PAINTING, aTransformRoot); |
michael@0 | 451 | if (userToDeviceSpace.IsSingular()) { |
michael@0 | 452 | return; |
michael@0 | 453 | } |
michael@0 | 454 | gfxRect dirtyBounds = userToDeviceSpace.TransformBounds( |
michael@0 | 455 | gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); |
michael@0 | 456 | dirtyBounds.RoundOut(); |
michael@0 | 457 | if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) { |
michael@0 | 458 | dirtyRect = &tmpDirtyRect; |
michael@0 | 459 | } |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | svgChildFrame->PaintSVG(aContext, dirtyRect, aTransformRoot); |
michael@0 | 463 | } |
michael@0 | 464 | }; |
michael@0 | 465 | |
michael@0 | 466 | void |
michael@0 | 467 | nsSVGUtils::PaintFrameWithEffects(nsRenderingContext *aContext, |
michael@0 | 468 | const nsIntRect *aDirtyRect, |
michael@0 | 469 | nsIFrame *aFrame, |
michael@0 | 470 | nsIFrame *aTransformRoot) |
michael@0 | 471 | { |
michael@0 | 472 | NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || |
michael@0 | 473 | (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || |
michael@0 | 474 | aFrame->PresContext()->IsGlyph(), |
michael@0 | 475 | "If display lists are enabled, only painting of non-display " |
michael@0 | 476 | "SVG should take this code path"); |
michael@0 | 477 | |
michael@0 | 478 | nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); |
michael@0 | 479 | if (!svgChildFrame) |
michael@0 | 480 | return; |
michael@0 | 481 | |
michael@0 | 482 | float opacity = aFrame->StyleDisplay()->mOpacity; |
michael@0 | 483 | if (opacity == 0.0f) |
michael@0 | 484 | return; |
michael@0 | 485 | |
michael@0 | 486 | const nsIContent* content = aFrame->GetContent(); |
michael@0 | 487 | if (content->IsSVG() && |
michael@0 | 488 | !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { |
michael@0 | 489 | return; |
michael@0 | 490 | } |
michael@0 | 491 | |
michael@0 | 492 | /* Properties are added lazily and may have been removed by a restyle, |
michael@0 | 493 | so make sure all applicable ones are set again. */ |
michael@0 | 494 | |
michael@0 | 495 | nsSVGEffects::EffectProperties effectProperties = |
michael@0 | 496 | nsSVGEffects::GetEffectProperties(aFrame); |
michael@0 | 497 | |
michael@0 | 498 | bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); |
michael@0 | 499 | |
michael@0 | 500 | if (aDirtyRect && |
michael@0 | 501 | !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { |
michael@0 | 502 | // Here we convert aFrame's paint bounds to outer-<svg> device space, |
michael@0 | 503 | // compare it to aDirtyRect, and return early if they don't intersect. |
michael@0 | 504 | // We don't do this optimization for nondisplay SVG since nondisplay |
michael@0 | 505 | // SVG doesn't maintain bounds/overflow rects. |
michael@0 | 506 | nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); |
michael@0 | 507 | if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || |
michael@0 | 508 | aFrame->IsSVGText()) { |
michael@0 | 509 | // Unlike containers, leaf frames do not include GetPosition() in |
michael@0 | 510 | // GetCanvasTM(). |
michael@0 | 511 | overflowRect = overflowRect + aFrame->GetPosition(); |
michael@0 | 512 | } |
michael@0 | 513 | int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); |
michael@0 | 514 | gfxMatrix tm = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot); |
michael@0 | 515 | if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { |
michael@0 | 516 | gfx::Matrix childrenOnlyTM; |
michael@0 | 517 | if (static_cast<nsSVGContainerFrame*>(aFrame)-> |
michael@0 | 518 | HasChildrenOnlyTransform(&childrenOnlyTM)) { |
michael@0 | 519 | // Undo the children-only transform: |
michael@0 | 520 | tm = ThebesMatrix(childrenOnlyTM).Invert() * tm; |
michael@0 | 521 | } |
michael@0 | 522 | } |
michael@0 | 523 | nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect, |
michael@0 | 524 | tm, aFrame->PresContext()). |
michael@0 | 525 | ToOutsidePixels(appUnitsPerDevPx); |
michael@0 | 526 | if (!aDirtyRect->Intersects(bounds)) { |
michael@0 | 527 | return; |
michael@0 | 528 | } |
michael@0 | 529 | } |
michael@0 | 530 | |
michael@0 | 531 | /* SVG defines the following rendering model: |
michael@0 | 532 | * |
michael@0 | 533 | * 1. Render fill |
michael@0 | 534 | * 2. Render stroke |
michael@0 | 535 | * 3. Render markers |
michael@0 | 536 | * 4. Apply filter |
michael@0 | 537 | * 5. Apply clipping, masking, group opacity |
michael@0 | 538 | * |
michael@0 | 539 | * We follow this, but perform a couple of optimizations: |
michael@0 | 540 | * |
michael@0 | 541 | * + Use cairo's clipPath when representable natively (single object |
michael@0 | 542 | * clip region). |
michael@0 | 543 | * |
michael@0 | 544 | * + Merge opacity and masking if both used together. |
michael@0 | 545 | */ |
michael@0 | 546 | |
michael@0 | 547 | if (opacity != 1.0f && CanOptimizeOpacity(aFrame)) |
michael@0 | 548 | opacity = 1.0f; |
michael@0 | 549 | |
michael@0 | 550 | gfxContext *gfx = aContext->ThebesContext(); |
michael@0 | 551 | bool complexEffects = false; |
michael@0 | 552 | |
michael@0 | 553 | nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); |
michael@0 | 554 | nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK); |
michael@0 | 555 | |
michael@0 | 556 | bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true; |
michael@0 | 557 | |
michael@0 | 558 | if (!isOK) { |
michael@0 | 559 | // Some resource is invalid. We shouldn't paint anything. |
michael@0 | 560 | return; |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | gfxMatrix matrix; |
michael@0 | 564 | if (clipPathFrame || maskFrame) |
michael@0 | 565 | matrix = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot); |
michael@0 | 566 | |
michael@0 | 567 | /* Check if we need to do additional operations on this child's |
michael@0 | 568 | * rendering, which necessitates rendering into another surface. */ |
michael@0 | 569 | if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip) |
michael@0 | 570 | || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { |
michael@0 | 571 | complexEffects = true; |
michael@0 | 572 | gfx->Save(); |
michael@0 | 573 | if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { |
michael@0 | 574 | // aFrame has a valid visual overflow rect, so clip to it before calling |
michael@0 | 575 | // PushGroup() to minimize the size of the surfaces we'll composite: |
michael@0 | 576 | gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx); |
michael@0 | 577 | gfx->Multiply(GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot)); |
michael@0 | 578 | nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); |
michael@0 | 579 | if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || |
michael@0 | 580 | aFrame->IsSVGText()) { |
michael@0 | 581 | // Unlike containers, leaf frames do not include GetPosition() in |
michael@0 | 582 | // GetCanvasTM(). |
michael@0 | 583 | overflowRect = overflowRect + aFrame->GetPosition(); |
michael@0 | 584 | } |
michael@0 | 585 | aContext->IntersectClip(overflowRect); |
michael@0 | 586 | } |
michael@0 | 587 | gfx->PushGroup(gfxContentType::COLOR_ALPHA); |
michael@0 | 588 | } |
michael@0 | 589 | |
michael@0 | 590 | /* If this frame has only a trivial clipPath, set up cairo's clipping now so |
michael@0 | 591 | * we can just do normal painting and get it clipped appropriately. |
michael@0 | 592 | */ |
michael@0 | 593 | if (clipPathFrame && isTrivialClip) { |
michael@0 | 594 | gfx->Save(); |
michael@0 | 595 | clipPathFrame->ClipPaint(aContext, aFrame, matrix); |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | /* Paint the child */ |
michael@0 | 599 | if (effectProperties.HasValidFilter()) { |
michael@0 | 600 | nsRegion* dirtyRegion = nullptr; |
michael@0 | 601 | nsRegion tmpDirtyRegion; |
michael@0 | 602 | if (aDirtyRect) { |
michael@0 | 603 | // aDirtyRect is in outer-<svg> device pixels, but the filter code needs |
michael@0 | 604 | // it in frame space. |
michael@0 | 605 | gfxMatrix userToDeviceSpace = |
michael@0 | 606 | GetUserToCanvasTM(aFrame, nsISVGChildFrame::FOR_OUTERSVG_TM); |
michael@0 | 607 | if (userToDeviceSpace.IsSingular()) { |
michael@0 | 608 | return; |
michael@0 | 609 | } |
michael@0 | 610 | gfxMatrix deviceToUserSpace = userToDeviceSpace; |
michael@0 | 611 | deviceToUserSpace.Invert(); |
michael@0 | 612 | gfxRect dirtyBounds = deviceToUserSpace.TransformBounds( |
michael@0 | 613 | gfxRect(aDirtyRect->x, aDirtyRect->y, |
michael@0 | 614 | aDirtyRect->width, aDirtyRect->height)); |
michael@0 | 615 | tmpDirtyRegion = |
michael@0 | 616 | nsLayoutUtils::RoundGfxRectToAppRect( |
michael@0 | 617 | dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) - |
michael@0 | 618 | aFrame->GetPosition(); |
michael@0 | 619 | dirtyRegion = &tmpDirtyRegion; |
michael@0 | 620 | } |
michael@0 | 621 | SVGPaintCallback paintCallback; |
michael@0 | 622 | nsFilterInstance::PaintFilteredFrame(aContext, aFrame, &paintCallback, |
michael@0 | 623 | dirtyRegion, aTransformRoot); |
michael@0 | 624 | } else { |
michael@0 | 625 | svgChildFrame->PaintSVG(aContext, aDirtyRect, aTransformRoot); |
michael@0 | 626 | } |
michael@0 | 627 | |
michael@0 | 628 | if (clipPathFrame && isTrivialClip) { |
michael@0 | 629 | gfx->Restore(); |
michael@0 | 630 | } |
michael@0 | 631 | |
michael@0 | 632 | /* No more effects, we're done. */ |
michael@0 | 633 | if (!complexEffects) |
michael@0 | 634 | return; |
michael@0 | 635 | |
michael@0 | 636 | gfx->PopGroupToSource(); |
michael@0 | 637 | |
michael@0 | 638 | nsRefPtr<gfxPattern> maskSurface = |
michael@0 | 639 | maskFrame ? maskFrame->ComputeMaskAlpha(aContext, aFrame, |
michael@0 | 640 | matrix, opacity) : nullptr; |
michael@0 | 641 | |
michael@0 | 642 | nsRefPtr<gfxPattern> clipMaskSurface; |
michael@0 | 643 | if (clipPathFrame && !isTrivialClip) { |
michael@0 | 644 | gfx->PushGroup(gfxContentType::COLOR_ALPHA); |
michael@0 | 645 | |
michael@0 | 646 | nsresult rv = clipPathFrame->ClipPaint(aContext, aFrame, matrix); |
michael@0 | 647 | clipMaskSurface = gfx->PopGroup(); |
michael@0 | 648 | |
michael@0 | 649 | if (NS_SUCCEEDED(rv) && clipMaskSurface) { |
michael@0 | 650 | // Still more set after clipping, so clip to another surface |
michael@0 | 651 | if (maskSurface || opacity != 1.0f) { |
michael@0 | 652 | gfx->PushGroup(gfxContentType::COLOR_ALPHA); |
michael@0 | 653 | gfx->Mask(clipMaskSurface); |
michael@0 | 654 | gfx->PopGroupToSource(); |
michael@0 | 655 | } else { |
michael@0 | 656 | gfx->Mask(clipMaskSurface); |
michael@0 | 657 | } |
michael@0 | 658 | } |
michael@0 | 659 | } |
michael@0 | 660 | |
michael@0 | 661 | if (maskSurface) { |
michael@0 | 662 | gfx->Mask(maskSurface); |
michael@0 | 663 | } else if (opacity != 1.0f) { |
michael@0 | 664 | gfx->Paint(opacity); |
michael@0 | 665 | } |
michael@0 | 666 | |
michael@0 | 667 | gfx->Restore(); |
michael@0 | 668 | } |
michael@0 | 669 | |
michael@0 | 670 | bool |
michael@0 | 671 | nsSVGUtils::HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint) |
michael@0 | 672 | { |
michael@0 | 673 | nsSVGEffects::EffectProperties props = |
michael@0 | 674 | nsSVGEffects::GetEffectProperties(aFrame); |
michael@0 | 675 | if (!props.mClipPath) |
michael@0 | 676 | return true; |
michael@0 | 677 | |
michael@0 | 678 | bool isOK = true; |
michael@0 | 679 | nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK); |
michael@0 | 680 | if (!isOK) { |
michael@0 | 681 | // clipPath is not a valid resource, so nothing gets painted, so |
michael@0 | 682 | // hit-testing must fail. |
michael@0 | 683 | return false; |
michael@0 | 684 | } |
michael@0 | 685 | if (!clipPathFrame) { |
michael@0 | 686 | // clipPath doesn't exist, ignore it. |
michael@0 | 687 | return true; |
michael@0 | 688 | } |
michael@0 | 689 | |
michael@0 | 690 | return clipPathFrame->ClipHitTest(aFrame, GetCanvasTM(aFrame, |
michael@0 | 691 | nsISVGChildFrame::FOR_HIT_TESTING), aPoint); |
michael@0 | 692 | } |
michael@0 | 693 | |
michael@0 | 694 | nsIFrame * |
michael@0 | 695 | nsSVGUtils::HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint) |
michael@0 | 696 | { |
michael@0 | 697 | // Traverse the list in reverse order, so that if we get a hit we know that's |
michael@0 | 698 | // the topmost frame that intersects the point; then we can just return it. |
michael@0 | 699 | nsIFrame* result = nullptr; |
michael@0 | 700 | for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); |
michael@0 | 701 | current; |
michael@0 | 702 | current = current->GetPrevSibling()) { |
michael@0 | 703 | nsISVGChildFrame* SVGFrame = do_QueryFrame(current); |
michael@0 | 704 | if (SVGFrame) { |
michael@0 | 705 | const nsIContent* content = current->GetContent(); |
michael@0 | 706 | if (content->IsSVG() && |
michael@0 | 707 | !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { |
michael@0 | 708 | continue; |
michael@0 | 709 | } |
michael@0 | 710 | result = SVGFrame->GetFrameForPoint(aPoint); |
michael@0 | 711 | if (result) |
michael@0 | 712 | break; |
michael@0 | 713 | } |
michael@0 | 714 | } |
michael@0 | 715 | |
michael@0 | 716 | if (result && !HitTestClip(aFrame, aPoint)) |
michael@0 | 717 | result = nullptr; |
michael@0 | 718 | |
michael@0 | 719 | return result; |
michael@0 | 720 | } |
michael@0 | 721 | |
michael@0 | 722 | nsRect |
michael@0 | 723 | nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames) |
michael@0 | 724 | { |
michael@0 | 725 | nsRect rect; |
michael@0 | 726 | |
michael@0 | 727 | for (nsIFrame* kid = aFrames.FirstChild(); |
michael@0 | 728 | kid; |
michael@0 | 729 | kid = kid->GetNextSibling()) { |
michael@0 | 730 | nsISVGChildFrame* child = do_QueryFrame(kid); |
michael@0 | 731 | if (child) { |
michael@0 | 732 | nsRect childRect = child->GetCoveredRegion(); |
michael@0 | 733 | rect.UnionRect(rect, childRect); |
michael@0 | 734 | } |
michael@0 | 735 | } |
michael@0 | 736 | |
michael@0 | 737 | return rect; |
michael@0 | 738 | } |
michael@0 | 739 | |
michael@0 | 740 | nsPoint |
michael@0 | 741 | nsSVGUtils::TransformOuterSVGPointToChildFrame(nsPoint aPoint, |
michael@0 | 742 | const gfxMatrix& aFrameToCanvasTM, |
michael@0 | 743 | nsPresContext* aPresContext) |
michael@0 | 744 | { |
michael@0 | 745 | NS_ABORT_IF_FALSE(!aFrameToCanvasTM.IsSingular(), |
michael@0 | 746 | "Callers must not pass a singular matrix"); |
michael@0 | 747 | gfxMatrix canvasDevToFrameUserSpace = aFrameToCanvasTM; |
michael@0 | 748 | canvasDevToFrameUserSpace.Invert(); |
michael@0 | 749 | gfxPoint devPt = gfxPoint(aPoint.x, aPoint.y) / |
michael@0 | 750 | aPresContext->AppUnitsPerDevPixel(); |
michael@0 | 751 | gfxPoint userPt = canvasDevToFrameUserSpace.Transform(devPt); |
michael@0 | 752 | gfxPoint appPt = (userPt * aPresContext->AppUnitsPerCSSPixel()).Round(); |
michael@0 | 753 | userPt.x = clamped(appPt.x, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX)); |
michael@0 | 754 | userPt.y = clamped(appPt.y, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX)); |
michael@0 | 755 | // now guaranteed to be safe: |
michael@0 | 756 | return nsPoint(nscoord(userPt.x), nscoord(userPt.y)); |
michael@0 | 757 | } |
michael@0 | 758 | |
michael@0 | 759 | nsRect |
michael@0 | 760 | nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect, |
michael@0 | 761 | const gfxMatrix& aMatrix, |
michael@0 | 762 | nsPresContext* aPresContext) |
michael@0 | 763 | { |
michael@0 | 764 | gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); |
michael@0 | 765 | r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel()); |
michael@0 | 766 | return nsLayoutUtils::RoundGfxRectToAppRect( |
michael@0 | 767 | aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel()); |
michael@0 | 768 | } |
michael@0 | 769 | |
michael@0 | 770 | gfxIntSize |
michael@0 | 771 | nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, |
michael@0 | 772 | bool *aResultOverflows) |
michael@0 | 773 | { |
michael@0 | 774 | gfxIntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height))); |
michael@0 | 775 | |
michael@0 | 776 | *aResultOverflows = surfaceSize.width != ceil(aSize.width) || |
michael@0 | 777 | surfaceSize.height != ceil(aSize.height); |
michael@0 | 778 | |
michael@0 | 779 | if (!gfxASurface::CheckSurfaceSize(surfaceSize)) { |
michael@0 | 780 | surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, |
michael@0 | 781 | surfaceSize.width); |
michael@0 | 782 | surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, |
michael@0 | 783 | surfaceSize.height); |
michael@0 | 784 | *aResultOverflows = true; |
michael@0 | 785 | } |
michael@0 | 786 | |
michael@0 | 787 | return surfaceSize; |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | bool |
michael@0 | 791 | nsSVGUtils::HitTestRect(const gfx::Matrix &aMatrix, |
michael@0 | 792 | float aRX, float aRY, float aRWidth, float aRHeight, |
michael@0 | 793 | float aX, float aY) |
michael@0 | 794 | { |
michael@0 | 795 | gfx::Rect rect(aRX, aRY, aRWidth, aRHeight); |
michael@0 | 796 | if (rect.IsEmpty() || aMatrix.IsSingular()) { |
michael@0 | 797 | return false; |
michael@0 | 798 | } |
michael@0 | 799 | gfx::Matrix toRectSpace = aMatrix; |
michael@0 | 800 | toRectSpace.Invert(); |
michael@0 | 801 | gfx::Point p = toRectSpace * gfx::Point(aX, aY); |
michael@0 | 802 | return rect.x <= p.x && p.x <= rect.XMost() && |
michael@0 | 803 | rect.y <= p.y && p.y <= rect.YMost(); |
michael@0 | 804 | } |
michael@0 | 805 | |
michael@0 | 806 | gfxRect |
michael@0 | 807 | nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame, |
michael@0 | 808 | float aX, float aY, float aWidth, float aHeight) |
michael@0 | 809 | { |
michael@0 | 810 | const nsStyleDisplay* disp = aFrame->StyleDisplay(); |
michael@0 | 811 | |
michael@0 | 812 | if (!(disp->mClipFlags & NS_STYLE_CLIP_RECT)) { |
michael@0 | 813 | NS_ASSERTION(disp->mClipFlags == NS_STYLE_CLIP_AUTO, |
michael@0 | 814 | "We don't know about this type of clip."); |
michael@0 | 815 | return gfxRect(aX, aY, aWidth, aHeight); |
michael@0 | 816 | } |
michael@0 | 817 | |
michael@0 | 818 | if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN || |
michael@0 | 819 | disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) { |
michael@0 | 820 | |
michael@0 | 821 | nsIntRect clipPxRect = |
michael@0 | 822 | disp->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel()); |
michael@0 | 823 | gfxRect clipRect = |
michael@0 | 824 | gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); |
michael@0 | 825 | |
michael@0 | 826 | if (NS_STYLE_CLIP_RIGHT_AUTO & disp->mClipFlags) { |
michael@0 | 827 | clipRect.width = aWidth - clipRect.X(); |
michael@0 | 828 | } |
michael@0 | 829 | if (NS_STYLE_CLIP_BOTTOM_AUTO & disp->mClipFlags) { |
michael@0 | 830 | clipRect.height = aHeight - clipRect.Y(); |
michael@0 | 831 | } |
michael@0 | 832 | |
michael@0 | 833 | if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) { |
michael@0 | 834 | clipRect.x = aX; |
michael@0 | 835 | clipRect.width = aWidth; |
michael@0 | 836 | } |
michael@0 | 837 | if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) { |
michael@0 | 838 | clipRect.y = aY; |
michael@0 | 839 | clipRect.height = aHeight; |
michael@0 | 840 | } |
michael@0 | 841 | |
michael@0 | 842 | return clipRect; |
michael@0 | 843 | } |
michael@0 | 844 | return gfxRect(aX, aY, aWidth, aHeight); |
michael@0 | 845 | } |
michael@0 | 846 | |
michael@0 | 847 | void |
michael@0 | 848 | nsSVGUtils::SetClipRect(gfxContext *aContext, |
michael@0 | 849 | const gfxMatrix &aCTM, |
michael@0 | 850 | const gfxRect &aRect) |
michael@0 | 851 | { |
michael@0 | 852 | if (aCTM.IsSingular()) |
michael@0 | 853 | return; |
michael@0 | 854 | |
michael@0 | 855 | gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext); |
michael@0 | 856 | aContext->Multiply(aCTM); |
michael@0 | 857 | aContext->Clip(aRect); |
michael@0 | 858 | } |
michael@0 | 859 | |
michael@0 | 860 | gfxRect |
michael@0 | 861 | nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags) |
michael@0 | 862 | { |
michael@0 | 863 | if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 864 | aFrame = aFrame->GetParent(); |
michael@0 | 865 | } |
michael@0 | 866 | gfxRect bbox; |
michael@0 | 867 | nsISVGChildFrame *svg = do_QueryFrame(aFrame); |
michael@0 | 868 | if (svg || aFrame->IsSVGText()) { |
michael@0 | 869 | // It is possible to apply a gradient, pattern, clipping path, mask or |
michael@0 | 870 | // filter to text. When one of these facilities is applied to text |
michael@0 | 871 | // the bounding box is the entire text element in all |
michael@0 | 872 | // cases. |
michael@0 | 873 | if (aFrame->IsSVGText()) { |
michael@0 | 874 | nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame); |
michael@0 | 875 | if (ancestor && ancestor->IsSVGText()) { |
michael@0 | 876 | while (ancestor->GetType() != nsGkAtoms::svgTextFrame) { |
michael@0 | 877 | ancestor = ancestor->GetParent(); |
michael@0 | 878 | } |
michael@0 | 879 | } |
michael@0 | 880 | svg = do_QueryFrame(ancestor); |
michael@0 | 881 | } |
michael@0 | 882 | nsIContent* content = aFrame->GetContent(); |
michael@0 | 883 | if (content->IsSVG() && |
michael@0 | 884 | !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { |
michael@0 | 885 | return bbox; |
michael@0 | 886 | } |
michael@0 | 887 | gfxMatrix matrix; |
michael@0 | 888 | if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { |
michael@0 | 889 | // The spec says getBBox "Returns the tight bounding box in *current user |
michael@0 | 890 | // space*". So we should really be doing this for all elements, but that |
michael@0 | 891 | // needs investigation to check that we won't break too much content. |
michael@0 | 892 | // NOTE: When changing this to apply to other frame types, make sure to |
michael@0 | 893 | // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. |
michael@0 | 894 | NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast"); |
michael@0 | 895 | nsSVGElement *element = static_cast<nsSVGElement*>(content); |
michael@0 | 896 | matrix = element->PrependLocalTransformsTo(matrix, |
michael@0 | 897 | nsSVGElement::eChildToUserSpace); |
michael@0 | 898 | } |
michael@0 | 899 | return svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); |
michael@0 | 900 | } |
michael@0 | 901 | return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame); |
michael@0 | 902 | } |
michael@0 | 903 | |
michael@0 | 904 | gfxPoint |
michael@0 | 905 | nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame) |
michael@0 | 906 | { |
michael@0 | 907 | if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { |
michael@0 | 908 | // The user space for non-SVG frames is defined as the bounding box of the |
michael@0 | 909 | // frame's border-box rects over all continuations. |
michael@0 | 910 | return gfxPoint(); |
michael@0 | 911 | } |
michael@0 | 912 | |
michael@0 | 913 | // Leaf frames apply their own offset inside their user space. |
michael@0 | 914 | if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || |
michael@0 | 915 | aFrame->IsSVGText()) { |
michael@0 | 916 | return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), |
michael@0 | 917 | nsPresContext::AppUnitsPerCSSPixel()).TopLeft(); |
michael@0 | 918 | } |
michael@0 | 919 | |
michael@0 | 920 | // For foreignObject frames, nsSVGUtils::GetBBox applies their local |
michael@0 | 921 | // transform, so we need to do the same here. |
michael@0 | 922 | if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { |
michael@0 | 923 | gfxMatrix transform = static_cast<nsSVGElement*>(aFrame->GetContent())-> |
michael@0 | 924 | PrependLocalTransformsTo(gfxMatrix(), |
michael@0 | 925 | nsSVGElement::eChildToUserSpace); |
michael@0 | 926 | NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); |
michael@0 | 927 | return transform.GetTranslation(); |
michael@0 | 928 | } |
michael@0 | 929 | |
michael@0 | 930 | return gfxPoint(); |
michael@0 | 931 | } |
michael@0 | 932 | |
michael@0 | 933 | gfxRect |
michael@0 | 934 | nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, |
michael@0 | 935 | const gfxRect &aBBox, nsIFrame *aFrame) |
michael@0 | 936 | { |
michael@0 | 937 | float x, y, width, height; |
michael@0 | 938 | if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
michael@0 | 939 | x = aBBox.X() + ObjectSpace(aBBox, &aXYWH[0]); |
michael@0 | 940 | y = aBBox.Y() + ObjectSpace(aBBox, &aXYWH[1]); |
michael@0 | 941 | width = ObjectSpace(aBBox, &aXYWH[2]); |
michael@0 | 942 | height = ObjectSpace(aBBox, &aXYWH[3]); |
michael@0 | 943 | } else { |
michael@0 | 944 | x = UserSpace(aFrame, &aXYWH[0]); |
michael@0 | 945 | y = UserSpace(aFrame, &aXYWH[1]); |
michael@0 | 946 | width = UserSpace(aFrame, &aXYWH[2]); |
michael@0 | 947 | height = UserSpace(aFrame, &aXYWH[3]); |
michael@0 | 948 | } |
michael@0 | 949 | return gfxRect(x, y, width, height); |
michael@0 | 950 | } |
michael@0 | 951 | |
michael@0 | 952 | bool |
michael@0 | 953 | nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame) |
michael@0 | 954 | { |
michael@0 | 955 | if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { |
michael@0 | 956 | return false; |
michael@0 | 957 | } |
michael@0 | 958 | nsIAtom *type = aFrame->GetType(); |
michael@0 | 959 | if (type != nsGkAtoms::svgImageFrame && |
michael@0 | 960 | type != nsGkAtoms::svgPathGeometryFrame) { |
michael@0 | 961 | return false; |
michael@0 | 962 | } |
michael@0 | 963 | if (aFrame->StyleSVGReset()->HasFilters()) { |
michael@0 | 964 | return false; |
michael@0 | 965 | } |
michael@0 | 966 | // XXX The SVG WG is intending to allow fill, stroke and markers on <image> |
michael@0 | 967 | if (type == nsGkAtoms::svgImageFrame) { |
michael@0 | 968 | return true; |
michael@0 | 969 | } |
michael@0 | 970 | const nsStyleSVG *style = aFrame->StyleSVG(); |
michael@0 | 971 | if (style->HasMarker()) { |
michael@0 | 972 | return false; |
michael@0 | 973 | } |
michael@0 | 974 | if (!style->HasFill() || !HasStroke(aFrame)) { |
michael@0 | 975 | return true; |
michael@0 | 976 | } |
michael@0 | 977 | return false; |
michael@0 | 978 | } |
michael@0 | 979 | |
michael@0 | 980 | gfxMatrix |
michael@0 | 981 | nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix, |
michael@0 | 982 | nsSVGEnum *aUnits, |
michael@0 | 983 | nsIFrame *aFrame) |
michael@0 | 984 | { |
michael@0 | 985 | if (aFrame && |
michael@0 | 986 | aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
michael@0 | 987 | gfxRect bbox = GetBBox(aFrame); |
michael@0 | 988 | return gfxMatrix().Scale(bbox.Width(), bbox.Height()) * |
michael@0 | 989 | gfxMatrix().Translate(gfxPoint(bbox.X(), bbox.Y())) * |
michael@0 | 990 | aMatrix; |
michael@0 | 991 | } |
michael@0 | 992 | return aMatrix; |
michael@0 | 993 | } |
michael@0 | 994 | |
michael@0 | 995 | nsIFrame* |
michael@0 | 996 | nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) |
michael@0 | 997 | { |
michael@0 | 998 | for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame; |
michael@0 | 999 | ancestorFrame = ancestorFrame->GetParent()) { |
michael@0 | 1000 | if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) { |
michael@0 | 1001 | return ancestorFrame; |
michael@0 | 1002 | } |
michael@0 | 1003 | } |
michael@0 | 1004 | return nullptr; |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | gfxMatrix |
michael@0 | 1008 | nsSVGUtils::GetStrokeTransform(nsIFrame *aFrame) |
michael@0 | 1009 | { |
michael@0 | 1010 | if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 1011 | aFrame = aFrame->GetParent(); |
michael@0 | 1012 | } |
michael@0 | 1013 | |
michael@0 | 1014 | if (aFrame->StyleSVGReset()->mVectorEffect == |
michael@0 | 1015 | NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) { |
michael@0 | 1016 | |
michael@0 | 1017 | nsIContent *content = aFrame->GetContent(); |
michael@0 | 1018 | NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast"); |
michael@0 | 1019 | |
michael@0 | 1020 | // a non-scaling stroke is in the screen co-ordinate |
michael@0 | 1021 | // space rather so we need to invert the transform |
michael@0 | 1022 | // to the screen co-ordinate space to get there. |
michael@0 | 1023 | // See http://www.w3.org/TR/SVGTiny12/painting.html#NonScalingStroke |
michael@0 | 1024 | gfx::Matrix transform = SVGContentUtils::GetCTM( |
michael@0 | 1025 | static_cast<nsSVGElement*>(content), true); |
michael@0 | 1026 | if (!transform.IsSingular()) { |
michael@0 | 1027 | transform.Invert(); |
michael@0 | 1028 | return ThebesMatrix(transform); |
michael@0 | 1029 | } |
michael@0 | 1030 | } |
michael@0 | 1031 | return gfxMatrix(); |
michael@0 | 1032 | } |
michael@0 | 1033 | |
michael@0 | 1034 | // The logic here comes from _cairo_stroke_style_max_distance_from_path |
michael@0 | 1035 | static gfxRect |
michael@0 | 1036 | PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, |
michael@0 | 1037 | nsIFrame* aFrame, |
michael@0 | 1038 | double aStyleExpansionFactor, |
michael@0 | 1039 | const gfxMatrix& aMatrix) |
michael@0 | 1040 | { |
michael@0 | 1041 | double style_expansion = |
michael@0 | 1042 | aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame); |
michael@0 | 1043 | |
michael@0 | 1044 | gfxMatrix matrix = aMatrix; |
michael@0 | 1045 | matrix.Multiply(nsSVGUtils::GetStrokeTransform(aFrame)); |
michael@0 | 1046 | |
michael@0 | 1047 | double dx = style_expansion * (fabs(matrix.xx) + fabs(matrix.xy)); |
michael@0 | 1048 | double dy = style_expansion * (fabs(matrix.yy) + fabs(matrix.yx)); |
michael@0 | 1049 | |
michael@0 | 1050 | gfxRect strokeExtents = aPathExtents; |
michael@0 | 1051 | strokeExtents.Inflate(dx, dy); |
michael@0 | 1052 | return strokeExtents; |
michael@0 | 1053 | } |
michael@0 | 1054 | |
michael@0 | 1055 | /*static*/ gfxRect |
michael@0 | 1056 | nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, |
michael@0 | 1057 | nsTextFrame* aFrame, |
michael@0 | 1058 | const gfxMatrix& aMatrix) |
michael@0 | 1059 | { |
michael@0 | 1060 | NS_ASSERTION(aFrame->IsSVGText(), "expected an nsTextFrame for SVG text"); |
michael@0 | 1061 | return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); |
michael@0 | 1062 | } |
michael@0 | 1063 | |
michael@0 | 1064 | /*static*/ gfxRect |
michael@0 | 1065 | nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, |
michael@0 | 1066 | nsSVGPathGeometryFrame* aFrame, |
michael@0 | 1067 | const gfxMatrix& aMatrix) |
michael@0 | 1068 | { |
michael@0 | 1069 | double styleExpansionFactor = 0.5; |
michael@0 | 1070 | |
michael@0 | 1071 | if (static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable()) { |
michael@0 | 1072 | const nsStyleSVG* style = aFrame->StyleSVG(); |
michael@0 | 1073 | |
michael@0 | 1074 | if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) { |
michael@0 | 1075 | styleExpansionFactor = M_SQRT1_2; |
michael@0 | 1076 | } |
michael@0 | 1077 | |
michael@0 | 1078 | if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER && |
michael@0 | 1079 | styleExpansionFactor < style->mStrokeMiterlimit && |
michael@0 | 1080 | aFrame->GetContent()->Tag() != nsGkAtoms::line) { |
michael@0 | 1081 | styleExpansionFactor = style->mStrokeMiterlimit; |
michael@0 | 1082 | } |
michael@0 | 1083 | } |
michael@0 | 1084 | |
michael@0 | 1085 | return ::PathExtentsToMaxStrokeExtents(aPathExtents, |
michael@0 | 1086 | aFrame, |
michael@0 | 1087 | styleExpansionFactor, |
michael@0 | 1088 | aMatrix); |
michael@0 | 1089 | } |
michael@0 | 1090 | |
michael@0 | 1091 | // ---------------------------------------------------------------------- |
michael@0 | 1092 | |
michael@0 | 1093 | /* static */ nscolor |
michael@0 | 1094 | nsSVGUtils::GetFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext, |
michael@0 | 1095 | nsStyleSVGPaint nsStyleSVG::*aFillOrStroke) |
michael@0 | 1096 | { |
michael@0 | 1097 | const nsStyleSVGPaint &paint = aStyleContext->StyleSVG()->*aFillOrStroke; |
michael@0 | 1098 | nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited(); |
michael@0 | 1099 | bool isServer = paint.mType == eStyleSVGPaintType_Server || |
michael@0 | 1100 | paint.mType == eStyleSVGPaintType_ContextFill || |
michael@0 | 1101 | paint.mType == eStyleSVGPaintType_ContextStroke; |
michael@0 | 1102 | nscolor color = isServer ? paint.mFallbackColor : paint.mPaint.mColor; |
michael@0 | 1103 | if (styleIfVisited) { |
michael@0 | 1104 | const nsStyleSVGPaint &paintIfVisited = |
michael@0 | 1105 | styleIfVisited->StyleSVG()->*aFillOrStroke; |
michael@0 | 1106 | // To prevent Web content from detecting if a user has visited a URL |
michael@0 | 1107 | // (via URL loading triggered by paint servers or performance |
michael@0 | 1108 | // differences between paint servers or between a paint server and a |
michael@0 | 1109 | // color), we do not allow whether links are visited to change which |
michael@0 | 1110 | // paint server is used or switch between paint servers and simple |
michael@0 | 1111 | // colors. A :visited style may only override a simple color with |
michael@0 | 1112 | // another simple color. |
michael@0 | 1113 | if (paintIfVisited.mType == eStyleSVGPaintType_Color && |
michael@0 | 1114 | paint.mType == eStyleSVGPaintType_Color) { |
michael@0 | 1115 | nscolor colors[2] = { color, paintIfVisited.mPaint.mColor }; |
michael@0 | 1116 | return nsStyleContext::CombineVisitedColors( |
michael@0 | 1117 | colors, aStyleContext->RelevantLinkVisited()); |
michael@0 | 1118 | } |
michael@0 | 1119 | } |
michael@0 | 1120 | return color; |
michael@0 | 1121 | } |
michael@0 | 1122 | |
michael@0 | 1123 | static void |
michael@0 | 1124 | SetupFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext, |
michael@0 | 1125 | nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, |
michael@0 | 1126 | float aOpacity) |
michael@0 | 1127 | { |
michael@0 | 1128 | nscolor color = nsSVGUtils::GetFallbackOrPaintColor( |
michael@0 | 1129 | aContext, aStyleContext, aFillOrStroke); |
michael@0 | 1130 | |
michael@0 | 1131 | aContext->SetColor(gfxRGBA(NS_GET_R(color)/255.0, |
michael@0 | 1132 | NS_GET_G(color)/255.0, |
michael@0 | 1133 | NS_GET_B(color)/255.0, |
michael@0 | 1134 | NS_GET_A(color)/255.0 * aOpacity)); |
michael@0 | 1135 | } |
michael@0 | 1136 | |
michael@0 | 1137 | static float |
michael@0 | 1138 | MaybeOptimizeOpacity(nsIFrame *aFrame, float aFillOrStrokeOpacity) |
michael@0 | 1139 | { |
michael@0 | 1140 | float opacity = aFrame->StyleDisplay()->mOpacity; |
michael@0 | 1141 | if (opacity < 1 && nsSVGUtils::CanOptimizeOpacity(aFrame)) { |
michael@0 | 1142 | return aFillOrStrokeOpacity * opacity; |
michael@0 | 1143 | } |
michael@0 | 1144 | return aFillOrStrokeOpacity; |
michael@0 | 1145 | } |
michael@0 | 1146 | |
michael@0 | 1147 | /* static */ bool |
michael@0 | 1148 | nsSVGUtils::SetupContextPaint(gfxContext *aContext, |
michael@0 | 1149 | gfxTextContextPaint *aContextPaint, |
michael@0 | 1150 | const nsStyleSVGPaint &aPaint, |
michael@0 | 1151 | float aOpacity) |
michael@0 | 1152 | { |
michael@0 | 1153 | nsRefPtr<gfxPattern> pattern; |
michael@0 | 1154 | |
michael@0 | 1155 | if (!aContextPaint) { |
michael@0 | 1156 | return false; |
michael@0 | 1157 | } |
michael@0 | 1158 | |
michael@0 | 1159 | switch (aPaint.mType) { |
michael@0 | 1160 | case eStyleSVGPaintType_ContextFill: |
michael@0 | 1161 | pattern = aContextPaint->GetFillPattern(aOpacity, aContext->CurrentMatrix()); |
michael@0 | 1162 | break; |
michael@0 | 1163 | case eStyleSVGPaintType_ContextStroke: |
michael@0 | 1164 | pattern = aContextPaint->GetStrokePattern(aOpacity, aContext->CurrentMatrix()); |
michael@0 | 1165 | break; |
michael@0 | 1166 | default: |
michael@0 | 1167 | return false; |
michael@0 | 1168 | } |
michael@0 | 1169 | |
michael@0 | 1170 | if (!pattern) { |
michael@0 | 1171 | return false; |
michael@0 | 1172 | } |
michael@0 | 1173 | |
michael@0 | 1174 | aContext->SetPattern(pattern); |
michael@0 | 1175 | |
michael@0 | 1176 | return true; |
michael@0 | 1177 | } |
michael@0 | 1178 | |
michael@0 | 1179 | bool |
michael@0 | 1180 | nsSVGUtils::SetupCairoFillPaint(nsIFrame *aFrame, gfxContext* aContext, |
michael@0 | 1181 | gfxTextContextPaint *aContextPaint) |
michael@0 | 1182 | { |
michael@0 | 1183 | const nsStyleSVG* style = aFrame->StyleSVG(); |
michael@0 | 1184 | if (style->mFill.mType == eStyleSVGPaintType_None) |
michael@0 | 1185 | return false; |
michael@0 | 1186 | |
michael@0 | 1187 | if (style->mFillRule == NS_STYLE_FILL_RULE_EVENODD) |
michael@0 | 1188 | aContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); |
michael@0 | 1189 | else |
michael@0 | 1190 | aContext->SetFillRule(gfxContext::FILL_RULE_WINDING); |
michael@0 | 1191 | |
michael@0 | 1192 | float opacity = MaybeOptimizeOpacity(aFrame, |
michael@0 | 1193 | GetOpacity(style->mFillOpacitySource, |
michael@0 | 1194 | style->mFillOpacity, |
michael@0 | 1195 | aContextPaint)); |
michael@0 | 1196 | nsSVGPaintServerFrame *ps = |
michael@0 | 1197 | nsSVGEffects::GetPaintServer(aFrame, &style->mFill, nsSVGEffects::FillProperty()); |
michael@0 | 1198 | if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mFill, opacity)) |
michael@0 | 1199 | return true; |
michael@0 | 1200 | |
michael@0 | 1201 | if (SetupContextPaint(aContext, aContextPaint, style->mFill, opacity)) { |
michael@0 | 1202 | return true; |
michael@0 | 1203 | } |
michael@0 | 1204 | |
michael@0 | 1205 | // On failure, use the fallback colour in case we have an |
michael@0 | 1206 | // objectBoundingBox where the width or height of the object is zero. |
michael@0 | 1207 | // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox |
michael@0 | 1208 | SetupFallbackOrPaintColor(aContext, aFrame->StyleContext(), |
michael@0 | 1209 | &nsStyleSVG::mFill, opacity); |
michael@0 | 1210 | |
michael@0 | 1211 | return true; |
michael@0 | 1212 | } |
michael@0 | 1213 | |
michael@0 | 1214 | bool |
michael@0 | 1215 | nsSVGUtils::SetupCairoStrokePaint(nsIFrame *aFrame, gfxContext* aContext, |
michael@0 | 1216 | gfxTextContextPaint *aContextPaint) |
michael@0 | 1217 | { |
michael@0 | 1218 | const nsStyleSVG* style = aFrame->StyleSVG(); |
michael@0 | 1219 | if (style->mStroke.mType == eStyleSVGPaintType_None) |
michael@0 | 1220 | return false; |
michael@0 | 1221 | |
michael@0 | 1222 | float opacity = MaybeOptimizeOpacity(aFrame, |
michael@0 | 1223 | GetOpacity(style->mStrokeOpacitySource, |
michael@0 | 1224 | style->mStrokeOpacity, |
michael@0 | 1225 | aContextPaint)); |
michael@0 | 1226 | |
michael@0 | 1227 | nsSVGPaintServerFrame *ps = |
michael@0 | 1228 | nsSVGEffects::GetPaintServer(aFrame, &style->mStroke, nsSVGEffects::StrokeProperty()); |
michael@0 | 1229 | if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mStroke, opacity)) |
michael@0 | 1230 | return true; |
michael@0 | 1231 | |
michael@0 | 1232 | if (SetupContextPaint(aContext, aContextPaint, style->mStroke, opacity)) { |
michael@0 | 1233 | return true; |
michael@0 | 1234 | } |
michael@0 | 1235 | |
michael@0 | 1236 | // On failure, use the fallback colour in case we have an |
michael@0 | 1237 | // objectBoundingBox where the width or height of the object is zero. |
michael@0 | 1238 | // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox |
michael@0 | 1239 | SetupFallbackOrPaintColor(aContext, aFrame->StyleContext(), |
michael@0 | 1240 | &nsStyleSVG::mStroke, opacity); |
michael@0 | 1241 | |
michael@0 | 1242 | return true; |
michael@0 | 1243 | } |
michael@0 | 1244 | |
michael@0 | 1245 | /* static */ float |
michael@0 | 1246 | nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType, |
michael@0 | 1247 | const float& aOpacity, |
michael@0 | 1248 | gfxTextContextPaint *aOuterContextPaint) |
michael@0 | 1249 | { |
michael@0 | 1250 | float opacity = 1.0f; |
michael@0 | 1251 | switch (aOpacityType) { |
michael@0 | 1252 | case eStyleSVGOpacitySource_Normal: |
michael@0 | 1253 | opacity = aOpacity; |
michael@0 | 1254 | break; |
michael@0 | 1255 | case eStyleSVGOpacitySource_ContextFillOpacity: |
michael@0 | 1256 | if (aOuterContextPaint) { |
michael@0 | 1257 | opacity = aOuterContextPaint->GetFillOpacity(); |
michael@0 | 1258 | } else { |
michael@0 | 1259 | NS_WARNING("context-fill-opacity used outside of an SVG glyph"); |
michael@0 | 1260 | } |
michael@0 | 1261 | break; |
michael@0 | 1262 | case eStyleSVGOpacitySource_ContextStrokeOpacity: |
michael@0 | 1263 | if (aOuterContextPaint) { |
michael@0 | 1264 | opacity = aOuterContextPaint->GetStrokeOpacity(); |
michael@0 | 1265 | } else { |
michael@0 | 1266 | NS_WARNING("context-stroke-opacity used outside of an SVG glyph"); |
michael@0 | 1267 | } |
michael@0 | 1268 | break; |
michael@0 | 1269 | default: |
michael@0 | 1270 | NS_NOTREACHED("Unknown object opacity inheritance type for SVG glyph"); |
michael@0 | 1271 | } |
michael@0 | 1272 | return opacity; |
michael@0 | 1273 | } |
michael@0 | 1274 | |
michael@0 | 1275 | bool |
michael@0 | 1276 | nsSVGUtils::HasStroke(nsIFrame* aFrame, gfxTextContextPaint *aContextPaint) |
michael@0 | 1277 | { |
michael@0 | 1278 | const nsStyleSVG *style = aFrame->StyleSVG(); |
michael@0 | 1279 | return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; |
michael@0 | 1280 | } |
michael@0 | 1281 | |
michael@0 | 1282 | float |
michael@0 | 1283 | nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, gfxTextContextPaint *aContextPaint) |
michael@0 | 1284 | { |
michael@0 | 1285 | const nsStyleSVG *style = aFrame->StyleSVG(); |
michael@0 | 1286 | if (aContextPaint && style->mStrokeWidthFromObject) { |
michael@0 | 1287 | return aContextPaint->GetStrokeWidth(); |
michael@0 | 1288 | } |
michael@0 | 1289 | |
michael@0 | 1290 | nsIContent* content = aFrame->GetContent(); |
michael@0 | 1291 | if (content->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 1292 | content = content->GetParent(); |
michael@0 | 1293 | } |
michael@0 | 1294 | |
michael@0 | 1295 | nsSVGElement *ctx = static_cast<nsSVGElement*>(content); |
michael@0 | 1296 | |
michael@0 | 1297 | return SVGContentUtils::CoordToFloat(aFrame->PresContext(), ctx, |
michael@0 | 1298 | style->mStrokeWidth); |
michael@0 | 1299 | } |
michael@0 | 1300 | |
michael@0 | 1301 | void |
michael@0 | 1302 | nsSVGUtils::SetupCairoStrokeBBoxGeometry(nsIFrame* aFrame, |
michael@0 | 1303 | gfxContext *aContext, |
michael@0 | 1304 | gfxTextContextPaint *aContextPaint) |
michael@0 | 1305 | { |
michael@0 | 1306 | float width = GetStrokeWidth(aFrame, aContextPaint); |
michael@0 | 1307 | if (width <= 0) |
michael@0 | 1308 | return; |
michael@0 | 1309 | aContext->SetLineWidth(width); |
michael@0 | 1310 | |
michael@0 | 1311 | // Apply any stroke-specific transform |
michael@0 | 1312 | gfxMatrix strokeTransform = GetStrokeTransform(aFrame); |
michael@0 | 1313 | if (!strokeTransform.IsIdentity()) { |
michael@0 | 1314 | aContext->Multiply(strokeTransform); |
michael@0 | 1315 | } |
michael@0 | 1316 | |
michael@0 | 1317 | const nsStyleSVG* style = aFrame->StyleSVG(); |
michael@0 | 1318 | |
michael@0 | 1319 | switch (style->mStrokeLinecap) { |
michael@0 | 1320 | case NS_STYLE_STROKE_LINECAP_BUTT: |
michael@0 | 1321 | aContext->SetLineCap(gfxContext::LINE_CAP_BUTT); |
michael@0 | 1322 | break; |
michael@0 | 1323 | case NS_STYLE_STROKE_LINECAP_ROUND: |
michael@0 | 1324 | aContext->SetLineCap(gfxContext::LINE_CAP_ROUND); |
michael@0 | 1325 | break; |
michael@0 | 1326 | case NS_STYLE_STROKE_LINECAP_SQUARE: |
michael@0 | 1327 | aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE); |
michael@0 | 1328 | break; |
michael@0 | 1329 | } |
michael@0 | 1330 | |
michael@0 | 1331 | aContext->SetMiterLimit(style->mStrokeMiterlimit); |
michael@0 | 1332 | |
michael@0 | 1333 | switch (style->mStrokeLinejoin) { |
michael@0 | 1334 | case NS_STYLE_STROKE_LINEJOIN_MITER: |
michael@0 | 1335 | aContext->SetLineJoin(gfxContext::LINE_JOIN_MITER); |
michael@0 | 1336 | break; |
michael@0 | 1337 | case NS_STYLE_STROKE_LINEJOIN_ROUND: |
michael@0 | 1338 | aContext->SetLineJoin(gfxContext::LINE_JOIN_ROUND); |
michael@0 | 1339 | break; |
michael@0 | 1340 | case NS_STYLE_STROKE_LINEJOIN_BEVEL: |
michael@0 | 1341 | aContext->SetLineJoin(gfxContext::LINE_JOIN_BEVEL); |
michael@0 | 1342 | break; |
michael@0 | 1343 | } |
michael@0 | 1344 | } |
michael@0 | 1345 | |
michael@0 | 1346 | static bool |
michael@0 | 1347 | GetStrokeDashData(nsIFrame* aFrame, |
michael@0 | 1348 | FallibleTArray<gfxFloat>& aDashes, |
michael@0 | 1349 | gfxFloat* aDashOffset, |
michael@0 | 1350 | gfxTextContextPaint *aContextPaint) |
michael@0 | 1351 | { |
michael@0 | 1352 | const nsStyleSVG* style = aFrame->StyleSVG(); |
michael@0 | 1353 | nsPresContext *presContext = aFrame->PresContext(); |
michael@0 | 1354 | nsIContent *content = aFrame->GetContent(); |
michael@0 | 1355 | nsSVGElement *ctx = static_cast<nsSVGElement*> |
michael@0 | 1356 | (content->IsNodeOfType(nsINode::eTEXT) ? |
michael@0 | 1357 | content->GetParent() : content); |
michael@0 | 1358 | |
michael@0 | 1359 | gfxFloat totalLength = 0.0; |
michael@0 | 1360 | if (aContextPaint && style->mStrokeDasharrayFromObject) { |
michael@0 | 1361 | aDashes = aContextPaint->GetStrokeDashArray(); |
michael@0 | 1362 | |
michael@0 | 1363 | for (uint32_t i = 0; i < aDashes.Length(); i++) { |
michael@0 | 1364 | if (aDashes[i] < 0.0) { |
michael@0 | 1365 | return false; |
michael@0 | 1366 | } |
michael@0 | 1367 | totalLength += aDashes[i]; |
michael@0 | 1368 | } |
michael@0 | 1369 | |
michael@0 | 1370 | } else { |
michael@0 | 1371 | uint32_t count = style->mStrokeDasharrayLength; |
michael@0 | 1372 | if (!count || !aDashes.SetLength(count)) { |
michael@0 | 1373 | return false; |
michael@0 | 1374 | } |
michael@0 | 1375 | |
michael@0 | 1376 | gfxFloat pathScale = 1.0; |
michael@0 | 1377 | |
michael@0 | 1378 | if (content->Tag() == nsGkAtoms::path) { |
michael@0 | 1379 | pathScale = static_cast<SVGPathElement*>(content)-> |
michael@0 | 1380 | GetPathLengthScale(SVGPathElement::eForStroking); |
michael@0 | 1381 | if (pathScale <= 0) { |
michael@0 | 1382 | return false; |
michael@0 | 1383 | } |
michael@0 | 1384 | } |
michael@0 | 1385 | |
michael@0 | 1386 | const nsStyleCoord *dasharray = style->mStrokeDasharray; |
michael@0 | 1387 | |
michael@0 | 1388 | for (uint32_t i = 0; i < count; i++) { |
michael@0 | 1389 | aDashes[i] = SVGContentUtils::CoordToFloat(presContext, |
michael@0 | 1390 | ctx, |
michael@0 | 1391 | dasharray[i]) * pathScale; |
michael@0 | 1392 | if (aDashes[i] < 0.0) { |
michael@0 | 1393 | return false; |
michael@0 | 1394 | } |
michael@0 | 1395 | totalLength += aDashes[i]; |
michael@0 | 1396 | } |
michael@0 | 1397 | } |
michael@0 | 1398 | |
michael@0 | 1399 | if (aContextPaint && style->mStrokeDashoffsetFromObject) { |
michael@0 | 1400 | *aDashOffset = aContextPaint->GetStrokeDashOffset(); |
michael@0 | 1401 | } else { |
michael@0 | 1402 | *aDashOffset = SVGContentUtils::CoordToFloat(presContext, |
michael@0 | 1403 | ctx, |
michael@0 | 1404 | style->mStrokeDashoffset); |
michael@0 | 1405 | } |
michael@0 | 1406 | |
michael@0 | 1407 | return (totalLength > 0.0); |
michael@0 | 1408 | } |
michael@0 | 1409 | |
michael@0 | 1410 | void |
michael@0 | 1411 | nsSVGUtils::SetupCairoStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext, |
michael@0 | 1412 | gfxTextContextPaint *aContextPaint) |
michael@0 | 1413 | { |
michael@0 | 1414 | SetupCairoStrokeBBoxGeometry(aFrame, aContext, aContextPaint); |
michael@0 | 1415 | |
michael@0 | 1416 | AutoFallibleTArray<gfxFloat, 10> dashes; |
michael@0 | 1417 | gfxFloat dashOffset; |
michael@0 | 1418 | if (GetStrokeDashData(aFrame, dashes, &dashOffset, aContextPaint)) { |
michael@0 | 1419 | aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset); |
michael@0 | 1420 | } |
michael@0 | 1421 | } |
michael@0 | 1422 | |
michael@0 | 1423 | uint16_t |
michael@0 | 1424 | nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) |
michael@0 | 1425 | { |
michael@0 | 1426 | uint16_t flags = 0; |
michael@0 | 1427 | |
michael@0 | 1428 | switch(aFrame->StyleVisibility()->mPointerEvents) { |
michael@0 | 1429 | case NS_STYLE_POINTER_EVENTS_NONE: |
michael@0 | 1430 | break; |
michael@0 | 1431 | case NS_STYLE_POINTER_EVENTS_AUTO: |
michael@0 | 1432 | case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: |
michael@0 | 1433 | if (aFrame->StyleVisibility()->IsVisible()) { |
michael@0 | 1434 | if (aFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) |
michael@0 | 1435 | flags |= SVG_HIT_TEST_FILL; |
michael@0 | 1436 | if (aFrame->StyleSVG()->mStroke.mType != eStyleSVGPaintType_None) |
michael@0 | 1437 | flags |= SVG_HIT_TEST_STROKE; |
michael@0 | 1438 | if (aFrame->StyleSVG()->mStrokeOpacity > 0) |
michael@0 | 1439 | flags |= SVG_HIT_TEST_CHECK_MRECT; |
michael@0 | 1440 | } |
michael@0 | 1441 | break; |
michael@0 | 1442 | case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: |
michael@0 | 1443 | if (aFrame->StyleVisibility()->IsVisible()) { |
michael@0 | 1444 | flags |= SVG_HIT_TEST_FILL; |
michael@0 | 1445 | } |
michael@0 | 1446 | break; |
michael@0 | 1447 | case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: |
michael@0 | 1448 | if (aFrame->StyleVisibility()->IsVisible()) { |
michael@0 | 1449 | flags |= SVG_HIT_TEST_STROKE; |
michael@0 | 1450 | } |
michael@0 | 1451 | break; |
michael@0 | 1452 | case NS_STYLE_POINTER_EVENTS_VISIBLE: |
michael@0 | 1453 | if (aFrame->StyleVisibility()->IsVisible()) { |
michael@0 | 1454 | flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; |
michael@0 | 1455 | } |
michael@0 | 1456 | break; |
michael@0 | 1457 | case NS_STYLE_POINTER_EVENTS_PAINTED: |
michael@0 | 1458 | if (aFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) |
michael@0 | 1459 | flags |= SVG_HIT_TEST_FILL; |
michael@0 | 1460 | if (aFrame->StyleSVG()->mStroke.mType != eStyleSVGPaintType_None) |
michael@0 | 1461 | flags |= SVG_HIT_TEST_STROKE; |
michael@0 | 1462 | if (aFrame->StyleSVG()->mStrokeOpacity) |
michael@0 | 1463 | flags |= SVG_HIT_TEST_CHECK_MRECT; |
michael@0 | 1464 | break; |
michael@0 | 1465 | case NS_STYLE_POINTER_EVENTS_FILL: |
michael@0 | 1466 | flags |= SVG_HIT_TEST_FILL; |
michael@0 | 1467 | break; |
michael@0 | 1468 | case NS_STYLE_POINTER_EVENTS_STROKE: |
michael@0 | 1469 | flags |= SVG_HIT_TEST_STROKE; |
michael@0 | 1470 | break; |
michael@0 | 1471 | case NS_STYLE_POINTER_EVENTS_ALL: |
michael@0 | 1472 | flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; |
michael@0 | 1473 | break; |
michael@0 | 1474 | default: |
michael@0 | 1475 | NS_ERROR("not reached"); |
michael@0 | 1476 | break; |
michael@0 | 1477 | } |
michael@0 | 1478 | |
michael@0 | 1479 | return flags; |
michael@0 | 1480 | } |
michael@0 | 1481 | |
michael@0 | 1482 | bool |
michael@0 | 1483 | nsSVGUtils::SetupCairoStroke(nsIFrame* aFrame, gfxContext* aContext, |
michael@0 | 1484 | gfxTextContextPaint *aContextPaint) |
michael@0 | 1485 | { |
michael@0 | 1486 | if (!HasStroke(aFrame, aContextPaint)) { |
michael@0 | 1487 | return false; |
michael@0 | 1488 | } |
michael@0 | 1489 | SetupCairoStrokeGeometry(aFrame, aContext, aContextPaint); |
michael@0 | 1490 | |
michael@0 | 1491 | return SetupCairoStrokePaint(aFrame, aContext, aContextPaint); |
michael@0 | 1492 | } |
michael@0 | 1493 | |
michael@0 | 1494 | bool |
michael@0 | 1495 | nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext, |
michael@0 | 1496 | DrawMode aDrawMode, |
michael@0 | 1497 | gfxTextContextPaint* aContextPaint) |
michael@0 | 1498 | { |
michael@0 | 1499 | nsIFrame* frame = aElement->GetPrimaryFrame(); |
michael@0 | 1500 | nsISVGChildFrame* svgFrame = do_QueryFrame(frame); |
michael@0 | 1501 | if (!svgFrame) { |
michael@0 | 1502 | return false; |
michael@0 | 1503 | } |
michael@0 | 1504 | nsRefPtr<nsRenderingContext> context(new nsRenderingContext()); |
michael@0 | 1505 | context->Init(frame->PresContext()->DeviceContext(), aContext); |
michael@0 | 1506 | context->AddUserData(&gfxTextContextPaint::sUserDataKey, aContextPaint, |
michael@0 | 1507 | nullptr); |
michael@0 | 1508 | svgFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); |
michael@0 | 1509 | nsresult rv = svgFrame->PaintSVG(context, nullptr, frame); |
michael@0 | 1510 | return NS_SUCCEEDED(rv); |
michael@0 | 1511 | } |
michael@0 | 1512 | |
michael@0 | 1513 | bool |
michael@0 | 1514 | nsSVGUtils::GetSVGGlyphExtents(Element* aElement, |
michael@0 | 1515 | const gfxMatrix& aSVGToAppSpace, |
michael@0 | 1516 | gfxRect* aResult) |
michael@0 | 1517 | { |
michael@0 | 1518 | nsIFrame* frame = aElement->GetPrimaryFrame(); |
michael@0 | 1519 | nsISVGChildFrame* svgFrame = do_QueryFrame(frame); |
michael@0 | 1520 | if (!svgFrame) { |
michael@0 | 1521 | return false; |
michael@0 | 1522 | } |
michael@0 | 1523 | |
michael@0 | 1524 | gfxMatrix transform(aSVGToAppSpace); |
michael@0 | 1525 | nsIContent* content = frame->GetContent(); |
michael@0 | 1526 | if (content->IsSVG()) { |
michael@0 | 1527 | transform = static_cast<nsSVGElement*>(content)-> |
michael@0 | 1528 | PrependLocalTransformsTo(aSVGToAppSpace); |
michael@0 | 1529 | } |
michael@0 | 1530 | |
michael@0 | 1531 | *aResult = svgFrame->GetBBoxContribution(gfx::ToMatrix(transform), |
michael@0 | 1532 | nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry | |
michael@0 | 1533 | nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry | |
michael@0 | 1534 | nsSVGUtils::eBBoxIncludeMarkers).ToThebesRect(); |
michael@0 | 1535 | return true; |
michael@0 | 1536 | } |
michael@0 | 1537 | |
michael@0 | 1538 | nsRect |
michael@0 | 1539 | nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect, |
michael@0 | 1540 | const gfxMatrix &aToCanvas, |
michael@0 | 1541 | const nsPresContext *presContext) |
michael@0 | 1542 | { |
michael@0 | 1543 | return nsLayoutUtils::RoundGfxRectToAppRect( |
michael@0 | 1544 | aToCanvas.TransformBounds(aUserspaceRect), |
michael@0 | 1545 | presContext->AppUnitsPerDevPixel()); |
michael@0 | 1546 | } |