Bug 759568 - Part 2. Render background-clip:text; r=jfkthame
authorCJKu <cku@mozilla.com>
Thu, 14 Apr 2016 16:28:07 +0800
changeset 331117 7854fe3d43d6612ab3b015220cfcf728680be41c
parent 331116 7176863d8df7554359e3fce9be36e883b102dd12
child 331118 cd594b3e98c11da6aa1c315183706c1977d5ddad
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs759568
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 759568 - Part 2. Render background-clip:text; r=jfkthame MozReview-Commit-ID: HQHBe7OioWs
layout/base/nsCSSRendering.cpp
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/generic/nsFrame.cpp
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -1809,17 +1809,18 @@ nsCSSRendering::GetImageLayerClip(const 
       aClipState->mAdditionalBGClipArea.Deflate(padding);
     }
 
     // Also clip at a non-scrolling, rounded-corner 'padding-box',
     // same as the scrolled content because of the 'overflow' property.
     backgroundClip = NS_STYLE_IMAGELAYER_CLIP_PADDING;
   }
 
-  if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_BORDER) {
+  if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_BORDER &&
+      backgroundClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
     nsMargin border = aForFrame->GetUsedBorder();
     if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING) {
       // Reduce |border| by 1px (device pixels) on all sides, if
       // possible, so that we don't get antialiasing seams between the
       // background and border.
       border.top = std::max(0, border.top - aAppUnitsPerPixel);
       border.right = std::max(0, border.right - aAppUnitsPerPixel);
       border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -486,16 +486,56 @@ AddAnimationsForProperty(nsIFrame* aFram
       continue;
     }
 
     AddAnimationForProperty(aFrame, *property, anim, aLayer, aData, aPending);
     effect->SetIsRunningOnCompositor(aProperty, true);
   }
 }
 
+static void
+ClipBackgroundByText(nsIFrame* aFrame, nsRenderingContext* aContext,
+                     const gfxRect& aFillRect)
+{
+  // The main function of enabling background-clip:text property value.
+  // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call
+  // this function to
+  // 1. Ask every text frame objects in aFrame puts glyph paths into aContext.
+  // 2. Then, clip aContext.
+  //
+  // Then, nsDisplayBackgroundImage paints bg-images into this clipped region,
+  // so we get images embedded in text shape!
+
+  nsDisplayListBuilder builder(aFrame, nsDisplayListBuilder::GENERATE_GLYPH, false);
+
+  builder.EnterPresShell(aFrame);
+  nsDisplayList list;
+  aFrame->BuildDisplayListForStackingContext(&builder,
+                                      nsRect(nsPoint(0, 0), aFrame->GetSize()),
+                                      &list);
+  builder.LeavePresShell(aFrame);
+
+#ifdef DEBUG
+  for (nsDisplayItem* i = list.GetBottom(); i; i = i->GetAbove()) {
+    MOZ_ASSERT(nsDisplayItem::TYPE_TEXT == i->GetType());
+  }
+#endif
+
+  gfxContext* ctx = aContext->ThebesContext();
+  gfxContextMatrixAutoSaveRestore save(ctx);
+  ctx->SetMatrix(ctx->CurrentMatrix().Translate(aFillRect.TopLeft()));
+  ctx->NewPath();
+
+  RefPtr<LayerManager> layerManager =
+    list.PaintRoot(&builder, aContext, nsDisplayList::PAINT_DEFAULT);
+
+  ctx->Clip();
+  list.DeleteAll();
+}
+
 /* static */ void
 nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(Layer* aLayer,
                                                          nsDisplayListBuilder* aBuilder,
                                                          nsDisplayItem* aItem,
                                                          nsIFrame* aFrame,
                                                          nsCSSProperty aProperty)
 {
   MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
@@ -2779,16 +2819,17 @@ nsDisplayBackgroundImage::GetInsideClipR
 
   nsRect clipRect;
   if (frame->GetType() == nsGkAtoms::canvasFrame) {
     nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame);
     clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame();
   } else {
     switch (aClip) {
     case NS_STYLE_IMAGELAYER_CLIP_BORDER:
+    case NS_STYLE_IMAGELAYER_CLIP_TEXT:
       clipRect = nsRect(aItem->ToReferenceFrame(), frame->GetSize());
       break;
     case NS_STYLE_IMAGELAYER_CLIP_PADDING:
       clipRect = frame->GetPaddingRect() - frame->GetPosition() + aItem->ToReferenceFrame();
       break;
     case NS_STYLE_IMAGELAYER_CLIP_CONTENT:
       clipRect = frame->GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
       break;
@@ -2816,17 +2857,18 @@ nsDisplayBackgroundImage::GetOpaqueRegio
   // this could easily lead to O(N^2) behavior inside InlineBackgroundData,
   // which expects frames to be sent to it in content order, not reverse
   // content order which we'll produce here.
   // Of course, if there's only one frame in the flow, it doesn't matter.
   if (mFrame->StyleBorder()->mBoxDecorationBreak ==
         NS_STYLE_BOX_DECORATION_BREAK_CLONE ||
       (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
     const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
-    if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL) {
+    if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL &&
+        layer.mClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
       result = GetInsideClipRegion(this, layer.mClip, mBounds);
     }
   }
 
   return result;
 }
 
 bool
@@ -2890,27 +2932,40 @@ nsDisplayBackgroundImage::Paint(nsDispla
                                 nsRenderingContext* aCtx) {
   PaintInternal(aBuilder, aCtx, mVisibleRect, &mBounds);
 }
 
 void
 nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder,
                                         nsRenderingContext* aCtx, const nsRect& aBounds,
                                         nsRect* aClipRect) {
-  nsPoint offset = ToReferenceFrame();
   uint32_t flags = aBuilder->GetBackgroundPaintFlags();
   CheckForBorderItem(this, flags);
 
+  nsRect borderBox = nsRect(ToReferenceFrame(), mFrame->GetSize());
+  gfxContext* ctx = aCtx->ThebesContext();
+  uint8_t clip = mBackgroundStyle->mImage.mLayers[mLayer].mClip;
+
+  if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+    gfxRect bounds = nsLayoutUtils::RectToGfxRect(borderBox, mFrame->PresContext()->AppUnitsPerDevPixel());
+    ctx->Save();
+    ClipBackgroundByText(mFrame, aCtx, bounds);
+  }
+
   image::DrawResult result =
     nsCSSRendering::PaintBackground(mFrame->PresContext(), *aCtx, mFrame,
                                     aBounds,
-                                    nsRect(offset, mFrame->GetSize()),
+                                    borderBox,
                                     flags, aClipRect, mLayer,
                                     CompositionOp::OP_OVER);
 
