nsSVGClipPathFrame.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. // Main header first:
  6. #include "nsSVGClipPathFrame.h"
  7. // Keep others in (case-insensitive) order:
  8. #include "gfxContext.h"
  9. #include "mozilla/dom/SVGClipPathElement.h"
  10. #include "nsGkAtoms.h"
  11. #include "nsSVGEffects.h"
  12. #include "nsSVGPathGeometryElement.h"
  13. #include "nsSVGPathGeometryFrame.h"
  14. #include "nsSVGUtils.h"
  15. using namespace mozilla;
  16. using namespace mozilla::dom;
  17. using namespace mozilla::gfx;
  18. // Arbitrary number
  19. #define MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH int16_t(512)
  20. //----------------------------------------------------------------------
  21. // Implementation
  22. nsIFrame*
  23. NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  24. {
  25. return new (aPresShell) nsSVGClipPathFrame(aContext);
  26. }
  27. NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)
  28. void
  29. nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
  30. nsIFrame* aClippedFrame,
  31. const gfxMatrix& aMatrix)
  32. {
  33. MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");
  34. DrawTarget& aDrawTarget = *aContext.GetDrawTarget();
  35. // No need for AutoReferenceLimiter since simple clip paths can't create
  36. // a reference loop (they don't reference other clip paths).
  37. // Restore current transform after applying clip path:
  38. gfxContextMatrixAutoSaveRestore autoRestore(&aContext);
  39. RefPtr<Path> clipPath;
  40. nsISVGChildFrame* singleClipPathChild = nullptr;
  41. IsTrivial(&singleClipPathChild);
  42. if (singleClipPathChild) {
  43. nsSVGPathGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
  44. if (pathFrame) {
  45. nsSVGPathGeometryElement* pathElement =
  46. static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent());
  47. gfxMatrix toChildsUserSpace = pathElement->
  48. PrependLocalTransformsTo(GetClipPathTransform(aClippedFrame) * aMatrix,
  49. eUserSpaceToParent);
  50. gfxMatrix newMatrix =
  51. aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers();
  52. if (!newMatrix.IsSingular()) {
  53. aContext.SetMatrix(newMatrix);
  54. FillRule clipRule =
  55. nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
  56. clipPath = pathElement->GetOrBuildPath(aDrawTarget, clipRule);
  57. }
  58. }
  59. }
  60. if (clipPath) {
  61. aContext.Clip(clipPath);
  62. } else {
  63. // The spec says clip away everything if we have no children or the
  64. // clipping path otherwise can't be resolved:
  65. aContext.Clip(Rect());
  66. }
  67. }
  68. already_AddRefed<SourceSurface>
  69. nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext,
  70. nsIFrame* aClippedFrame,
  71. const gfxMatrix& aMatrix,
  72. Matrix* aMaskTransform,
  73. SourceSurface* aExtraMask,
  74. const Matrix& aExtraMasksTransform,
  75. DrawResult* aResult)
  76. {
  77. MOZ_ASSERT(!IsTrivial(), "Caller needs to use ApplyClipPath");
  78. if (aResult) {
  79. *aResult = DrawResult::SUCCESS;
  80. }
  81. DrawTarget& aReferenceDT = *aReferenceContext.GetDrawTarget();
  82. // A clipPath can reference another clipPath. We re-enter this method for
  83. // each clipPath in a reference chain, so here we limit chain length:
  84. static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing;
  85. AutoReferenceLimiter
  86. refChainLengthLimiter(&sRefChainLengthCounter,
  87. MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH);
  88. if (!refChainLengthLimiter.Reference()) {
  89. return nullptr; // Reference chain is too long!
  90. }
  91. // And to prevent reference loops we check that this clipPath only appears
  92. // once in the reference chain (if any) that we're currently processing:
  93. AutoReferenceLimiter refLoopDetector(&mReferencing, 1);
  94. if (!refLoopDetector.Reference()) {
  95. return nullptr; // Reference loop!
  96. }
  97. IntRect devSpaceClipExtents;
  98. {
  99. gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext);
  100. aReferenceContext.SetMatrix(gfxMatrix());
  101. gfxRect rect = aReferenceContext.GetClipExtents();
  102. devSpaceClipExtents = RoundedOut(ToRect(rect));
  103. if (devSpaceClipExtents.IsEmpty()) {
  104. // We don't need to create a mask surface, all drawing is clipped anyway.
  105. return nullptr;
  106. }
  107. }
  108. RefPtr<DrawTarget> maskDT =
  109. aReferenceDT.CreateSimilarDrawTarget(devSpaceClipExtents.Size(),
  110. SurfaceFormat::A8);
  111. gfxMatrix mat = aReferenceContext.CurrentMatrix() *
  112. gfxMatrix::Translation(-devSpaceClipExtents.TopLeft());
  113. // Paint this clipPath's contents into maskDT:
  114. {
  115. RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(maskDT);
  116. if (!ctx) {
  117. gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
  118. return nullptr;
  119. }
  120. ctx->SetMatrix(mat);
  121. // We need to set mMatrixForChildren here so that under the PaintSVG calls
  122. // on our children (below) our GetCanvasTM() method will return the correct
  123. // transform.
  124. mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
  125. // Check if this clipPath is itself clipped by another clipPath:
  126. nsSVGClipPathFrame* clipPathThatClipsClipPath =
  127. nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
  128. bool clippingOfClipPathRequiredMasking;
  129. if (clipPathThatClipsClipPath) {
  130. ctx->Save();
  131. clippingOfClipPathRequiredMasking = !clipPathThatClipsClipPath->IsTrivial();
  132. if (!clippingOfClipPathRequiredMasking) {
  133. clipPathThatClipsClipPath->ApplyClipPath(*ctx, aClippedFrame, aMatrix);
  134. } else {
  135. Matrix maskTransform;
  136. RefPtr<SourceSurface> mask =
  137. clipPathThatClipsClipPath->GetClipMask(*ctx, aClippedFrame,
  138. aMatrix, &maskTransform);
  139. ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
  140. mask, maskTransform);
  141. // The corresponding PopGroupAndBlend call below will mask the
  142. // blend using |mask|.
  143. }
  144. }
  145. // Paint our children into the mask:
  146. for (nsIFrame* kid = mFrames.FirstChild(); kid;
  147. kid = kid->GetNextSibling()) {
  148. nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
  149. if (SVGFrame) {
  150. // The CTM of each frame referencing us can be different.
  151. SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
  152. bool isOK = true;
  153. // Children of this clipPath may themselves be clipped.
  154. nsSVGClipPathFrame *clipPathThatClipsChild =
  155. nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK);
  156. if (!isOK) {
  157. continue;
  158. }
  159. bool childsClipPathRequiresMasking;
  160. if (clipPathThatClipsChild) {
  161. childsClipPathRequiresMasking = !clipPathThatClipsChild->IsTrivial();
  162. ctx->Save();
  163. if (!childsClipPathRequiresMasking) {
  164. clipPathThatClipsChild->ApplyClipPath(*ctx, aClippedFrame, aMatrix);
  165. } else {
  166. Matrix maskTransform;
  167. RefPtr<SourceSurface> mask =
  168. clipPathThatClipsChild->GetClipMask(*ctx, aClippedFrame,
  169. aMatrix, &maskTransform);
  170. ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
  171. mask, maskTransform);
  172. // The corresponding PopGroupAndBlend call below will mask the
  173. // blend using |mask|.
  174. }
  175. }
  176. gfxMatrix toChildsUserSpace = mMatrixForChildren;
  177. nsIFrame* child = do_QueryFrame(SVGFrame);
  178. nsIContent* childContent = child->GetContent();
  179. if (childContent->IsSVGElement()) {
  180. toChildsUserSpace =
  181. static_cast<const nsSVGElement*>(childContent)->
  182. PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent);
  183. }
  184. // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
  185. // nsSVGPathGeometryFrame::Render checks for that state bit and paints
  186. // only the geometry (opaque black) if set.
  187. DrawResult result = SVGFrame->PaintSVG(*ctx, toChildsUserSpace);
  188. if (aResult) {
  189. *aResult &= result;
  190. }
  191. if (clipPathThatClipsChild) {
  192. if (childsClipPathRequiresMasking) {
  193. ctx->PopGroupAndBlend();
  194. }
  195. ctx->Restore();
  196. }
  197. }
  198. }
  199. if (clipPathThatClipsClipPath) {
  200. if (clippingOfClipPathRequiredMasking) {
  201. ctx->PopGroupAndBlend();
  202. }
  203. ctx->Restore();
  204. }
  205. }
  206. // Moz2D transforms in the opposite direction to Thebes
  207. mat.Invert();
  208. if (aExtraMask) {
  209. // We could potentially due this more efficiently with OPERATOR_IN
  210. // but that operator does not work well on CG or D2D
  211. RefPtr<SourceSurface> currentMask = maskDT->Snapshot();
  212. Matrix transform = maskDT->GetTransform();
  213. maskDT->SetTransform(Matrix());
  214. maskDT->ClearRect(Rect(0, 0,
  215. devSpaceClipExtents.width,
  216. devSpaceClipExtents.height));
  217. maskDT->SetTransform(aExtraMasksTransform * transform);
  218. // draw currentMask with the inverse of the transform that we just so that
  219. // it ends up in the same spot with aExtraMask transformed by aExtraMasksTransform
  220. maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP, aExtraMasksTransform.Inverse() * ToMatrix(mat)),
  221. aExtraMask,
  222. Point(0, 0));
  223. }
  224. *aMaskTransform = ToMatrix(mat);
  225. return maskDT->Snapshot();
  226. }
  227. bool
  228. nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
  229. const gfxPoint &aPoint)
  230. {
  231. // A clipPath can reference another clipPath. We re-enter this method for
  232. // each clipPath in a reference chain, so here we limit chain length:
  233. static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing;
  234. AutoReferenceLimiter
  235. refChainLengthLimiter(&sRefChainLengthCounter,
  236. MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH);
  237. if (!refChainLengthLimiter.Reference()) {
  238. return false; // Reference chain is too long!
  239. }
  240. // And to prevent reference loops we check that this clipPath only appears
  241. // once in the reference chain (if any) that we're currently processing:
  242. AutoReferenceLimiter refLoopDetector(&mReferencing, 1);
  243. if (!refLoopDetector.Reference()) {
  244. return true; // Reference loop!
  245. }
  246. gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
  247. if (!matrix.Invert()) {
  248. return false;
  249. }
  250. gfxPoint point = matrix.Transform(aPoint);
  251. // clipPath elements can themselves be clipped by a different clip path. In
  252. // that case the other clip path further clips away the element that is being
  253. // clipped by the original clipPath. If this clipPath is being clipped by a
  254. // different clip path we need to check if it prevents the original element
  255. // from recieving events at aPoint:
  256. nsSVGClipPathFrame *clipPathFrame =
  257. nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
  258. if (clipPathFrame &&
  259. !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
  260. return false;
  261. }
  262. for (nsIFrame* kid = mFrames.FirstChild(); kid;
  263. kid = kid->GetNextSibling()) {
  264. nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
  265. if (SVGFrame) {
  266. gfxPoint pointForChild = point;
  267. gfxMatrix m = static_cast<nsSVGElement*>(kid->GetContent())->
  268. PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent);
  269. if (!m.IsIdentity()) {
  270. if (!m.Invert()) {
  271. return false;
  272. }
  273. pointForChild = m.Transform(point);
  274. }
  275. if (SVGFrame->GetFrameForPoint(pointForChild)) {
  276. return true;
  277. }
  278. }
  279. }
  280. return false;
  281. }
  282. bool
  283. nsSVGClipPathFrame::IsTrivial(nsISVGChildFrame **aSingleChild)
  284. {
  285. // If the clip path is clipped then it's non-trivial
  286. if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr))
  287. return false;
  288. if (aSingleChild) {
  289. *aSingleChild = nullptr;
  290. }
  291. nsISVGChildFrame *foundChild = nullptr;
  292. for (nsIFrame* kid = mFrames.FirstChild(); kid;
  293. kid = kid->GetNextSibling()) {
  294. nsISVGChildFrame *svgChild = do_QueryFrame(kid);
  295. if (svgChild) {
  296. // We consider a non-trivial clipPath to be one containing
  297. // either more than one svg child and/or a svg container
  298. if (foundChild || svgChild->IsDisplayContainer())
  299. return false;
  300. // or where the child is itself clipped
  301. if (nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(nullptr))
  302. return false;
  303. foundChild = svgChild;
  304. }
  305. }
  306. if (aSingleChild) {
  307. *aSingleChild = foundChild;
  308. }
  309. return true;
  310. }
  311. bool
  312. nsSVGClipPathFrame::IsValid()
  313. {
  314. // A clipPath can reference another clipPath. We re-enter this method for
  315. // each clipPath in a reference chain, so here we limit chain length:
  316. static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing;
  317. AutoReferenceLimiter
  318. refChainLengthLimiter(&sRefChainLengthCounter,
  319. MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH);
  320. if (!refChainLengthLimiter.Reference()) {
  321. return false; // Reference chain is too long!
  322. }
  323. // And to prevent reference loops we check that this clipPath only appears
  324. // once in the reference chain (if any) that we're currently processing:
  325. AutoReferenceLimiter refLoopDetector(&mReferencing, 1);
  326. if (!refLoopDetector.Reference()) {
  327. return false; // Reference loop!
  328. }
  329. bool isOK = true;
  330. nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(&isOK);
  331. if (!isOK) {
  332. return false;
  333. }
  334. for (nsIFrame* kid = mFrames.FirstChild(); kid;
  335. kid = kid->GetNextSibling()) {
  336. nsIAtom* kidType = kid->GetType();
  337. if (kidType == nsGkAtoms::svgUseFrame) {
  338. for (nsIFrame* grandKid : kid->PrincipalChildList()) {
  339. nsIAtom* grandKidType = grandKid->GetType();
  340. if (grandKidType != nsGkAtoms::svgPathGeometryFrame &&
  341. grandKidType != nsGkAtoms::svgTextFrame) {
  342. return false;
  343. }
  344. }
  345. continue;
  346. }
  347. if (kidType != nsGkAtoms::svgPathGeometryFrame &&
  348. kidType != nsGkAtoms::svgTextFrame) {
  349. return false;
  350. }
  351. }
  352. return true;
  353. }
  354. nsresult
  355. nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
  356. nsIAtom* aAttribute,
  357. int32_t aModType)
  358. {
  359. if (aNameSpaceID == kNameSpaceID_None) {
  360. if (aAttribute == nsGkAtoms::transform) {
  361. nsSVGEffects::InvalidateDirectRenderingObservers(this);
  362. nsSVGUtils::NotifyChildrenOfSVGChange(this,
  363. nsISVGChildFrame::TRANSFORM_CHANGED);
  364. }
  365. if (aAttribute == nsGkAtoms::clipPathUnits) {
  366. nsSVGEffects::InvalidateDirectRenderingObservers(this);
  367. }
  368. }
  369. return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
  370. aAttribute, aModType);
  371. }
  372. void
  373. nsSVGClipPathFrame::Init(nsIContent* aContent,
  374. nsContainerFrame* aParent,
  375. nsIFrame* aPrevInFlow)
  376. {
  377. NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
  378. "Content is not an SVG clipPath!");
  379. AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
  380. nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
  381. }
  382. nsIAtom *
  383. nsSVGClipPathFrame::GetType() const
  384. {
  385. return nsGkAtoms::svgClipPathFrame;
  386. }
  387. gfxMatrix
  388. nsSVGClipPathFrame::GetCanvasTM()
  389. {
  390. return mMatrixForChildren;
  391. }
  392. gfxMatrix
  393. nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame)
  394. {
  395. SVGClipPathElement *content = static_cast<SVGClipPathElement*>(mContent);
  396. gfxMatrix tm = content->PrependLocalTransformsTo(gfxMatrix());
  397. nsSVGEnum* clipPathUnits =
  398. &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
  399. return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame);
  400. }
  401. SVGBBox
  402. nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox,
  403. const gfxMatrix &aMatrix)
  404. {
  405. nsIContent* node = GetContent()->GetFirstChild();
  406. SVGBBox unionBBox, tmpBBox;
  407. for (; node; node = node->GetNextSibling()) {
  408. nsIFrame *frame =
  409. static_cast<nsSVGElement*>(node)->GetPrimaryFrame();
  410. if (frame) {
  411. nsISVGChildFrame *svg = do_QueryFrame(frame);
  412. if (svg) {
  413. tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(aMatrix),
  414. nsSVGUtils::eBBoxIncludeFill);
  415. nsSVGEffects::EffectProperties effectProperties =
  416. nsSVGEffects::GetEffectProperties(frame);
  417. bool isOK = true;
  418. nsSVGClipPathFrame *clipPathFrame =
  419. effectProperties.GetClipPathFrame(&isOK);
  420. if (clipPathFrame && isOK) {
  421. tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix);
  422. }
  423. tmpBBox.Intersect(aBBox);
  424. unionBBox.UnionEdges(tmpBBox);
  425. }
  426. }
  427. }
  428. nsSVGEffects::EffectProperties props =
  429. nsSVGEffects::GetEffectProperties(this);
  430. if (props.mClipPath) {
  431. bool isOK = true;
  432. nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK);
  433. if (clipPathFrame && isOK) {
  434. tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix);
  435. unionBBox.Intersect(tmpBBox);
  436. } else if (!isOK) {
  437. unionBBox = SVGBBox();
  438. }
  439. }
  440. return unionBBox;
  441. }