+  if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+    ctx->Restore();
+  }
+
   nsDisplayBackgroundGeometry::UpdateDrawResult(this, result);
 }
 
 void nsDisplayBackgroundImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                                          const nsDisplayItemGeometry* aGeometry,
                                                          nsRegion* aInvalidRegion)
 {
   if (!mBackgroundStyle) {
@@ -3301,29 +3356,45 @@ nsDisplayBackgroundColor::Paint(nsDispla
   nsRect borderBox = nsRect(ToReferenceFrame(), mFrame->GetSize());
 
 #if 0
   // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this
   // results in a precision induced rounding issue that makes the rect one
   // pixel shorter in rare cases. Disabled in favor of the old code for now.
   // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to
   // reproduce the bug.
+  //
+  // TODO:
+  // This new path does not include support for background-clip:text; need to
+  // be fixed if/when we switch to this new code path.
+
   DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
 
   Rect rect = NSRectToSnappedRect(borderBox,
                                   mFrame->PresContext()->AppUnitsPerDevPixel(),
                                   aDrawTarget);
   ColorPattern color(ToDeviceColor(mColor));
   aDrawTarget.FillRect(rect, color);
 #else
   gfxContext* ctx = aCtx->ThebesContext();
 
   gfxRect bounds =
     nsLayoutUtils::RectToGfxRect(borderBox, mFrame->PresContext()->AppUnitsPerDevPixel());
 
+  uint8_t clip = mBackgroundStyle->mImage.mLayers[0].mClip;
+  if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+    gfxContextAutoSaveRestore save(ctx);
+
+    ClipBackgroundByText(mFrame, aCtx, bounds);
+    ctx->SetColor(mColor);
+    ctx->Fill();
+
+    return;
+  }
+
   ctx->SetColor(mColor);
   ctx->NewPath();
   ctx->Rectangle(bounds, true);
   ctx->Fill();
 #endif
 }
 
 nsRegion
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -221,17 +221,18 @@ public:
    * @param aBuildCaret whether or not we should include the caret in any
    * display lists that we make.
    */
   enum Mode {
     PAINTING,
     EVENT_DELIVERY,
     PLUGIN_GEOMETRY,
     FRAME_VISIBILITY,
-    TRANSFORM_COMPUTATION
+    TRANSFORM_COMPUTATION,
+    GENERATE_GLYPH
   };
   nsDisplayListBuilder(nsIFrame* aReferenceFrame, Mode aMode, bool aBuildCaret);
   ~nsDisplayListBuilder();
 
   void SetWillComputePluginGeometry(bool aWillComputePluginGeometry)
   {
     mWillComputePluginGeometry = aWillComputePluginGeometry;
   }
@@ -263,16 +264,24 @@ public:
   bool IsForPainting() { return mMode == PAINTING; }
 
   /**
    * @return true if the display list is being built for determining frame
    * visibility.
    */
   bool IsForFrameVisibility() { return mMode == FRAME_VISIBILITY; }
 
+  /**
+   * @return true if the display list is being built for creating the glyph
+   * path from text items. While painting the display list, all text display
+   * items should only create glyph paths in target context, instead of
+   * drawing text into it.
+   */
+  bool IsForGenerateGlyphPath() { return mMode == GENERATE_GLYPH; }
+
   bool WillComputePluginGeometry() { return mWillComputePluginGeometry; }
   /**
    * @return true if "painting is suppressed" during page load and we
    * should paint only the background of the document.
    */
   bool IsBackgroundOnly() {
     NS_ASSERTION(mPresShellStates.Length() > 0,
                  "don't call this if we're not in a presshell");
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1852,18 +1852,23 @@ nsFrame::DisplayBackgroundUnconditional(
 void
 nsFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder*   aBuilder,
                                         const nsDisplayListSet& aLists,
                                         bool                    aForceBackground)
 {
   // The visibility check belongs here since child elements have the
   // opportunity to override the visibility property and display even if
   // their parent is hidden.
-  if (!IsVisibleForPainting(aBuilder))
+  if (!IsVisibleForPainting(aBuilder)) {
     return;
+  }
+
+  if (aBuilder->IsForGenerateGlyphPath()) {
+    return;
+  }
 
   nsCSSShadowArray* shadows = StyleEffects()->mBoxShadow;
   if (shadows && shadows->HasShadowWithInset(false)) {
     aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
       nsDisplayBoxShadowOuter(aBuilder, this));
   }
 
   bool bgIsThemed = DisplayBackgroundUnconditional(aBuilder, aLists,
@@ -2549,20 +2554,26 @@ nsIFrame::BuildDisplayListForChild(nsDis
                                    const nsRect&           aDirtyRect,
                                    const nsDisplayListSet& aLists,
                                    uint32_t                aFlags) {
   // If painting is restricted to just the background of the top level frame,
   // then we have nothing to do here.
   if (aBuilder->IsBackgroundOnly())
     return;
 
+  if (aBuilder->IsForGenerateGlyphPath()) {
+    if (nsGkAtoms::textFrame != aChild->GetType() && aChild->IsLeaf()) {
+      return;
+    }
+  }
+
   nsIFrame* child = aChild;
   if (child->GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
     return;
-  
+
   bool isSVG = (child->GetStateBits() & NS_FRAME_SVG_LAYOUT);
 
   // true if this is a real or pseudo stacking context
   bool pseudoStackingContext =
     (aFlags & DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT) != 0;
   if (!isSVG &&
       (aFlags & DISPLAY_CHILD_INLINE) &&
       !child->IsFrameOfType(eLineParticipant)) {
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -276,18 +276,16 @@ struct TextRunUserData {
  * underline information. The data is computed lazily and cached as necessary.
  * These live for just the duration of one paint operation.
  */
 class nsTextPaintStyle {
 public:
   explicit nsTextPaintStyle(nsTextFrame* aFrame);
 
   void SetResolveColors(bool aResolveColors) {
-    NS_ASSERTION(mFrame->IsSVGText() || aResolveColors,
-                 "must resolve colors is frame is not for SVG text");
     mResolveColors = aResolveColors;
   }
 
   nscolor GetTextColor();
   /**
    * Compute the colors for normally-selected text. Returns false if
    * the normal selection is not being displayed.
    */
@@ -4794,27 +4792,35 @@ nsDisplayText::Paint(nsDisplayListBuilde
   gfxContext* ctx = aCtx->ThebesContext();
   gfxContextAutoSaveRestore save(ctx);
 
   gfxRect pixelVisible(extraVisible.x, extraVisible.y,
                        extraVisible.width, extraVisible.height);
   pixelVisible.Inflate(2);
   pixelVisible.RoundOut();
 
-  ctx->NewPath();
-  ctx->Rectangle(pixelVisible);
-  ctx->Clip();
+  if (!aBuilder->IsForGenerateGlyphPath()) {
+    ctx->NewPath();
+    ctx->Rectangle(pixelVisible);
+    ctx->Clip();
+  }
 
   NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
   NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
 
   nsPoint framePt = ToReferenceFrame();
   nsTextFrame::PaintTextParams params(aCtx->ThebesContext());
   params.framePt = gfxPoint(framePt.x, framePt.y);
+
   params.dirtyRect = extraVisible;
+  nsTextFrame::DrawPathCallbacks callbacks;
+  if (aBuilder->IsForGenerateGlyphPath()) {
+    params.callbacks = &callbacks;
+  }
+
   f->PaintText(params, *this, mOpacity);
 }
 
 void
 nsTextFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists)
 {
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -382,16 +382,18 @@ public:
      */
     virtual void NotifyBeforeSelectionDecorationLine(nscolor aColor) { }
 
     /**
      * Called just after a path corresponding to a selection decoration line
      * has been emitted to the gfxContext.
      */
     virtual void NotifySelectionDecorationLinePathEmitted() { }
+
+    virtual void NotifyGlyphPathEmitted() override {}
   };
 
   struct PaintTextParams
   {
     gfxContext* context;
     gfxPoint framePt;
     LayoutDeviceRect dirtyRect;
     gfxTextContextPaint* contextPaint = nullptr;