1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsCSSRenderingBorders.h"
8 
9 #include "gfxUtils.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/Helpers.h"
13 #include "mozilla/gfx/PathHelpers.h"
14 #include "BorderConsts.h"
15 #include "DashedCornerFinder.h"
16 #include "DottedCornerFinder.h"
17 #include "ImageRegion.h"
18 #include "nsLayoutUtils.h"
19 #include "nsStyleConsts.h"
20 #include "nsContentUtils.h"
21 #include "nsCSSColorUtils.h"
22 #include "nsCSSRendering.h"
23 #include "nsCSSRenderingGradients.h"
24 #include "nsDisplayList.h"
25 #include "nsExpirationTracker.h"
26 #include "nsIScriptError.h"
27 #include "nsClassHashtable.h"
28 #include "nsPresContext.h"
29 #include "nsStyleStruct.h"
30 #include "gfx2DGlue.h"
31 #include "gfxGradientCache.h"
32 #include "mozilla/image/WebRenderImageProvider.h"
33 #include "mozilla/layers/StackingContextHelper.h"
34 #include "mozilla/layers/RenderRootStateManager.h"
35 #include "mozilla/layers/WebRenderLayerManager.h"
36 #include "mozilla/ProfilerLabels.h"
37 #include "mozilla/Range.h"
38 #include <algorithm>
39 
40 using namespace mozilla;
41 using namespace mozilla::gfx;
42 using namespace mozilla::image;
43 
44 #define MAX_COMPOSITE_BORDER_WIDTH LayoutDeviceIntCoord(10000)
45 
46 /**
47  * nsCSSRendering::PaintBorder
48  * nsCSSRendering::PaintOutline
49  *   -> DrawBorders
50  *
51  * DrawBorders
52  *   -> Ability to use specialized approach?
53  *      |- Draw using specialized function
54  *   |- separate corners?
55  *   |- dashed side mask
56  *   |
57  *   -> can border be drawn in 1 pass? (e.g., solid border same color all
58  * around)
59  *      |- DrawBorderSides with all 4 sides
60  *   -> more than 1 pass?
61  *      |- for each corner
62  *         |- clip to DoCornerClipSubPath
63  *         |- for each side adjacent to corner
64  *            |- clip to GetSideClipSubPath
65  *            |- DrawBorderSides with one side
66  *      |- for each side
67  *         |- GetSideClipWithoutCornersRect
68  *         |- DrawDashedOrDottedSide || DrawBorderSides with one side
69  */
70 
71 static void ComputeBorderCornerDimensions(const Float* aBorderWidths,
72                                           const RectCornerRadii& aRadii,
73                                           RectCornerRadii* aDimsResult);
74 
75 // given a side index, get the previous and next side index
76 #define NEXT_SIDE(_s) mozilla::Side(((_s) + 1) & 3)
77 #define PREV_SIDE(_s) mozilla::Side(((_s) + 3) & 3)
78 
79 // given a corner index, get the previous and next corner index
80 #define NEXT_CORNER(_s) Corner(((_s) + 1) & 3)
81 #define PREV_CORNER(_s) Corner(((_s) + 3) & 3)
82 
83 // from the given base color and the background color, turn
84 // color into a color for the given border pattern style
85 static sRGBColor MakeBorderColor(nscolor aColor,
86                                  BorderColorStyle aBorderColorStyle);
87 
88 // Given a line index (an index starting from the outside of the
89 // border going inwards) and an array of line styles, calculate the
90 // color that that stripe of the border should be rendered in.
91 static sRGBColor ComputeColorForLine(uint32_t aLineIndex,
92                                      const BorderColorStyle* aBorderColorStyle,
93                                      uint32_t aBorderColorStyleCount,
94                                      nscolor aBorderColor);
95 
96 // little helper function to check if the array of 4 floats given are
97 // equal to the given value
CheckFourFloatsEqual(const Float * vals,Float k)98 static bool CheckFourFloatsEqual(const Float* vals, Float k) {
99   return (vals[0] == k && vals[1] == k && vals[2] == k && vals[3] == k);
100 }
101 
IsZeroSize(const Size & sz)102 static bool IsZeroSize(const Size& sz) {
103   return sz.width == 0.0 || sz.height == 0.0;
104 }
105 
106 /* static */
AllCornersZeroSize(const RectCornerRadii & corners)107 bool nsCSSBorderRenderer::AllCornersZeroSize(const RectCornerRadii& corners) {
108   return IsZeroSize(corners[eCornerTopLeft]) &&
109          IsZeroSize(corners[eCornerTopRight]) &&
110          IsZeroSize(corners[eCornerBottomRight]) &&
111          IsZeroSize(corners[eCornerBottomLeft]);
112 }
113 
GetHorizontalSide(Corner aCorner)114 static mozilla::Side GetHorizontalSide(Corner aCorner) {
115   return (aCorner == C_TL || aCorner == C_TR) ? eSideTop : eSideBottom;
116 }
117 
GetVerticalSide(Corner aCorner)118 static mozilla::Side GetVerticalSide(Corner aCorner) {
119   return (aCorner == C_TL || aCorner == C_BL) ? eSideLeft : eSideRight;
120 }
121 
GetCWCorner(mozilla::Side aSide)122 static Corner GetCWCorner(mozilla::Side aSide) {
123   return Corner(NEXT_SIDE(aSide));
124 }
125 
GetCCWCorner(mozilla::Side aSide)126 static Corner GetCCWCorner(mozilla::Side aSide) { return Corner(aSide); }
127 
IsSingleSide(mozilla::SideBits aSides)128 static bool IsSingleSide(mozilla::SideBits aSides) {
129   return aSides == SideBits::eTop || aSides == SideBits::eRight ||
130          aSides == SideBits::eBottom || aSides == SideBits::eLeft;
131 }
132 
IsHorizontalSide(mozilla::Side aSide)133 static bool IsHorizontalSide(mozilla::Side aSide) {
134   return aSide == eSideTop || aSide == eSideBottom;
135 }
136 
137 typedef enum {
138   // Normal solid square corner.  Will be rectangular, the size of the
139   // adjacent sides.  If the corner has a border radius, the corner
140   // will always be solid, since we don't do dotted/dashed etc.
141   CORNER_NORMAL,
142 
143   // Paint the corner in whatever style is not dotted/dashed of the
144   // adjacent corners.
145   CORNER_SOLID,
146 
147   // Paint the corner as a dot, the size of the bigger of the adjacent
148   // sides.
149   CORNER_DOT
150 } CornerStyle;
151 
nsCSSBorderRenderer(nsPresContext * aPresContext,DrawTarget * aDrawTarget,const Rect & aDirtyRect,Rect & aOuterRect,const StyleBorderStyle * aBorderStyles,const Float * aBorderWidths,RectCornerRadii & aBorderRadii,const nscolor * aBorderColors,bool aBackfaceIsVisible,const Maybe<Rect> & aClipRect)152 nsCSSBorderRenderer::nsCSSBorderRenderer(
153     nsPresContext* aPresContext, DrawTarget* aDrawTarget,
154     const Rect& aDirtyRect, Rect& aOuterRect,
155     const StyleBorderStyle* aBorderStyles, const Float* aBorderWidths,
156     RectCornerRadii& aBorderRadii, const nscolor* aBorderColors,
157     bool aBackfaceIsVisible, const Maybe<Rect>& aClipRect)
158     : mPresContext(aPresContext),
159       mDrawTarget(aDrawTarget),
160       mDirtyRect(aDirtyRect),
161       mOuterRect(aOuterRect),
162       mBorderRadii(aBorderRadii),
163       mBackfaceIsVisible(aBackfaceIsVisible),
164       mLocalClip(aClipRect) {
165   PodCopy(mBorderStyles, aBorderStyles, 4);
166   PodCopy(mBorderWidths, aBorderWidths, 4);
167   PodCopy(mBorderColors, aBorderColors, 4);
168   mInnerRect = mOuterRect;
169   mInnerRect.Deflate(Margin(
170       mBorderStyles[0] != StyleBorderStyle::None ? mBorderWidths[0] : 0,
171       mBorderStyles[1] != StyleBorderStyle::None ? mBorderWidths[1] : 0,
172       mBorderStyles[2] != StyleBorderStyle::None ? mBorderWidths[2] : 0,
173       mBorderStyles[3] != StyleBorderStyle::None ? mBorderWidths[3] : 0));
174 
175   ComputeBorderCornerDimensions(mBorderWidths, mBorderRadii,
176                                 &mBorderCornerDimensions);
177 
178   mOneUnitBorder = CheckFourFloatsEqual(mBorderWidths, 1.0);
179   mNoBorderRadius = AllCornersZeroSize(mBorderRadii);
180   mAllBordersSameStyle = AreBorderSideFinalStylesSame(SideBits::eAll);
181   mAllBordersSameWidth = AllBordersSameWidth();
182   mAvoidStroke = false;
183 }
184 
185 /* static */
ComputeInnerRadii(const RectCornerRadii & aRadii,const Float * aBorderSizes,RectCornerRadii * aInnerRadiiRet)186 void nsCSSBorderRenderer::ComputeInnerRadii(const RectCornerRadii& aRadii,
187                                             const Float* aBorderSizes,
188                                             RectCornerRadii* aInnerRadiiRet) {
189   RectCornerRadii& iRadii = *aInnerRadiiRet;
190 
191   iRadii[C_TL].width =
192       std::max(0.f, aRadii[C_TL].width - aBorderSizes[eSideLeft]);
193   iRadii[C_TL].height =
194       std::max(0.f, aRadii[C_TL].height - aBorderSizes[eSideTop]);
195 
196   iRadii[C_TR].width =
197       std::max(0.f, aRadii[C_TR].width - aBorderSizes[eSideRight]);
198   iRadii[C_TR].height =
199       std::max(0.f, aRadii[C_TR].height - aBorderSizes[eSideTop]);
200 
201   iRadii[C_BR].width =
202       std::max(0.f, aRadii[C_BR].width - aBorderSizes[eSideRight]);
203   iRadii[C_BR].height =
204       std::max(0.f, aRadii[C_BR].height - aBorderSizes[eSideBottom]);
205 
206   iRadii[C_BL].width =
207       std::max(0.f, aRadii[C_BL].width - aBorderSizes[eSideLeft]);
208   iRadii[C_BL].height =
209       std::max(0.f, aRadii[C_BL].height - aBorderSizes[eSideBottom]);
210 }
211 
212 /* static */
ComputeOuterRadii(const RectCornerRadii & aRadii,const Float * aBorderSizes,RectCornerRadii * aOuterRadiiRet)213 void nsCSSBorderRenderer::ComputeOuterRadii(const RectCornerRadii& aRadii,
214                                             const Float* aBorderSizes,
215                                             RectCornerRadii* aOuterRadiiRet) {
216   RectCornerRadii& oRadii = *aOuterRadiiRet;
217 
218   // default all corners to sharp corners
219   oRadii = RectCornerRadii(0.f);
220 
221   // round the edges that have radii > 0.0 to start with
222   if (aRadii[C_TL].width > 0.f && aRadii[C_TL].height > 0.f) {
223     oRadii[C_TL].width =
224         std::max(0.f, aRadii[C_TL].width + aBorderSizes[eSideLeft]);
225     oRadii[C_TL].height =
226         std::max(0.f, aRadii[C_TL].height + aBorderSizes[eSideTop]);
227   }
228 
229   if (aRadii[C_TR].width > 0.f && aRadii[C_TR].height > 0.f) {
230     oRadii[C_TR].width =
231         std::max(0.f, aRadii[C_TR].width + aBorderSizes[eSideRight]);
232     oRadii[C_TR].height =
233         std::max(0.f, aRadii[C_TR].height + aBorderSizes[eSideTop]);
234   }
235 
236   if (aRadii[C_BR].width > 0.f && aRadii[C_BR].height > 0.f) {
237     oRadii[C_BR].width =
238         std::max(0.f, aRadii[C_BR].width + aBorderSizes[eSideRight]);
239     oRadii[C_BR].height =
240         std::max(0.f, aRadii[C_BR].height + aBorderSizes[eSideBottom]);
241   }
242 
243   if (aRadii[C_BL].width > 0.f && aRadii[C_BL].height > 0.f) {
244     oRadii[C_BL].width =
245         std::max(0.f, aRadii[C_BL].width + aBorderSizes[eSideLeft]);
246     oRadii[C_BL].height =
247         std::max(0.f, aRadii[C_BL].height + aBorderSizes[eSideBottom]);
248   }
249 }
250 
ComputeBorderCornerDimensions(const Float * aBorderWidths,const RectCornerRadii & aRadii,RectCornerRadii * aDimsRet)251 /*static*/ void ComputeBorderCornerDimensions(const Float* aBorderWidths,
252                                               const RectCornerRadii& aRadii,
253                                               RectCornerRadii* aDimsRet) {
254   Float leftWidth = aBorderWidths[eSideLeft];
255   Float topWidth = aBorderWidths[eSideTop];
256   Float rightWidth = aBorderWidths[eSideRight];
257   Float bottomWidth = aBorderWidths[eSideBottom];
258 
259   if (nsCSSBorderRenderer::AllCornersZeroSize(aRadii)) {
260     // These will always be in pixel units from CSS
261     (*aDimsRet)[C_TL] = Size(leftWidth, topWidth);
262     (*aDimsRet)[C_TR] = Size(rightWidth, topWidth);
263     (*aDimsRet)[C_BR] = Size(rightWidth, bottomWidth);
264     (*aDimsRet)[C_BL] = Size(leftWidth, bottomWidth);
265   } else {
266     // Always round up to whole pixels for the corners; it's safe to
267     // make the corners bigger than necessary, and this way we ensure
268     // that we avoid seams.
269     (*aDimsRet)[C_TL] = Size(ceil(std::max(leftWidth, aRadii[C_TL].width)),
270                              ceil(std::max(topWidth, aRadii[C_TL].height)));
271     (*aDimsRet)[C_TR] = Size(ceil(std::max(rightWidth, aRadii[C_TR].width)),
272                              ceil(std::max(topWidth, aRadii[C_TR].height)));
273     (*aDimsRet)[C_BR] = Size(ceil(std::max(rightWidth, aRadii[C_BR].width)),
274                              ceil(std::max(bottomWidth, aRadii[C_BR].height)));
275     (*aDimsRet)[C_BL] = Size(ceil(std::max(leftWidth, aRadii[C_BL].width)),
276                              ceil(std::max(bottomWidth, aRadii[C_BL].height)));
277   }
278 }
279 
AreBorderSideFinalStylesSame(mozilla::SideBits aSides)280 bool nsCSSBorderRenderer::AreBorderSideFinalStylesSame(
281     mozilla::SideBits aSides) {
282   NS_ASSERTION(aSides != SideBits::eNone &&
283                    (aSides & ~SideBits::eAll) == SideBits::eNone,
284                "AreBorderSidesSame: invalid whichSides!");
285 
286   /* First check if the specified styles and colors are the same for all sides
287    */
288   int firstStyle = 0;
289   for (const auto i : mozilla::AllPhysicalSides()) {
290     if (firstStyle == i) {
291       if ((static_cast<mozilla::SideBits>(1 << i) & aSides) ==
292           SideBits::eNone) {
293         firstStyle++;
294       }
295       continue;
296     }
297 
298     if ((static_cast<mozilla::SideBits>(1 << i) & aSides) == SideBits::eNone) {
299       continue;
300     }
301 
302     if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
303         mBorderColors[firstStyle] != mBorderColors[i]) {
304       return false;
305     }
306   }
307 
308   /* Then if it's one of the two-tone styles and we're not
309    * just comparing the TL or BR sides */
310   switch (mBorderStyles[firstStyle]) {
311     case StyleBorderStyle::Groove:
312     case StyleBorderStyle::Ridge:
313     case StyleBorderStyle::Inset:
314     case StyleBorderStyle::Outset:
315       return ((aSides & ~(SideBits::eTop | SideBits::eLeft)) ==
316                   SideBits::eNone ||
317               (aSides & ~(SideBits::eBottom | SideBits::eRight)) ==
318                   SideBits::eNone);
319     default:
320       return true;
321   }
322 }
323 
IsSolidCornerStyle(StyleBorderStyle aStyle,Corner aCorner)324 bool nsCSSBorderRenderer::IsSolidCornerStyle(StyleBorderStyle aStyle,
325                                              Corner aCorner) {
326   switch (aStyle) {
327     case StyleBorderStyle::Solid:
328       return true;
329 
330     case StyleBorderStyle::Inset:
331     case StyleBorderStyle::Outset:
332       return (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
333 
334     case StyleBorderStyle::Groove:
335     case StyleBorderStyle::Ridge:
336       return mOneUnitBorder &&
337              (aCorner == eCornerTopLeft || aCorner == eCornerBottomRight);
338 
339     case StyleBorderStyle::Double:
340       return mOneUnitBorder;
341 
342     default:
343       return false;
344   }
345 }
346 
IsCornerMergeable(Corner aCorner)347 bool nsCSSBorderRenderer::IsCornerMergeable(Corner aCorner) {
348   // Corner between dotted borders with same width and small radii is
349   // merged into single dot.
350   //
351   //  widthH / 2.0
352   // |<---------->|
353   // |            |
354   // |radius.width|
355   // |<--->|      |
356   // |     |      |
357   // |    _+------+------------+-----
358   // |  /      ###|###         |
359   // |/    #######|#######     |
360   // +   #########|#########   |
361   // |  ##########|##########  |
362   // | ###########|########### |
363   // | ###########|########### |
364   // |############|############|
365   // +------------+############|
366   // |#########################|
367   // | ####################### |
368   // | ####################### |
369   // |  #####################  |
370   // |   ###################   |
371   // |     ###############     |
372   // |         #######         |
373   // +-------------------------+----
374   // |                         |
375   // |                         |
376   mozilla::Side sideH(GetHorizontalSide(aCorner));
377   mozilla::Side sideV(GetVerticalSide(aCorner));
378   StyleBorderStyle styleH = mBorderStyles[sideH];
379   StyleBorderStyle styleV = mBorderStyles[sideV];
380   if (styleH != styleV || styleH != StyleBorderStyle::Dotted) {
381     return false;
382   }
383 
384   Float widthH = mBorderWidths[sideH];
385   Float widthV = mBorderWidths[sideV];
386   if (widthH != widthV) {
387     return false;
388   }
389 
390   Size radius = mBorderRadii[aCorner];
391   return IsZeroSize(radius) ||
392          (radius.width < widthH / 2.0f && radius.height < widthH / 2.0f);
393 }
394 
BorderColorStyleForSolidCorner(StyleBorderStyle aStyle,Corner aCorner)395 BorderColorStyle nsCSSBorderRenderer::BorderColorStyleForSolidCorner(
396     StyleBorderStyle aStyle, Corner aCorner) {
397   // note that this function assumes that the corner is already solid,
398   // as per the earlier function
399   switch (aStyle) {
400     case StyleBorderStyle::Solid:
401     case StyleBorderStyle::Double:
402       return BorderColorStyleSolid;
403 
404     case StyleBorderStyle::Inset:
405     case StyleBorderStyle::Groove:
406       if (aCorner == eCornerTopLeft) {
407         return BorderColorStyleDark;
408       } else if (aCorner == eCornerBottomRight) {
409         return BorderColorStyleLight;
410       }
411       break;
412 
413     case StyleBorderStyle::Outset:
414     case StyleBorderStyle::Ridge:
415       if (aCorner == eCornerTopLeft) {
416         return BorderColorStyleLight;
417       } else if (aCorner == eCornerBottomRight) {
418         return BorderColorStyleDark;
419       }
420       break;
421     default:
422       return BorderColorStyleNone;
423   }
424 
425   return BorderColorStyleNone;
426 }
427 
GetCornerRect(Corner aCorner)428 Rect nsCSSBorderRenderer::GetCornerRect(Corner aCorner) {
429   Point offset(0.f, 0.f);
430 
431   if (aCorner == C_TR || aCorner == C_BR)
432     offset.x = mOuterRect.Width() - mBorderCornerDimensions[aCorner].width;
433   if (aCorner == C_BR || aCorner == C_BL)
434     offset.y = mOuterRect.Height() - mBorderCornerDimensions[aCorner].height;
435 
436   return Rect(mOuterRect.TopLeft() + offset, mBorderCornerDimensions[aCorner]);
437 }
438 
GetSideClipWithoutCornersRect(mozilla::Side aSide)439 Rect nsCSSBorderRenderer::GetSideClipWithoutCornersRect(mozilla::Side aSide) {
440   Point offset(0.f, 0.f);
441 
442   // The offset from the outside rect to the start of this side's
443   // box.  For the top and bottom sides, the height of the box
444   // must be the border height; the x start must take into account
445   // the corner size (which may be bigger than the right or left
446   // side's width).  The same applies to the right and left sides.
447   if (aSide == eSideTop) {
448     offset.x = mBorderCornerDimensions[C_TL].width;
449   } else if (aSide == eSideRight) {
450     offset.x = mOuterRect.Width() - mBorderWidths[eSideRight];
451     offset.y = mBorderCornerDimensions[C_TR].height;
452   } else if (aSide == eSideBottom) {
453     offset.x = mBorderCornerDimensions[C_BL].width;
454     offset.y = mOuterRect.Height() - mBorderWidths[eSideBottom];
455   } else if (aSide == eSideLeft) {
456     offset.y = mBorderCornerDimensions[C_TL].height;
457   }
458 
459   // The sum of the width & height of the corners adjacent to the
460   // side.  This relies on the relationship between side indexing and
461   // corner indexing; that is, 0 == SIDE_TOP and 0 == CORNER_TOP_LEFT,
462   // with both proceeding clockwise.
463   Size sideCornerSum = mBorderCornerDimensions[GetCCWCorner(aSide)] +
464                        mBorderCornerDimensions[GetCWCorner(aSide)];
465   Rect rect(mOuterRect.TopLeft() + offset, mOuterRect.Size() - sideCornerSum);
466 
467   if (IsHorizontalSide(aSide))
468     rect.height = mBorderWidths[aSide];
469   else
470     rect.width = mBorderWidths[aSide];
471 
472   return rect;
473 }
474 
475 // The side border type and the adjacent border types are
476 // examined and one of the different types of clipping (listed
477 // below) is selected.
478 
479 typedef enum {
480   // clip to the trapezoid formed by the corners of the
481   // inner and outer rectangles for the given side
482   //
483   // +---------------
484   // |\%%%%%%%%%%%%%%
485   // |  \%%%%%%%%%%%%
486   // |   \%%%%%%%%%%%
487   // |     \%%%%%%%%%
488   // |      +--------
489   // |      |
490   // |      |
491   SIDE_CLIP_TRAPEZOID,
492 
493   // clip to the trapezoid formed by the outer rectangle
494   // corners and the center of the region, making sure
495   // that diagonal lines all go directly from the outside
496   // corner to the inside corner, but that they then continue on
497   // to the middle.
498   //
499   // This is needed for correctly clipping rounded borders,
500   // which might extend past the SIDE_CLIP_TRAPEZOID trap.
501   //
502   // +-------__--+---
503   //  \%%%%_-%%%%%%%%
504   //    \+-%%%%%%%%%%
505   //    / \%%%%%%%%%%
506   //   /   \%%%%%%%%%
507   //  |     +%%_-+---
508   // |        +%%%%%%
509   // |       / \%%%%%
510   // +      +    \%%%
511   // |      |      +-
512   SIDE_CLIP_TRAPEZOID_FULL,
513 
514   // clip to the rectangle formed by the given side including corner.
515   // This is used by the non-dotted side next to dotted side.
516   //
517   // +---------------
518   // |%%%%%%%%%%%%%%%
519   // |%%%%%%%%%%%%%%%
520   // |%%%%%%%%%%%%%%%
521   // |%%%%%%%%%%%%%%%
522   // +------+--------
523   // |      |
524   // |      |
525   SIDE_CLIP_RECTANGLE_CORNER,
526 
527   // clip to the rectangle formed by the given side excluding corner.
528   // This is used by the dotted side next to non-dotted side.
529   //
530   // +------+--------
531   // |      |%%%%%%%%
532   // |      |%%%%%%%%
533   // |      |%%%%%%%%
534   // |      |%%%%%%%%
535   // |      +--------
536   // |      |
537   // |      |
538   SIDE_CLIP_RECTANGLE_NO_CORNER,
539 } SideClipType;
540 
541 // Given three points, p0, p1, and midPoint, move p1 further in to the
542 // rectangle (of which aMidPoint is the center) so that it reaches the
543 // closer of the horizontal or vertical lines intersecting the midpoint,
544 // while maintaing the slope of the line.  If p0 and p1 are the same,
545 // just move p1 to midPoint (since there's no slope to maintain).
546 // FIXME: Extending only to the midpoint isn't actually sufficient for
547 // boxes with asymmetric radii.
MaybeMoveToMidPoint(Point & aP0,Point & aP1,const Point & aMidPoint)548 static void MaybeMoveToMidPoint(Point& aP0, Point& aP1,
549                                 const Point& aMidPoint) {
550   Point ps = aP1 - aP0;
551 
552   if (ps.x == 0.0) {
553     if (ps.y == 0.0) {
554       aP1 = aMidPoint;
555     } else {
556       aP1.y = aMidPoint.y;
557     }
558   } else {
559     if (ps.y == 0.0) {
560       aP1.x = aMidPoint.x;
561     } else {
562       Float k =
563           std::min((aMidPoint.x - aP0.x) / ps.x, (aMidPoint.y - aP0.y) / ps.y);
564       aP1 = aP0 + ps * k;
565     }
566   }
567 }
568 
GetSideClipSubPath(mozilla::Side aSide)569 already_AddRefed<Path> nsCSSBorderRenderer::GetSideClipSubPath(
570     mozilla::Side aSide) {
571   // the clip proceeds clockwise from the top left corner;
572   // so "start" in each case is the start of the region from that side.
573   //
574   // the final path will be formed like:
575   // s0 ------- e0
576   // |         /
577   // s1 ----- e1
578   //
579   // that is, the second point will always be on the inside
580 
581   Point start[2];
582   Point end[2];
583 
584 #define IS_DOTTED(_s) ((_s) == StyleBorderStyle::Dotted)
585   bool isDotted = IS_DOTTED(mBorderStyles[aSide]);
586   bool startIsDotted = IS_DOTTED(mBorderStyles[PREV_SIDE(aSide)]);
587   bool endIsDotted = IS_DOTTED(mBorderStyles[NEXT_SIDE(aSide)]);
588 #undef IS_DOTTED
589 
590   SideClipType startType = SIDE_CLIP_TRAPEZOID;
591   SideClipType endType = SIDE_CLIP_TRAPEZOID;
592 
593   if (!IsZeroSize(mBorderRadii[GetCCWCorner(aSide)])) {
594     startType = SIDE_CLIP_TRAPEZOID_FULL;
595   } else if (startIsDotted && !isDotted) {
596     startType = SIDE_CLIP_RECTANGLE_CORNER;
597   } else if (!startIsDotted && isDotted) {
598     startType = SIDE_CLIP_RECTANGLE_NO_CORNER;
599   }
600 
601   if (!IsZeroSize(mBorderRadii[GetCWCorner(aSide)])) {
602     endType = SIDE_CLIP_TRAPEZOID_FULL;
603   } else if (endIsDotted && !isDotted) {
604     endType = SIDE_CLIP_RECTANGLE_CORNER;
605   } else if (!endIsDotted && isDotted) {
606     endType = SIDE_CLIP_RECTANGLE_NO_CORNER;
607   }
608 
609   Point midPoint = mInnerRect.Center();
610 
611   start[0] = mOuterRect.CCWCorner(aSide);
612   start[1] = mInnerRect.CCWCorner(aSide);
613 
614   end[0] = mOuterRect.CWCorner(aSide);
615   end[1] = mInnerRect.CWCorner(aSide);
616 
617   if (startType == SIDE_CLIP_TRAPEZOID_FULL) {
618     MaybeMoveToMidPoint(start[0], start[1], midPoint);
619   } else if (startType == SIDE_CLIP_RECTANGLE_CORNER) {
620     if (IsHorizontalSide(aSide)) {
621       start[1] =
622           Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
623     } else {
624       start[1] =
625           Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
626     }
627   } else if (startType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
628     if (IsHorizontalSide(aSide)) {
629       start[0] =
630           Point(mInnerRect.CCWCorner(aSide).x, mOuterRect.CCWCorner(aSide).y);
631     } else {
632       start[0] =
633           Point(mOuterRect.CCWCorner(aSide).x, mInnerRect.CCWCorner(aSide).y);
634     }
635   }
636 
637   if (endType == SIDE_CLIP_TRAPEZOID_FULL) {
638     MaybeMoveToMidPoint(end[0], end[1], midPoint);
639   } else if (endType == SIDE_CLIP_RECTANGLE_CORNER) {
640     if (IsHorizontalSide(aSide)) {
641       end[1] =
642           Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
643     } else {
644       end[1] =
645           Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
646     }
647   } else if (endType == SIDE_CLIP_RECTANGLE_NO_CORNER) {
648     if (IsHorizontalSide(aSide)) {
649       end[0] =
650           Point(mInnerRect.CWCorner(aSide).x, mOuterRect.CWCorner(aSide).y);
651     } else {
652       end[0] =
653           Point(mOuterRect.CWCorner(aSide).x, mInnerRect.CWCorner(aSide).y);
654     }
655   }
656 
657   RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
658   builder->MoveTo(start[0]);
659   builder->LineTo(end[0]);
660   builder->LineTo(end[1]);
661   builder->LineTo(start[1]);
662   builder->Close();
663   return builder->Finish();
664 }
665 
GetStraightBorderPoint(mozilla::Side aSide,Corner aCorner,bool * aIsUnfilled,Float aDotOffset)666 Point nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide,
667                                                   Corner aCorner,
668                                                   bool* aIsUnfilled,
669                                                   Float aDotOffset) {
670   // Calculate the end point of the side for dashed/dotted border, that is also
671   // the end point of the corner curve.  The point is specified by aSide and
672   // aCorner. (e.g. eSideTop and C_TL means the left end of border-top)
673   //
674   //
675   //  aCorner        aSide
676   //         +--------------------
677   //         |
678   //         |
679   //         |         +----------
680   //         |    the end point
681   //         |
682   //         |         +----------
683   //         |         |
684   //         |         |
685   //         |         |
686   //
687   // The position of the point depends on the border-style, border-width, and
688   // border-radius of the side, corner, and the adjacent side beyond the corner,
689   // to make those sides (and corner) interact well.
690   //
691   // If the style of aSide is dotted and the dot at the point should be
692   // unfilled, true is stored to *aIsUnfilled, otherwise false is stored.
693 
694   const Float signsList[4][2] = {
695       {+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}};
696   const Float(&signs)[2] = signsList[aCorner];
697 
698   *aIsUnfilled = false;
699 
700   Point P = mOuterRect.AtCorner(aCorner);
701   StyleBorderStyle style = mBorderStyles[aSide];
702   Float borderWidth = mBorderWidths[aSide];
703   Size dim = mBorderCornerDimensions[aCorner];
704   bool isHorizontal = IsHorizontalSide(aSide);
705   //
706   //    aCorner      aSide
707   //           +--------------
708   //           |
709   //           |   +----------
710   //           |   |
711   // otherSide |   |
712   //           |   |
713   mozilla::Side otherSide = ((uint8_t)aSide == (uint8_t)aCorner)
714                                 ? PREV_SIDE(aSide)
715                                 : NEXT_SIDE(aSide);
716   StyleBorderStyle otherStyle = mBorderStyles[otherSide];
717   Float otherBorderWidth = mBorderWidths[otherSide];
718   Size radius = mBorderRadii[aCorner];
719   if (IsZeroSize(radius)) {
720     radius.width = 0.0f;
721     radius.height = 0.0f;
722   }
723   if (style == StyleBorderStyle::Dotted) {
724     // Offset the dot's location along the side toward the corner by a
725     // multiple of its width.
726     if (isHorizontal) {
727       P.x -= signs[0] * aDotOffset * borderWidth;
728     } else {
729       P.y -= signs[1] * aDotOffset * borderWidth;
730     }
731   }
732   if (style == StyleBorderStyle::Dotted &&
733       otherStyle == StyleBorderStyle::Dotted) {
734     if (borderWidth == otherBorderWidth) {
735       if (radius.width < borderWidth / 2.0f &&
736           radius.height < borderWidth / 2.0f) {
737         // Two dots are merged into one and placed at the corner.
738         //
739         //  borderWidth / 2.0
740         // |<---------->|
741         // |            |
742         // |radius.width|
743         // |<--->|      |
744         // |     |      |
745         // |    _+------+------------+-----
746         // |  /      ###|###         |
747         // |/    #######|#######     |
748         // +   #########|#########   |
749         // |  ##########|##########  |
750         // | ###########|########### |
751         // | ###########|########### |
752         // |############|############|
753         // +------------+############|
754         // |########### P ###########|
755         // | ####################### |
756         // | ####################### |
757         // |  #####################  |
758         // |   ###################   |
759         // |     ###############     |
760         // |         #######         |
761         // +-------------------------+----
762         // |                         |
763         // |                         |
764         P.x += signs[0] * borderWidth / 2.0f;
765         P.y += signs[1] * borderWidth / 2.0f;
766       } else {
767         // Two dots are drawn separately.
768         //
769         //    borderWidth * 1.5
770         //   |<------------>|
771         //   |              |
772         //   |radius.width  |
773         //   |<----->|      |
774         //   |       |      |
775         //   |    _--+-+----+---
776         //   |  _-     |  ##|##
777         //   | /       | ###|###
778         //   |/        |####|####
779         //   |         |####+####
780         //   |         |### P ###
781         //   +         | ###|###
782         //   |         |  ##|##
783         //   +---------+----+---
784         //   |  #####  |
785         //   | ####### |
786         //   |#########|
787         //   +----+----+
788         //   |#########|
789         //   | ####### |
790         //   |  #####  |
791         //   |         |
792         //
793         // There should be enough gap between 2 dots even if radius.width is
794         // small but larger than borderWidth / 2.0.  borderWidth * 1.5 is the
795         // value that there's imaginally unfilled dot at the corner.  The
796         // unfilled dot may overflow from the outer curve, but filled dots
797         // doesn't, so this could be acceptable solution at least for now.
798         // We may have to find better model/value.
799         //
800         //    imaginally unfilled dot at the corner
801         //        |
802         //        v    +----+---
803         //      *****  |  ##|##
804         //     ******* | ###|###
805         //    *********|####|####
806         //    *********|####+####
807         //    *********|### P ###
808         //     ******* | ###|###
809         //      *****  |  ##|##
810         //   +---------+----+---
811         //   |  #####  |
812         //   | ####### |
813         //   |#########|
814         //   +----+----+
815         //   |#########|
816         //   | ####### |
817         //   |  #####  |
818         //   |         |
819         Float minimum = borderWidth * 1.5f;
820         if (isHorizontal) {
821           P.x += signs[0] * std::max(radius.width, minimum);
822           P.y += signs[1] * borderWidth / 2.0f;
823         } else {
824           P.x += signs[0] * borderWidth / 2.0f;
825           P.y += signs[1] * std::max(radius.height, minimum);
826         }
827       }
828 
829       return P;
830     }
831 
832     if (borderWidth < otherBorderWidth) {
833       // This side is smaller than other side, other side draws the corner.
834       //
835       //  otherBorderWidth + borderWidth / 2.0
836       // |<---------->|
837       // |            |
838       // +---------+--+--------
839       // |  #####  | *|*  ###
840       // | ####### |**|**#####
841       // |#########|**+**##+##
842       // |####+####|* P *#####
843       // |#########| ***  ###
844       // | ####### +-----------
845       // |  #####  |  ^
846       // |         |  |
847       // |         | first dot is not filled
848       // |         |
849       //
850       //      radius.width
851       // |<----------------->|
852       // |                   |
853       // |             ___---+-------------
854       // |         __--     #|#       ###
855       // |       _-        ##|##     #####
856       // |     /           ##+##     ##+##
857       // |   /             # P #     #####
858       // |  |               #|#       ###
859       // | |             __--+-------------
860       // ||            _-    ^
861       // ||           /      |
862       // |           /      first dot is filled
863       // |          |
864       // |          |
865       // |  #####  |
866       // | ####### |
867       // |#########|
868       // +----+----+
869       // |#########|
870       // | ####### |
871       // |  #####  |
872       Float minimum = otherBorderWidth + borderWidth / 2.0f;
873       if (isHorizontal) {
874         if (radius.width < minimum) {
875           *aIsUnfilled = true;
876           P.x += signs[0] * minimum;
877         } else {
878           P.x += signs[0] * radius.width;
879         }
880         P.y += signs[1] * borderWidth / 2.0f;
881       } else {
882         P.x += signs[0] * borderWidth / 2.0f;
883         if (radius.height < minimum) {
884           *aIsUnfilled = true;
885           P.y += signs[1] * minimum;
886         } else {
887           P.y += signs[1] * radius.height;
888         }
889       }
890 
891       return P;
892     }
893 
894     // This side is larger than other side, this side draws the corner.
895     //
896     //  borderWidth / 2.0
897     // |<-->|
898     // |    |
899     // +----+---------------------
900     // |  ##|##           #####
901     // | ###|###         #######
902     // |####|####       #########
903     // |####+####       ####+####
904     // |### P ###       #########
905     // | #######         #######
906     // |  #####           #####
907     // +-----+---------------------
908     // | *** |
909     // |*****|
910     // |**+**| <-- first dot in other side is not filled
911     // |*****|
912     // | *** |
913     // | ### |
914     // |#####|
915     // |##+##|
916     // |#####|
917     // | ### |
918     // |     |
919     if (isHorizontal) {
920       P.x += signs[0] * std::max(radius.width, borderWidth / 2.0f);
921       P.y += signs[1] * borderWidth / 2.0f;
922     } else {
923       P.x += signs[0] * borderWidth / 2.0f;
924       P.y += signs[1] * std::max(radius.height, borderWidth / 2.0f);
925     }
926     return P;
927   }
928 
929   if (style == StyleBorderStyle::Dotted) {
930     // If only this side is dotted, other side draws the corner.
931     //
932     //  otherBorderWidth + borderWidth / 2.0
933     // |<------->|
934     // |         |
935     // +------+--+--------
936     // |##  ##| *|*  ###
937     // |##  ##|**|**#####
938     // |##  ##|**+**##+##
939     // |##  ##|* P *#####
940     // |##  ##| ***  ###
941     // |##  ##+-----------
942     // |##  ##|  ^
943     // |##  ##|  |
944     // |##  ##| first dot is not filled
945     // |##  ##|
946     //
947     //      radius.width
948     // |<----------------->|
949     // |                   |
950     // |             ___---+-------------
951     // |         __--     #|#       ###
952     // |       _-        ##|##     #####
953     // |     /           ##+##     ##+##
954     // |   /             # P #     #####
955     // |  |               #|#       ###
956     // | |             __--+-------------
957     // ||            _-    ^
958     // ||          /       |
959     // |         /        first dot is filled
960     // |        |
961     // |       |
962     // |      |
963     // |      |
964     // |      |
965     // +------+
966     // |##  ##|
967     // |##  ##|
968     // |##  ##|
969     Float minimum = otherBorderWidth + borderWidth / 2.0f;
970     if (isHorizontal) {
971       if (radius.width < minimum) {
972         *aIsUnfilled = true;
973         P.x += signs[0] * minimum;
974       } else {
975         P.x += signs[0] * radius.width;
976       }
977       P.y += signs[1] * borderWidth / 2.0f;
978     } else {
979       P.x += signs[0] * borderWidth / 2.0f;
980       if (radius.height < minimum) {
981         *aIsUnfilled = true;
982         P.y += signs[1] * minimum;
983       } else {
984         P.y += signs[1] * radius.height;
985       }
986     }
987     return P;
988   }
989 
990   if (otherStyle == StyleBorderStyle::Dotted && IsZeroSize(radius)) {
991     // If other side is dotted and radius=0, draw side to the end of corner.
992     //
993     //   +-------------------------------
994     //   |##########          ##########
995     // P +##########          ##########
996     //   |##########          ##########
997     //   +-----+-------------------------
998     //   | *** |
999     //   |*****|
1000     //   |**+**| <-- first dot in other side is not filled
1001     //   |*****|
1002     //   | *** |
1003     //   | ### |
1004     //   |#####|
1005     //   |##+##|
1006     //   |#####|
1007     //   | ### |
1008     //   |     |
1009     if (isHorizontal) {
1010       P.y += signs[1] * borderWidth / 2.0f;
1011     } else {
1012       P.x += signs[0] * borderWidth / 2.0f;
1013     }
1014     return P;
1015   }
1016 
1017   // Other cases.
1018   //
1019   //  dim.width
1020   // |<----------------->|
1021   // |                   |
1022   // |             ___---+------------------
1023   // |         __--      |#######        ###
1024   // |       _-        P +#######        ###
1025   // |     /             |#######        ###
1026   // |   /          __---+------------------
1027   // |  |       __--
1028   // | |       /
1029   // ||      /
1030   // ||     |
1031   // |     |
1032   // |    |
1033   // |   |
1034   // |   |
1035   // +-+-+
1036   // |###|
1037   // |###|
1038   // |###|
1039   // |###|
1040   // |###|
1041   // |   |
1042   // |   |
1043   if (isHorizontal) {
1044     P.x += signs[0] * dim.width;
1045     P.y += signs[1] * borderWidth / 2.0f;
1046   } else {
1047     P.x += signs[0] * borderWidth / 2.0f;
1048     P.y += signs[1] * dim.height;
1049   }
1050 
1051   return P;
1052 }
1053 
GetOuterAndInnerBezier(Bezier * aOuterBezier,Bezier * aInnerBezier,Corner aCorner)1054 void nsCSSBorderRenderer::GetOuterAndInnerBezier(Bezier* aOuterBezier,
1055                                                  Bezier* aInnerBezier,
1056                                                  Corner aCorner) {
1057   // Return bezier control points for outer and inner curve for given corner.
1058   //
1059   //               ___---+ outer curve
1060   //           __--      |
1061   //         _-          |
1062   //       /             |
1063   //     /               |
1064   //    |                |
1065   //   |             __--+ inner curve
1066   //  |            _-
1067   //  |           /
1068   // |           /
1069   // |          |
1070   // |          |
1071   // |         |
1072   // |         |
1073   // |         |
1074   // +---------+
1075 
1076   mozilla::Side sideH(GetHorizontalSide(aCorner));
1077   mozilla::Side sideV(GetVerticalSide(aCorner));
1078 
1079   Size outerCornerSize(ceil(mBorderRadii[aCorner].width),
1080                        ceil(mBorderRadii[aCorner].height));
1081   Size innerCornerSize(
1082       ceil(std::max(0.0f, mBorderRadii[aCorner].width - mBorderWidths[sideV])),
1083       ceil(
1084           std::max(0.0f, mBorderRadii[aCorner].height - mBorderWidths[sideH])));
1085 
1086   GetBezierPointsForCorner(aOuterBezier, aCorner, mOuterRect.AtCorner(aCorner),
1087                            outerCornerSize);
1088 
1089   GetBezierPointsForCorner(aInnerBezier, aCorner, mInnerRect.AtCorner(aCorner),
1090                            innerCornerSize);
1091 }
1092 
FillSolidBorder(const Rect & aOuterRect,const Rect & aInnerRect,const RectCornerRadii & aBorderRadii,const Float * aBorderSizes,SideBits aSides,const ColorPattern & aColor)1093 void nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect,
1094                                           const Rect& aInnerRect,
1095                                           const RectCornerRadii& aBorderRadii,
1096                                           const Float* aBorderSizes,
1097                                           SideBits aSides,
1098                                           const ColorPattern& aColor) {
1099   // Note that this function is allowed to draw more than just the
1100   // requested sides.
1101 
1102   // If we have a border radius, do full rounded rectangles
1103   // and fill, regardless of what sides we're asked to draw.
1104   if (!AllCornersZeroSize(aBorderRadii)) {
1105     RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
1106 
1107     RectCornerRadii innerRadii;
1108     ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii);
1109 
1110     // do the outer border
1111     AppendRoundedRectToPath(builder, aOuterRect, aBorderRadii, true);
1112 
1113     // then do the inner border CCW
1114     AppendRoundedRectToPath(builder, aInnerRect, innerRadii, false);
1115 
1116     RefPtr<Path> path = builder->Finish();
1117 
1118     mDrawTarget->Fill(path, aColor);
1119     return;
1120   }
1121 
1122   // If we're asked to draw all sides of an equal-sized border,
1123   // stroking is fastest.  This is a fairly common path, but partial
1124   // sides is probably second in the list -- there are a bunch of
1125   // common border styles, such as inset and outset, that are
1126   // top-left/bottom-right split.
1127   if (aSides == SideBits::eAll &&
1128       CheckFourFloatsEqual(aBorderSizes, aBorderSizes[0]) && !mAvoidStroke) {
1129     Float strokeWidth = aBorderSizes[0];
1130     Rect r(aOuterRect);
1131     r.Deflate(strokeWidth / 2.f);
1132     mDrawTarget->StrokeRect(r, aColor, StrokeOptions(strokeWidth));
1133     return;
1134   }
1135 
1136   // Otherwise, we have unequal sized borders or we're only
1137   // drawing some sides; create rectangles for each side
1138   // and fill them.
1139 
1140   Rect r[4];
1141 
1142   // compute base rects for each side
1143   if (aSides & SideBits::eTop) {
1144     r[eSideTop] = Rect(aOuterRect.X(), aOuterRect.Y(), aOuterRect.Width(),
1145                        aBorderSizes[eSideTop]);
1146   }
1147 
1148   if (aSides & SideBits::eBottom) {
1149     r[eSideBottom] =
1150         Rect(aOuterRect.X(), aOuterRect.YMost() - aBorderSizes[eSideBottom],
1151              aOuterRect.Width(), aBorderSizes[eSideBottom]);
1152   }
1153 
1154   if (aSides & SideBits::eLeft) {
1155     r[eSideLeft] = Rect(aOuterRect.X(), aOuterRect.Y(), aBorderSizes[eSideLeft],
1156                         aOuterRect.Height());
1157   }
1158 
1159   if (aSides & SideBits::eRight) {
1160     r[eSideRight] =
1161         Rect(aOuterRect.XMost() - aBorderSizes[eSideRight], aOuterRect.Y(),
1162              aBorderSizes[eSideRight], aOuterRect.Height());
1163   }
1164 
1165   // If two sides meet at a corner that we're rendering, then
1166   // make sure that we adjust one of the sides to avoid overlap.
1167   // This is especially important in the case of colors with
1168   // an alpha channel.
1169 
1170   if ((aSides & (SideBits::eTop | SideBits::eLeft)) ==
1171       (SideBits::eTop | SideBits::eLeft)) {
1172     // adjust the left's top down a bit
1173     r[eSideLeft].y += aBorderSizes[eSideTop];
1174     r[eSideLeft].height -= aBorderSizes[eSideTop];
1175   }
1176 
1177   if ((aSides & (SideBits::eTop | SideBits::eRight)) ==
1178       (SideBits::eTop | SideBits::eRight)) {
1179     // adjust the top's left a bit
1180     r[eSideTop].width -= aBorderSizes[eSideRight];
1181   }
1182 
1183   if ((aSides & (SideBits::eBottom | SideBits::eRight)) ==
1184       (SideBits::eBottom | SideBits::eRight)) {
1185     // adjust the right's bottom a bit
1186     r[eSideRight].height -= aBorderSizes[eSideBottom];
1187   }
1188 
1189   if ((aSides & (SideBits::eBottom | SideBits::eLeft)) ==
1190       (SideBits::eBottom | SideBits::eLeft)) {
1191     // adjust the bottom's left a bit
1192     r[eSideBottom].x += aBorderSizes[eSideLeft];
1193     r[eSideBottom].width -= aBorderSizes[eSideLeft];
1194   }
1195 
1196   // Filling these one by one is faster than filling them all at once.
1197   for (uint32_t i = 0; i < 4; i++) {
1198     if (aSides & static_cast<mozilla::SideBits>(1 << i)) {
1199       MaybeSnapToDevicePixels(r[i], *mDrawTarget, true);
1200       mDrawTarget->FillRect(r[i], aColor);
1201     }
1202   }
1203 }
1204 
MakeBorderColor(nscolor aColor,BorderColorStyle aBorderColorStyle)1205 sRGBColor MakeBorderColor(nscolor aColor, BorderColorStyle aBorderColorStyle) {
1206   nscolor colors[2];
1207   int k = 0;
1208 
1209   switch (aBorderColorStyle) {
1210     case BorderColorStyleNone:
1211       return sRGBColor(0.f, 0.f, 0.f, 0.f);  // transparent black
1212 
1213     case BorderColorStyleLight:
1214       k = 1;
1215       [[fallthrough]];
1216     case BorderColorStyleDark:
1217       NS_GetSpecial3DColors(colors, aColor);
1218       return sRGBColor::FromABGR(colors[k]);
1219 
1220     case BorderColorStyleSolid:
1221     default:
1222       return sRGBColor::FromABGR(aColor);
1223   }
1224 }
1225 
ComputeColorForLine(uint32_t aLineIndex,const BorderColorStyle * aBorderColorStyle,uint32_t aBorderColorStyleCount,nscolor aBorderColor)1226 sRGBColor ComputeColorForLine(uint32_t aLineIndex,
1227                               const BorderColorStyle* aBorderColorStyle,
1228                               uint32_t aBorderColorStyleCount,
1229                               nscolor aBorderColor) {
1230   NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
1231 
1232   return MakeBorderColor(aBorderColor, aBorderColorStyle[aLineIndex]);
1233 }
1234 
DrawBorderSides(mozilla::SideBits aSides)1235 void nsCSSBorderRenderer::DrawBorderSides(mozilla::SideBits aSides) {
1236   if (aSides == SideBits::eNone ||
1237       (aSides & ~SideBits::eAll) != SideBits::eNone) {
1238     NS_WARNING("DrawBorderSides: invalid sides!");
1239     return;
1240   }
1241 
1242   StyleBorderStyle borderRenderStyle = StyleBorderStyle::None;
1243   nscolor borderRenderColor;
1244 
1245   uint32_t borderColorStyleCount = 0;
1246   BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
1247   BorderColorStyle* borderColorStyle = nullptr;
1248 
1249   for (const auto i : mozilla::AllPhysicalSides()) {
1250     if ((aSides & static_cast<mozilla::SideBits>(1 << i)) == SideBits::eNone) {
1251       continue;
1252     }
1253     borderRenderStyle = mBorderStyles[i];
1254     borderRenderColor = mBorderColors[i];
1255     break;
1256   }
1257 
1258   if (borderRenderStyle == StyleBorderStyle::None ||
1259       borderRenderStyle == StyleBorderStyle::Hidden) {
1260     return;
1261   }
1262 
1263   if (borderRenderStyle == StyleBorderStyle::Dashed ||
1264       borderRenderStyle == StyleBorderStyle::Dotted) {
1265     // Draw each corner separately, with the given side's color.
1266     if (aSides & SideBits::eTop) {
1267       DrawDashedOrDottedCorner(eSideTop, C_TL);
1268     } else if (aSides & SideBits::eLeft) {
1269       DrawDashedOrDottedCorner(eSideLeft, C_TL);
1270     }
1271 
1272     if (aSides & SideBits::eTop) {
1273       DrawDashedOrDottedCorner(eSideTop, C_TR);
1274     } else if (aSides & SideBits::eRight) {
1275       DrawDashedOrDottedCorner(eSideRight, C_TR);
1276     }
1277 
1278     if (aSides & SideBits::eBottom) {
1279       DrawDashedOrDottedCorner(eSideBottom, C_BL);
1280     } else if (aSides & SideBits::eLeft) {
1281       DrawDashedOrDottedCorner(eSideLeft, C_BL);
1282     }
1283 
1284     if (aSides & SideBits::eBottom) {
1285       DrawDashedOrDottedCorner(eSideBottom, C_BR);
1286     } else if (aSides & SideBits::eRight) {
1287       DrawDashedOrDottedCorner(eSideRight, C_BR);
1288     }
1289     return;
1290   }
1291 
1292   // The borderColorStyle array goes from the outer to the inner style.
1293   //
1294   // If the border width is 1, we need to change the borderRenderStyle
1295   // a bit to make sure that we get the right colors -- e.g. 'ridge'
1296   // with a 1px border needs to look like solid, not like 'outset'.
1297   if (mOneUnitBorder && (borderRenderStyle == StyleBorderStyle::Ridge ||
1298                          borderRenderStyle == StyleBorderStyle::Groove ||
1299                          borderRenderStyle == StyleBorderStyle::Double)) {
1300     borderRenderStyle = StyleBorderStyle::Solid;
1301   }
1302 
1303   switch (borderRenderStyle) {
1304     case StyleBorderStyle::Solid:
1305       borderColorStyleTopLeft[0] = BorderColorStyleSolid;
1306 
1307       borderColorStyleBottomRight[0] = BorderColorStyleSolid;
1308 
1309       borderColorStyleCount = 1;
1310       break;
1311 
1312     case StyleBorderStyle::Groove:
1313       borderColorStyleTopLeft[0] = BorderColorStyleDark;
1314       borderColorStyleTopLeft[1] = BorderColorStyleLight;
1315 
1316       borderColorStyleBottomRight[0] = BorderColorStyleLight;
1317       borderColorStyleBottomRight[1] = BorderColorStyleDark;
1318 
1319       borderColorStyleCount = 2;
1320       break;
1321 
1322     case StyleBorderStyle::Ridge:
1323       borderColorStyleTopLeft[0] = BorderColorStyleLight;
1324       borderColorStyleTopLeft[1] = BorderColorStyleDark;
1325 
1326       borderColorStyleBottomRight[0] = BorderColorStyleDark;
1327       borderColorStyleBottomRight[1] = BorderColorStyleLight;
1328 
1329       borderColorStyleCount = 2;
1330       break;
1331 
1332     case StyleBorderStyle::Double:
1333       borderColorStyleTopLeft[0] = BorderColorStyleSolid;
1334       borderColorStyleTopLeft[1] = BorderColorStyleNone;
1335       borderColorStyleTopLeft[2] = BorderColorStyleSolid;
1336 
1337       borderColorStyleBottomRight[0] = BorderColorStyleSolid;
1338       borderColorStyleBottomRight[1] = BorderColorStyleNone;
1339       borderColorStyleBottomRight[2] = BorderColorStyleSolid;
1340 
1341       borderColorStyleCount = 3;
1342       break;
1343 
1344     case StyleBorderStyle::Inset:
1345       borderColorStyleTopLeft[0] = BorderColorStyleDark;
1346       borderColorStyleBottomRight[0] = BorderColorStyleLight;
1347 
1348       borderColorStyleCount = 1;
1349       break;
1350 
1351     case StyleBorderStyle::Outset:
1352       borderColorStyleTopLeft[0] = BorderColorStyleLight;
1353       borderColorStyleBottomRight[0] = BorderColorStyleDark;
1354 
1355       borderColorStyleCount = 1;
1356       break;
1357 
1358     default:
1359       MOZ_ASSERT_UNREACHABLE("Unhandled border style!!");
1360       break;
1361   }
1362 
1363   // The only way to get to here is by having a
1364   // borderColorStyleCount < 1 or > 3; this should never happen,
1365   // since -moz-border-colors doesn't get handled here.
1366   NS_ASSERTION(borderColorStyleCount > 0 && borderColorStyleCount < 4,
1367                "Non-border-colors case with borderColorStyleCount < 1 or > 3; "
1368                "what happened?");
1369 
1370   // The caller should never give us anything with a mix
1371   // of TL/BR if the border style would require a
1372   // TL/BR split.
1373   if (aSides & (SideBits::eBottom | SideBits::eRight)) {
1374     borderColorStyle = borderColorStyleBottomRight;
1375   } else {
1376     borderColorStyle = borderColorStyleTopLeft;
1377   }
1378 
1379   // Distribute the border across the available space.
1380   Float borderWidths[3][4];
1381 
1382   if (borderColorStyleCount == 1) {
1383     for (const auto i : mozilla::AllPhysicalSides()) {
1384       borderWidths[0][i] = mBorderWidths[i];
1385     }
1386   } else if (borderColorStyleCount == 2) {
1387     // with 2 color styles, any extra pixel goes to the outside
1388     for (const auto i : mozilla::AllPhysicalSides()) {
1389       borderWidths[0][i] =
1390           int32_t(mBorderWidths[i]) / 2 + int32_t(mBorderWidths[i]) % 2;
1391       borderWidths[1][i] = int32_t(mBorderWidths[i]) / 2;
1392     }
1393   } else if (borderColorStyleCount == 3) {
1394     // with 3 color styles, any extra pixel (or lack of extra pixel)
1395     // goes to the middle
1396     for (const auto i : mozilla::AllPhysicalSides()) {
1397       if (mBorderWidths[i] == 1.0) {
1398         borderWidths[0][i] = 1.f;
1399         borderWidths[1][i] = borderWidths[2][i] = 0.f;
1400       } else {
1401         int32_t rest = int32_t(mBorderWidths[i]) % 3;
1402         borderWidths[0][i] = borderWidths[2][i] = borderWidths[1][i] =
1403             (int32_t(mBorderWidths[i]) - rest) / 3;
1404 
1405         if (rest == 1) {
1406           borderWidths[1][i] += 1.f;
1407         } else if (rest == 2) {
1408           borderWidths[0][i] += 1.f;
1409           borderWidths[2][i] += 1.f;
1410         }
1411       }
1412     }
1413   }
1414 
1415   // make a copy that we can modify
1416   RectCornerRadii radii = mBorderRadii;
1417 
1418   Rect soRect(mOuterRect);
1419   Rect siRect(mOuterRect);
1420 
1421   // If adjacent side is dotted and radius=0, draw side to the end of corner.
1422   //
1423   // +--------------------------------
1424   // |################################
1425   // |
1426   // |################################
1427   // +-----+--------------------------
1428   // |     |
1429   // |     |
1430   // |     |
1431   // |     |
1432   // |     |
1433   // | ### |
1434   // |#####|
1435   // |#####|
1436   // |#####|
1437   // | ### |
1438   // |     |
1439   bool noMarginTop = false;
1440   bool noMarginRight = false;
1441   bool noMarginBottom = false;
1442   bool noMarginLeft = false;
1443 
1444   // If there is at least one dotted side, every side is rendered separately.
1445   if (IsSingleSide(aSides)) {
1446     if (aSides == SideBits::eTop) {
1447       if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
1448           IsZeroSize(mBorderRadii[C_TR])) {
1449         noMarginRight = true;
1450       }
1451       if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
1452           IsZeroSize(mBorderRadii[C_TL])) {
1453         noMarginLeft = true;
1454       }
1455     } else if (aSides == SideBits::eRight) {
1456       if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
1457           IsZeroSize(mBorderRadii[C_TR])) {
1458         noMarginTop = true;
1459       }
1460       if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
1461           IsZeroSize(mBorderRadii[C_BR])) {
1462         noMarginBottom = true;
1463       }
1464     } else if (aSides == SideBits::eBottom) {
1465       if (mBorderStyles[eSideRight] == StyleBorderStyle::Dotted &&
1466           IsZeroSize(mBorderRadii[C_BR])) {
1467         noMarginRight = true;
1468       }
1469       if (mBorderStyles[eSideLeft] == StyleBorderStyle::Dotted &&
1470           IsZeroSize(mBorderRadii[C_BL])) {
1471         noMarginLeft = true;
1472       }
1473     } else {
1474       if (mBorderStyles[eSideTop] == StyleBorderStyle::Dotted &&
1475           IsZeroSize(mBorderRadii[C_TL])) {
1476         noMarginTop = true;
1477       }
1478       if (mBorderStyles[eSideBottom] == StyleBorderStyle::Dotted &&
1479           IsZeroSize(mBorderRadii[C_BL])) {
1480         noMarginBottom = true;
1481       }
1482     }
1483   }
1484 
1485   for (unsigned int i = 0; i < borderColorStyleCount; i++) {
1486     // walk siRect inwards at the start of the loop to get the
1487     // correct inner rect.
1488     //
1489     // If noMarginTop is false:
1490     //   --------------------+
1491     //                      /|
1492     //                     / |
1493     //                    L  |
1494     //   ----------------+   |
1495     //                   |   |
1496     //                   |   |
1497     //
1498     // If noMarginTop is true:
1499     //   ----------------+<--+
1500     //                   |   |
1501     //                   |   |
1502     //                   |   |
1503     //                   |   |
1504     //                   |   |
1505     //                   |   |
1506     siRect.Deflate(Margin(noMarginTop ? 0 : borderWidths[i][0],
1507                           noMarginRight ? 0 : borderWidths[i][1],
1508                           noMarginBottom ? 0 : borderWidths[i][2],
1509                           noMarginLeft ? 0 : borderWidths[i][3]));
1510 
1511     if (borderColorStyle[i] != BorderColorStyleNone) {
1512       sRGBColor c = ComputeColorForLine(
1513           i, borderColorStyle, borderColorStyleCount, borderRenderColor);
1514       ColorPattern color(ToDeviceColor(c));
1515 
1516       FillSolidBorder(soRect, siRect, radii, borderWidths[i], aSides, color);
1517     }
1518 
1519     ComputeInnerRadii(radii, borderWidths[i], &radii);
1520 
1521     // And now soRect is the same as siRect, for the next line in.
1522     soRect = siRect;
1523   }
1524 }
1525 
SetupDashedOptions(StrokeOptions * aStrokeOptions,Float aDash[2],mozilla::Side aSide,Float aBorderLength,bool isCorner)1526 void nsCSSBorderRenderer::SetupDashedOptions(StrokeOptions* aStrokeOptions,
1527                                              Float aDash[2],
1528                                              mozilla::Side aSide,
1529                                              Float aBorderLength,
1530                                              bool isCorner) {
1531   MOZ_ASSERT(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
1532                  mBorderStyles[aSide] == StyleBorderStyle::Dotted,
1533              "Style should be dashed or dotted.");
1534 
1535   StyleBorderStyle style = mBorderStyles[aSide];
1536   Float borderWidth = mBorderWidths[aSide];
1537 
1538   // Dashed line starts and ends with half segment in most case.
1539   //
1540   // __--+---+---+---+---+---+---+---+---+--__
1541   //     |###|   |   |###|###|   |   |###|
1542   //     |###|   |   |###|###|   |   |###|
1543   //     |###|   |   |###|###|   |   |###|
1544   // __--+---+---+---+---+---+---+---+---+--__
1545   //
1546   // If radius=0 and other side is either dotted or 0-width, it starts or ends
1547   // with full segment.
1548   //
1549   // +---+---+---+---+---+---+---+---+---+---+
1550   // |###|###|   |   |###|###|   |   |###|###|
1551   // |###|###|   |   |###|###|   |   |###|###|
1552   // |###|###|   |   |###|###|   |   |###|###|
1553   // +---++--+---+---+---+---+---+---+--++---+
1554   // |    |                             |    |
1555   // |    |                             |    |
1556   // |    |                             |    |
1557   // |    |                             |    |
1558   // | ## |                             | ## |
1559   // |####|                             |####|
1560   // |####|                             |####|
1561   // | ## |                             | ## |
1562   // |    |                             |    |
1563   bool fullStart = false, fullEnd = false;
1564   Float halfDash;
1565   if (style == StyleBorderStyle::Dashed) {
1566     // If either end of the side is not connecting onto a corner then we want a
1567     // full dash at that end.
1568     //
1569     // Note that in the case that a corner is empty, either the adjacent side
1570     // has zero width, or else DrawBorders() set the corner to be empty
1571     // (it does that if the adjacent side has zero length and the border widths
1572     // of this and the adjacent sides are thin enough that the corner will be
1573     // insignificantly small).
1574 
1575     if (mBorderRadii[GetCCWCorner(aSide)].IsEmpty() &&
1576         (mBorderCornerDimensions[GetCCWCorner(aSide)].IsEmpty() ||
1577          mBorderStyles[PREV_SIDE(aSide)] == StyleBorderStyle::Dotted ||
1578          // XXX why this <=1 check?
1579          borderWidth <= 1.0f)) {
1580       fullStart = true;
1581     }
1582 
1583     if (mBorderRadii[GetCWCorner(aSide)].IsEmpty() &&
1584         (mBorderCornerDimensions[GetCWCorner(aSide)].IsEmpty() ||
1585          mBorderStyles[NEXT_SIDE(aSide)] == StyleBorderStyle::Dotted)) {
1586       fullEnd = true;
1587     }
1588 
1589     halfDash = borderWidth * DOT_LENGTH * DASH_LENGTH / 2.0f;
1590   } else {
1591     halfDash = borderWidth * DOT_LENGTH / 2.0f;
1592   }
1593 
1594   if (style == StyleBorderStyle::Dashed && aBorderLength > 0.0f) {
1595     // The number of half segments, with maximum dash length.
1596     int32_t count = floor(aBorderLength / halfDash);
1597     Float minHalfDash = borderWidth * DOT_LENGTH / 2.0f;
1598 
1599     if (fullStart && fullEnd) {
1600       // count should be 4n + 2
1601       //
1602       //   1 +       4       +        4      + 1
1603       //
1604       // |   |               |               |   |
1605       // +---+---+---+---+---+---+---+---+---+---+
1606       // |###|###|   |   |###|###|   |   |###|###|
1607       // |###|###|   |   |###|###|   |   |###|###|
1608       // |###|###|   |   |###|###|   |   |###|###|
1609       // +---+---+---+---+---+---+---+---+---+---+
1610 
1611       // If border is too short, draw solid line.
1612       if (aBorderLength < 6.0f * minHalfDash) {
1613         return;
1614       }
1615 
1616       if (count % 4 == 0) {
1617         count += 2;
1618       } else if (count % 4 == 1) {
1619         count += 1;
1620       } else if (count % 4 == 3) {
1621         count += 3;
1622       }
1623     } else if (fullStart || fullEnd) {
1624       // count should be 4n + 1
1625       //
1626       //   1 +       4       +        4
1627       //
1628       // |   |               |               |
1629       // +---+---+---+---+---+---+---+---+---+
1630       // |###|###|   |   |###|###|   |   |###|
1631       // |###|###|   |   |###|###|   |   |###|
1632       // |###|###|   |   |###|###|   |   |###|
1633       // +---+---+---+---+---+---+---+---+---+
1634       //
1635       //         4       +        4      + 1
1636       //
1637       // |               |               |   |
1638       // +---+---+---+---+---+---+---+---+---+
1639       // |###|   |   |###|###|   |   |###|###|
1640       // |###|   |   |###|###|   |   |###|###|
1641       // |###|   |   |###|###|   |   |###|###|
1642       // +---+---+---+---+---+---+---+---+---+
1643 
1644       // If border is too short, draw solid line.
1645       if (aBorderLength < 5.0f * minHalfDash) {
1646         return;
1647       }
1648 
1649       if (count % 4 == 0) {
1650         count += 1;
1651       } else if (count % 4 == 2) {
1652         count += 3;
1653       } else if (count % 4 == 3) {
1654         count += 2;
1655       }
1656     } else {
1657       // count should be 4n
1658       //
1659       //         4       +        4
1660       //
1661       // |               |               |
1662       // +---+---+---+---+---+---+---+---+
1663       // |###|   |   |###|###|   |   |###|
1664       // |###|   |   |###|###|   |   |###|
1665       // |###|   |   |###|###|   |   |###|
1666       // +---+---+---+---+---+---+---+---+
1667 
1668       // If border is too short, draw solid line.
1669       if (aBorderLength < 4.0f * minHalfDash) {
1670         return;
1671       }
1672 
1673       if (count % 4 == 1) {
1674         count += 3;
1675       } else if (count % 4 == 2) {
1676         count += 2;
1677       } else if (count % 4 == 3) {
1678         count += 1;
1679       }
1680     }
1681     halfDash = aBorderLength / count;
1682   }
1683 
1684   Float fullDash = halfDash * 2.0f;
1685 
1686   aDash[0] = fullDash;
1687   aDash[1] = fullDash;
1688 
1689   if (style == StyleBorderStyle::Dashed && fullDash > 1.0f) {
1690     if (!fullStart) {
1691       // Draw half segments on both ends.
1692       aStrokeOptions->mDashOffset = halfDash;
1693     }
1694   } else if (style != StyleBorderStyle::Dotted && isCorner) {
1695     // If side ends with filled full segment, corner should start with unfilled
1696     // full segment. Not needed for dotted corners, as they overlap one dot with
1697     // the side's end.
1698     //
1699     //     corner            side
1700     //   ------------>|<---------------------------
1701     //                |
1702     //          __+---+---+---+---+---+---+---+---+
1703     //       _+-  |   |###|###|   |   |###|###|   |
1704     //     /##|   |   |###|###|   |   |###|###|   |
1705     //    +####|   |  |###|###|   |   |###|###|   |
1706     //   /#\####| _+--+---+---+---+---+---+---+---+
1707     //  |####\##+-
1708     //  |#####+-
1709     //  +--###/
1710     //  |  --+
1711     aStrokeOptions->mDashOffset = fullDash;
1712   }
1713 
1714   aStrokeOptions->mDashPattern = aDash;
1715   aStrokeOptions->mDashLength = 2;
1716 
1717   PrintAsFormatString("dash: %f %f\n", aDash[0], aDash[1]);
1718 }
1719 
GetBorderLength(mozilla::Side aSide,const Point & aStart,const Point & aEnd)1720 static Float GetBorderLength(mozilla::Side aSide, const Point& aStart,
1721                              const Point& aEnd) {
1722   if (aSide == eSideTop) {
1723     return aEnd.x - aStart.x;
1724   }
1725   if (aSide == eSideRight) {
1726     return aEnd.y - aStart.y;
1727   }
1728   if (aSide == eSideBottom) {
1729     return aStart.x - aEnd.x;
1730   }
1731   return aStart.y - aEnd.y;
1732 }
1733 
DrawDashedOrDottedSide(mozilla::Side aSide)1734 void nsCSSBorderRenderer::DrawDashedOrDottedSide(mozilla::Side aSide) {
1735   // Draw dashed/dotted side with following approach.
1736   //
1737   // dashed side
1738   //   Draw dashed line along the side, with appropriate dash length and gap
1739   //   to make the side symmetric as far as possible.  Dash length equals to
1740   //   the gap, and the ratio of the dash length to border-width is the maximum
1741   //   value in in [1, 3] range.
1742   //   In most case, line ends with half segment, to joint with corner easily.
1743   //   If adjacent side is dotted or 0px and border-radius for the corner
1744   //   between them is 0, the line ends with full segment.
1745   //   (see comment for GetStraightBorderPoint for more detail)
1746   //
1747   // dotted side
1748   //   If border-width <= 2.0, draw 1:1 dashed line.
1749   //   Otherwise, draw circles along the side, with appropriate gap that makes
1750   //   the side symmetric as far as possible.  The ratio of the gap to
1751   //   border-width is the maximum value in [0.5, 1] range in most case.
1752   //   if the side is too short and there's only 2 dots, it can be more smaller.
1753   //   If there's no space to place 2 dots at the side, draw single dot at the
1754   //   middle of the side.
1755   //   In most case, line ends with filled dot, to joint with corner easily,
1756   //   If adjacent side is dotted with larger border-width, or other style,
1757   //   the line ends with unfilled dot.
1758   //   (see comment for GetStraightBorderPoint for more detail)
1759 
1760   NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
1761                    mBorderStyles[aSide] == StyleBorderStyle::Dotted,
1762                "Style should be dashed or dotted.");
1763 
1764   Float borderWidth = mBorderWidths[aSide];
1765   if (borderWidth == 0.0f) {
1766     return;
1767   }
1768 
1769   if (mBorderStyles[aSide] == StyleBorderStyle::Dotted && borderWidth > 2.0f) {
1770     DrawDottedSideSlow(aSide);
1771     return;
1772   }
1773 
1774   nscolor borderColor = mBorderColors[aSide];
1775   bool ignored;
1776   // Get the start and end points of the side, ensuring that any dot origins get
1777   // pushed outward to account for stroking.
1778   Point start =
1779       GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f);
1780   Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f);
1781   if (borderWidth < 2.0f) {
1782     // Round start to draw dot on each pixel.
1783     if (IsHorizontalSide(aSide)) {
1784       start.x = round(start.x);
1785     } else {
1786       start.y = round(start.y);
1787     }
1788   }
1789 
1790   Float borderLength = GetBorderLength(aSide, start, end);
1791   if (borderLength < 0.0f) {
1792     return;
1793   }
1794 
1795   StrokeOptions strokeOptions(borderWidth);
1796   Float dash[2];
1797   SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false);
1798 
1799   // For dotted sides that can merge with their prior dotted sides, advance the
1800   // dash offset to measure the distance around the combined path. This prevents
1801   // two dots from bunching together at a corner.
1802   mozilla::Side mergeSide = aSide;
1803   while (IsCornerMergeable(GetCCWCorner(mergeSide))) {
1804     mergeSide = PREV_SIDE(mergeSide);
1805     // If we looped all the way around, measure starting at the top side, since
1806     // we need to pick a fixed location to start measuring distance from still.
1807     if (mergeSide == aSide) {
1808       mergeSide = eSideTop;
1809       break;
1810     }
1811   }
1812   while (mergeSide != aSide) {
1813     // Measure the length of the merged side starting from a possibly
1814     // unmergeable corner up to the merged corner. A merged corner effectively
1815     // has no border radius, so we can just use the cheaper AtCorner to find the
1816     // end point.
1817     Float mergeLength =
1818         GetBorderLength(mergeSide,
1819                         GetStraightBorderPoint(
1820                             mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f),
1821                         mOuterRect.AtCorner(GetCWCorner(mergeSide)));
1822     // Add in the merged side length. Also offset the dash progress by an extra
1823     // dot's width to avoid drawing a dot that would overdraw where the merged
1824     // side would have ended in a gap, i.e. O_O_
1825     //                                    O
1826     strokeOptions.mDashOffset += mergeLength + borderWidth;
1827     mergeSide = NEXT_SIDE(mergeSide);
1828   }
1829 
1830   DrawOptions drawOptions;
1831   if (mBorderStyles[aSide] == StyleBorderStyle::Dotted) {
1832     drawOptions.mAntialiasMode = AntialiasMode::NONE;
1833   }
1834 
1835   mDrawTarget->StrokeLine(start, end, ColorPattern(ToDeviceColor(borderColor)),
1836                           strokeOptions, drawOptions);
1837 }
1838 
DrawDottedSideSlow(mozilla::Side aSide)1839 void nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide) {
1840   // Draw each circles separately for dotted with borderWidth > 2.0.
1841   // Dashed line with CapStyle::ROUND doesn't render perfect circles.
1842 
1843   NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted,
1844                "Style should be dotted.");
1845 
1846   Float borderWidth = mBorderWidths[aSide];
1847   if (borderWidth == 0.0f) {
1848     return;
1849   }
1850 
1851   nscolor borderColor = mBorderColors[aSide];
1852   bool isStartUnfilled, isEndUnfilled;
1853   Point start =
1854       GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &isStartUnfilled);
1855   Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &isEndUnfilled);
1856   enum {
1857     // Corner is not mergeable.
1858     NO_MERGE,
1859 
1860     // Corner between different colors.
1861     // Two dots are merged into one, and both side draw half dot.
1862     MERGE_HALF,
1863 
1864     // Corner between same colors, CCW corner of the side.
1865     // Two dots are merged into one, and this side draw entire dot.
1866     //
1867     // MERGE_ALL               MERGE_NONE
1868     //   |                       |
1869     //   v                       v
1870     // +-----------------------+----+
1871     // | ##      ##      ##    | ## |
1872     // |####    ####    ####   |####|
1873     // |####    ####    ####   |####|
1874     // | ##      ##      ##    | ## |
1875     // +----+------------------+    |
1876     // |    |                  |    |
1877     // |    |                  |    |
1878     // |    |                  |    |
1879     // | ## |                  | ## |
1880     // |####|                  |####|
1881     MERGE_ALL,
1882 
1883     // Corner between same colors, CW corner of the side.
1884     // Two dots are merged into one, and this side doesn't draw dot.
1885     MERGE_NONE
1886   } mergeStart = NO_MERGE,
1887     mergeEnd = NO_MERGE;
1888 
1889   if (IsCornerMergeable(GetCCWCorner(aSide))) {
1890     if (borderColor == mBorderColors[PREV_SIDE(aSide)]) {
1891       mergeStart = MERGE_ALL;
1892     } else {
1893       mergeStart = MERGE_HALF;
1894     }
1895   }
1896 
1897   if (IsCornerMergeable(GetCWCorner(aSide))) {
1898     if (borderColor == mBorderColors[NEXT_SIDE(aSide)]) {
1899       mergeEnd = MERGE_NONE;
1900     } else {
1901       mergeEnd = MERGE_HALF;
1902     }
1903   }
1904 
1905   Float borderLength = GetBorderLength(aSide, start, end);
1906   if (borderLength < 0.0f) {
1907     if (isStartUnfilled || isEndUnfilled) {
1908       return;
1909     }
1910     borderLength = 0.0f;
1911     start = end = (start + end) / 2.0f;
1912   }
1913 
1914   Float dotWidth = borderWidth * DOT_LENGTH;
1915   Float radius = borderWidth / 2.0f;
1916   if (borderLength < dotWidth) {
1917     // If dots on start and end may overlap, draw a dot at the middle of them.
1918     //
1919     //     ___---+-------+---___
1920     // __--      | ##### |      --__
1921     //          #|#######|#
1922     //         ##|#######|##
1923     //        ###|#######|###
1924     //        ###+###+###+###
1925     //         start ## end #
1926     //         ##|#######|##
1927     //          #|#######|#
1928     //           | ##### |
1929     //       __--+-------+--__
1930     //     _-                 -_
1931     //
1932     // If that circle overflows from outer rect, do not draw it.
1933     //
1934     //           +-------+
1935     //           | ##### |
1936     //          #|#######|#
1937     //         ##|#######|##
1938     //        ###|#######|###
1939     //        ###|###+###|###
1940     //        ###|#######|###
1941     //         ##|#######|##
1942     //          #|#######|#
1943     //           | ##### |
1944     //           +--+-+--+
1945     //           |  | |  |
1946     //           |  | |  |
1947     if (!mOuterRect.Contains(Rect(start.x - radius, start.y - radius,
1948                                   borderWidth, borderWidth))) {
1949       return;
1950     }
1951 
1952     if (isStartUnfilled || isEndUnfilled) {
1953       return;
1954     }
1955 
1956     Point P = (start + end) / 2;
1957     RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
1958     builder->MoveTo(Point(P.x + radius, P.y));
1959     builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
1960     RefPtr<Path> path = builder->Finish();
1961     mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
1962     return;
1963   }
1964 
1965   if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
1966     // MERGE_HALF
1967     //               Eo
1968     //   -------+----+
1969     //        ##### /
1970     //       ######/
1971     //      ######/
1972     //      ####+
1973     //      ##/ end
1974     //       /
1975     //      /
1976     //   --+
1977     //     Ei
1978     //
1979     // other (NO_MERGE, MERGE_ALL, MERGE_NONE)
1980     //               Eo
1981     //   ------------+
1982     //        #####  |
1983     //       ####### |
1984     //      #########|
1985     //      ####+####|
1986     //      ## end ##|
1987     //       ####### |
1988     //        #####  |
1989     //   ------------+
1990     //               Ei
1991 
1992     Point I(0.0f, 0.0f), J(0.0f, 0.0f);
1993     if (aSide == eSideTop) {
1994       I.x = 1.0f;
1995       J.y = 1.0f;
1996     } else if (aSide == eSideRight) {
1997       I.y = 1.0f;
1998       J.x = -1.0f;
1999     } else if (aSide == eSideBottom) {
2000       I.x = -1.0f;
2001       J.y = -1.0f;
2002     } else if (aSide == eSideLeft) {
2003       I.y = -1.0f;
2004       J.x = 1.0f;
2005     }
2006 
2007     Point So, Si, Eo, Ei;
2008 
2009     So = (start + (-I + -J) * borderWidth / 2.0f);
2010     Si = (mergeStart == MERGE_HALF) ? (start + (I + J) * borderWidth / 2.0f)
2011                                     : (start + (-I + J) * borderWidth / 2.0f);
2012     Eo = (end + (I - J) * borderWidth / 2.0f);
2013     Ei = (mergeEnd == MERGE_HALF) ? (end + (-I + J) * borderWidth / 2.0f)
2014                                   : (end + (I + J) * borderWidth / 2.0f);
2015 
2016     RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2017     builder->MoveTo(So);
2018     builder->LineTo(Eo);
2019     builder->LineTo(Ei);
2020     builder->LineTo(Si);
2021     builder->Close();
2022     RefPtr<Path> path = builder->Finish();
2023 
2024     mDrawTarget->PushClip(path);
2025   }
2026 
2027   size_t count = round(borderLength / dotWidth);
2028   if (isStartUnfilled == isEndUnfilled) {
2029     // Split into 2n segments.
2030     if (count % 2) {
2031       count++;
2032     }
2033   } else {
2034     // Split into 2n+1 segments.
2035     if (count % 2 == 0) {
2036       count++;
2037     }
2038   }
2039 
2040   // A: radius == borderWidth / 2.0
2041   // B: borderLength / count == borderWidth * (1 - overlap)
2042   //
2043   //   A      B         B        B        B     A
2044   // |<-->|<------>|<------>|<------>|<------>|<-->|
2045   // |    |        |        |        |        |    |
2046   // +----+--------+--------+--------+--------+----+
2047   // |  ##|##    **|**    ##|##    **|**    ##|##  |
2048   // | ###|###  ***|***  ###|###  ***|***  ###|### |
2049   // |####|####****|****####|####****|****####|####|
2050   // |####+####****+****####+####****+****####+####|
2051   // |# start #****|****####|####****|****## end ##|
2052   // | ###|###  ***|***  ###|###  ***|***  ###|### |
2053   // |  ##|##    **|**    ##|##    **|**    ##|##  |
2054   // +----+----+---+--------+--------+---+----+----+
2055   // |         |                         |         |
2056   // |         |                         |         |
2057 
2058   // If isStartUnfilled is true, draw dots on 2j+1 points, if not, draw dots on
2059   // 2j points.
2060   size_t from = isStartUnfilled ? 1 : 0;
2061 
2062   // If mergeEnd == MERGE_NONE, last dot is drawn by next side.
2063   size_t to = count;
2064   if (mergeEnd == MERGE_NONE) {
2065     if (to > 2) {
2066       to -= 2;
2067     } else {
2068       to = 0;
2069     }
2070   }
2071 
2072   Point fromP = (start * (count - from) + end * from) / count;
2073   Point toP = (start * (count - to) + end * to) / count;
2074   // Extend dirty rect to avoid clipping pixel for anti-aliasing.
2075   const Float AA_MARGIN = 2.0f;
2076 
2077   if (aSide == eSideTop) {
2078     // Tweak |from| and |to| to fit into |mDirtyRect + radius margin|,
2079     // to render only paths that may overlap mDirtyRect.
2080     //
2081     //                mDirtyRect + radius margin
2082     //              +--+---------------------+--+
2083     //              |                           |
2084     //              |         mDirtyRect        |
2085     //              +  +---------------------+  +
2086     // from   ===>  |from                    to |   <===  to
2087     //    +-----+-----+-----+-----+-----+-----+-----+-----+
2088     //   ###        |###         ###         ###|        ###
2089     //  #####       #####       #####       #####       #####
2090     //  #####       #####       #####       #####       #####
2091     //  #####       #####       #####       #####       #####
2092     //   ###        |###         ###         ###|        ###
2093     //              |  |                     |  |
2094     //              +  +---------------------+  +
2095     //              |                           |
2096     //              |                           |
2097     //              +--+---------------------+--+
2098 
2099     Float left = mDirtyRect.x - radius - AA_MARGIN;
2100     if (fromP.x < left) {
2101       size_t tmp = ceil(count * (left - start.x) / (end.x - start.x));
2102       if (tmp > from) {
2103         // We increment by 2, so odd/even should match between before/after.
2104         if ((tmp & 1) != (from & 1)) {
2105           from = tmp - 1;
2106         } else {
2107           from = tmp;
2108         }
2109       }
2110     }
2111     Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
2112     if (toP.x > right) {
2113       size_t tmp = floor(count * (right - start.x) / (end.x - start.x));
2114       if (tmp < to) {
2115         if ((tmp & 1) != (to & 1)) {
2116           to = tmp + 1;
2117         } else {
2118           to = tmp;
2119         }
2120       }
2121     }
2122   } else if (aSide == eSideRight) {
2123     Float top = mDirtyRect.y - radius - AA_MARGIN;
2124     if (fromP.y < top) {
2125       size_t tmp = ceil(count * (top - start.y) / (end.y - start.y));
2126       if (tmp > from) {
2127         if ((tmp & 1) != (from & 1)) {
2128           from = tmp - 1;
2129         } else {
2130           from = tmp;
2131         }
2132       }
2133     }
2134     Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
2135     if (toP.y > bottom) {
2136       size_t tmp = floor(count * (bottom - start.y) / (end.y - start.y));
2137       if (tmp < to) {
2138         if ((tmp & 1) != (to & 1)) {
2139           to = tmp + 1;
2140         } else {
2141           to = tmp;
2142         }
2143       }
2144     }
2145   } else if (aSide == eSideBottom) {
2146     Float right = mDirtyRect.x + mDirtyRect.width + radius + AA_MARGIN;
2147     if (fromP.x > right) {
2148       size_t tmp = ceil(count * (right - start.x) / (end.x - start.x));
2149       if (tmp > from) {
2150         if ((tmp & 1) != (from & 1)) {
2151           from = tmp - 1;
2152         } else {
2153           from = tmp;
2154         }
2155       }
2156     }
2157     Float left = mDirtyRect.x - radius - AA_MARGIN;
2158     if (toP.x < left) {
2159       size_t tmp = floor(count * (left - start.x) / (end.x - start.x));
2160       if (tmp < to) {
2161         if ((tmp & 1) != (to & 1)) {
2162           to = tmp + 1;
2163         } else {
2164           to = tmp;
2165         }
2166       }
2167     }
2168   } else if (aSide == eSideLeft) {
2169     Float bottom = mDirtyRect.y + mDirtyRect.height + radius + AA_MARGIN;
2170     if (fromP.y > bottom) {
2171       size_t tmp = ceil(count * (bottom - start.y) / (end.y - start.y));
2172       if (tmp > from) {
2173         if ((tmp & 1) != (from & 1)) {
2174           from = tmp - 1;
2175         } else {
2176           from = tmp;
2177         }
2178       }
2179     }
2180     Float top = mDirtyRect.y - radius - AA_MARGIN;
2181     if (toP.y < top) {
2182       size_t tmp = floor(count * (top - start.y) / (end.y - start.y));
2183       if (tmp < to) {
2184         if ((tmp & 1) != (to & 1)) {
2185           to = tmp + 1;
2186         } else {
2187           to = tmp;
2188         }
2189       }
2190     }
2191   }
2192 
2193   RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2194   size_t segmentCount = 0;
2195   for (size_t i = from; i <= to; i += 2) {
2196     if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
2197       RefPtr<Path> path = builder->Finish();
2198       mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2199       builder = mDrawTarget->CreatePathBuilder();
2200       segmentCount = 0;
2201     }
2202 
2203     Point P = (start * (count - i) + end * i) / count;
2204     builder->MoveTo(Point(P.x + radius, P.y));
2205     builder->Arc(P, radius, 0.0f, Float(2.0 * M_PI));
2206     segmentCount++;
2207   }
2208   RefPtr<Path> path = builder->Finish();
2209   mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2210 
2211   if (mergeStart == MERGE_HALF || mergeEnd == MERGE_HALF) {
2212     mDrawTarget->PopClip();
2213   }
2214 }
2215 
DrawDashedOrDottedCorner(mozilla::Side aSide,Corner aCorner)2216 void nsCSSBorderRenderer::DrawDashedOrDottedCorner(mozilla::Side aSide,
2217                                                    Corner aCorner) {
2218   // Draw dashed/dotted corner with following approach.
2219   //
2220   // dashed corner
2221   //   If both side has same border-width and border-width <= 2.0, draw dashed
2222   //   line along the corner, with appropriate dash length and gap to make the
2223   //   corner symmetric as far as possible.  Dash length equals to the gap, and
2224   //   the ratio of the dash length to border-width is the maximum value in in
2225   //   [1, 3] range.
2226   //   Otherwise, draw dashed segments along the corner, keeping same dash
2227   //   length ratio to border-width at that point.
2228   //   (see DashedCornerFinder.h for more detail)
2229   //   Line ends with half segments, to joint with both side easily.
2230   //
2231   // dotted corner
2232   //   If both side has same border-width and border-width <= 2.0, draw 1:1
2233   //   dashed line along the corner.
2234   //   Otherwise Draw circles along the corner, with appropriate gap that makes
2235   //   the corner symmetric as far as possible.  The size of the circle may
2236   //   change along the corner, that is tangent to the outer curver and the
2237   //   inner curve.  The ratio of the gap to circle diameter is the maximum
2238   //   value in [0.5, 1] range.
2239   //   (see DottedCornerFinder.h for more detail)
2240   //   Corner ends with filled dots but those dots are drawn by
2241   //   DrawDashedOrDottedSide.  So this may draw no circles if there's no space
2242   //   between 2 dots at both ends.
2243 
2244   NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
2245                    mBorderStyles[aSide] == StyleBorderStyle::Dotted,
2246                "Style should be dashed or dotted.");
2247 
2248   if (IsCornerMergeable(aCorner)) {
2249     // DrawDashedOrDottedSide will draw corner.
2250     return;
2251   }
2252 
2253   mozilla::Side sideH(GetHorizontalSide(aCorner));
2254   mozilla::Side sideV(GetVerticalSide(aCorner));
2255   Float borderWidthH = mBorderWidths[sideH];
2256   Float borderWidthV = mBorderWidths[sideV];
2257   if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
2258     return;
2259   }
2260 
2261   StyleBorderStyle styleH = mBorderStyles[sideH];
2262   StyleBorderStyle styleV = mBorderStyles[sideV];
2263 
2264   // Corner between dotted and others with radius=0 is drawn by side.
2265   if (IsZeroSize(mBorderRadii[aCorner]) &&
2266       (styleV == StyleBorderStyle::Dotted ||
2267        styleH == StyleBorderStyle::Dotted)) {
2268     return;
2269   }
2270 
2271   Float maxRadius =
2272       std::max(mBorderRadii[aCorner].width, mBorderRadii[aCorner].height);
2273   if (maxRadius > BORDER_DOTTED_CORNER_MAX_RADIUS) {
2274     DrawFallbackSolidCorner(aSide, aCorner);
2275     return;
2276   }
2277 
2278   if (borderWidthH != borderWidthV || borderWidthH > 2.0f) {
2279     StyleBorderStyle style = mBorderStyles[aSide];
2280     if (style == StyleBorderStyle::Dotted) {
2281       DrawDottedCornerSlow(aSide, aCorner);
2282     } else {
2283       DrawDashedCornerSlow(aSide, aCorner);
2284     }
2285     return;
2286   }
2287 
2288   nscolor borderColor = mBorderColors[aSide];
2289   Point points[4];
2290   bool ignored;
2291   // Get the start and end points of the corner arc, ensuring that any dot
2292   // origins get pushed backwards towards the edges of the corner rect to
2293   // account for stroking.
2294   points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f);
2295   points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f);
2296   // Round points to draw dot on each pixel.
2297   if (borderWidthH < 2.0f) {
2298     points[0].x = round(points[0].x);
2299   }
2300   if (borderWidthV < 2.0f) {
2301     points[3].y = round(points[3].y);
2302   }
2303   points[1] = points[0];
2304   points[1].x += kKappaFactor * (points[3].x - points[0].x);
2305   points[2] = points[3];
2306   points[2].y += kKappaFactor * (points[0].y - points[3].y);
2307 
2308   Float len = GetQuarterEllipticArcLength(fabs(points[0].x - points[3].x),
2309                                           fabs(points[0].y - points[3].y));
2310 
2311   Float dash[2];
2312   StrokeOptions strokeOptions(borderWidthH);
2313   SetupDashedOptions(&strokeOptions, dash, aSide, len, true);
2314 
2315   RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2316   builder->MoveTo(points[0]);
2317   builder->BezierTo(points[1], points[2], points[3]);
2318   RefPtr<Path> path = builder->Finish();
2319   mDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
2320                       strokeOptions);
2321 }
2322 
DrawDottedCornerSlow(mozilla::Side aSide,Corner aCorner)2323 void nsCSSBorderRenderer::DrawDottedCornerSlow(mozilla::Side aSide,
2324                                                Corner aCorner) {
2325   NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dotted,
2326                "Style should be dotted.");
2327 
2328   mozilla::Side sideH(GetHorizontalSide(aCorner));
2329   mozilla::Side sideV(GetVerticalSide(aCorner));
2330   Float R0 = mBorderWidths[sideH] / 2.0f;
2331   Float Rn = mBorderWidths[sideV] / 2.0f;
2332   if (R0 == 0.0f && Rn == 0.0f) {
2333     return;
2334   }
2335 
2336   nscolor borderColor = mBorderColors[aSide];
2337   Bezier outerBezier;
2338   Bezier innerBezier;
2339   GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
2340 
2341   bool ignored;
2342   Point C0 = GetStraightBorderPoint(sideH, aCorner, &ignored);
2343   Point Cn = GetStraightBorderPoint(sideV, aCorner, &ignored);
2344   DottedCornerFinder finder(outerBezier, innerBezier, aCorner,
2345                             mBorderRadii[aCorner].width,
2346                             mBorderRadii[aCorner].height, C0, R0, Cn, Rn,
2347                             mBorderCornerDimensions[aCorner]);
2348 
2349   RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2350   size_t segmentCount = 0;
2351   const Float AA_MARGIN = 2.0f;
2352   Rect marginedDirtyRect = mDirtyRect;
2353   marginedDirtyRect.Inflate(std::max(R0, Rn) + AA_MARGIN);
2354   bool entered = false;
2355   while (finder.HasMore()) {
2356     if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
2357       RefPtr<Path> path = builder->Finish();
2358       mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2359       builder = mDrawTarget->CreatePathBuilder();
2360       segmentCount = 0;
2361     }
2362 
2363     DottedCornerFinder::Result result = finder.Next();
2364 
2365     if (marginedDirtyRect.Contains(result.C) && result.r > 0) {
2366       entered = true;
2367       builder->MoveTo(Point(result.C.x + result.r, result.C.y));
2368       builder->Arc(result.C, result.r, 0, Float(2.0 * M_PI));
2369       segmentCount++;
2370     } else if (entered) {
2371       break;
2372     }
2373   }
2374   RefPtr<Path> path = builder->Finish();
2375   mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2376 }
2377 
DashedPathOverlapsRect(Rect & pathRect,const Rect & marginedDirtyRect,DashedCornerFinder::Result & result)2378 static inline bool DashedPathOverlapsRect(Rect& pathRect,
2379                                           const Rect& marginedDirtyRect,
2380                                           DashedCornerFinder::Result& result) {
2381   // Calculate a rect that contains all control points of the |result| path,
2382   // and check if it intersects with |marginedDirtyRect|.
2383   pathRect.SetRect(result.outerSectionBezier.mPoints[0].x,
2384                    result.outerSectionBezier.mPoints[0].y, 0, 0);
2385   pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[1]);
2386   pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[2]);
2387   pathRect.ExpandToEnclose(result.outerSectionBezier.mPoints[3]);
2388   pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[0]);
2389   pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[1]);
2390   pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[2]);
2391   pathRect.ExpandToEnclose(result.innerSectionBezier.mPoints[3]);
2392 
2393   return pathRect.Intersects(marginedDirtyRect);
2394 }
2395 
DrawDashedCornerSlow(mozilla::Side aSide,Corner aCorner)2396 void nsCSSBorderRenderer::DrawDashedCornerSlow(mozilla::Side aSide,
2397                                                Corner aCorner) {
2398   NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed,
2399                "Style should be dashed.");
2400 
2401   mozilla::Side sideH(GetHorizontalSide(aCorner));
2402   mozilla::Side sideV(GetVerticalSide(aCorner));
2403   Float borderWidthH = mBorderWidths[sideH];
2404   Float borderWidthV = mBorderWidths[sideV];
2405   if (borderWidthH == 0.0f && borderWidthV == 0.0f) {
2406     return;
2407   }
2408 
2409   nscolor borderColor = mBorderColors[aSide];
2410   Bezier outerBezier;
2411   Bezier innerBezier;
2412   GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
2413 
2414   DashedCornerFinder finder(outerBezier, innerBezier, borderWidthH,
2415                             borderWidthV, mBorderCornerDimensions[aCorner]);
2416 
2417   RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2418   size_t segmentCount = 0;
2419   const Float AA_MARGIN = 2.0f;
2420   Rect marginedDirtyRect = mDirtyRect;
2421   marginedDirtyRect.Inflate(AA_MARGIN);
2422   Rect pathRect;
2423   bool entered = false;
2424   while (finder.HasMore()) {
2425     if (segmentCount > BORDER_SEGMENT_COUNT_MAX) {
2426       RefPtr<Path> path = builder->Finish();
2427       mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2428       builder = mDrawTarget->CreatePathBuilder();
2429       segmentCount = 0;
2430     }
2431 
2432     DashedCornerFinder::Result result = finder.Next();
2433 
2434     if (DashedPathOverlapsRect(pathRect, marginedDirtyRect, result)) {
2435       entered = true;
2436       builder->MoveTo(result.outerSectionBezier.mPoints[0]);
2437       builder->BezierTo(result.outerSectionBezier.mPoints[1],
2438                         result.outerSectionBezier.mPoints[2],
2439                         result.outerSectionBezier.mPoints[3]);
2440       builder->LineTo(result.innerSectionBezier.mPoints[3]);
2441       builder->BezierTo(result.innerSectionBezier.mPoints[2],
2442                         result.innerSectionBezier.mPoints[1],
2443                         result.innerSectionBezier.mPoints[0]);
2444       builder->LineTo(result.outerSectionBezier.mPoints[0]);
2445       segmentCount++;
2446     } else if (entered) {
2447       break;
2448     }
2449   }
2450 
2451   if (outerBezier.mPoints[0].x != innerBezier.mPoints[0].x) {
2452     // Fill gap before the first section.
2453     //
2454     //     outnerPoint[0]
2455     //         |
2456     //         v
2457     //        _+-----------+--
2458     //      /   \##########|
2459     //    /      \#########|
2460     //   +        \########|
2461     //   |\         \######|
2462     //   |  \        \#####|
2463     //   |    \       \####|
2464     //   |      \       \##|
2465     //   |        \      \#|
2466     //   |          \     \|
2467     //   |            \  _-+--
2468     //   +--------------+  ^
2469     //   |              |  |
2470     //   |              |  innerPoint[0]
2471     //   |              |
2472     builder->MoveTo(outerBezier.mPoints[0]);
2473     builder->LineTo(innerBezier.mPoints[0]);
2474     builder->LineTo(Point(innerBezier.mPoints[0].x, outerBezier.mPoints[0].y));
2475     builder->LineTo(outerBezier.mPoints[0]);
2476   }
2477 
2478   if (outerBezier.mPoints[3].y != innerBezier.mPoints[3].y) {
2479     // Fill gap after the last section.
2480     //
2481     // outnerPoint[3]
2482     //   |
2483     //   |
2484     //   |    _+-----------+--
2485     //   |  /   \          |
2486     //   v/      \         |
2487     //   +        \        |
2488     //   |\         \      |
2489     //   |##\        \     |
2490     //   |####\       \    |
2491     //   |######\       \  |
2492     //   |########\      \ |
2493     //   |##########\     \|
2494     //   |############\  _-+--
2495     //   +--------------+<-- innerPoint[3]
2496     //   |              |
2497     //   |              |
2498     //   |              |
2499     builder->MoveTo(outerBezier.mPoints[3]);
2500     builder->LineTo(innerBezier.mPoints[3]);
2501     builder->LineTo(Point(outerBezier.mPoints[3].x, innerBezier.mPoints[3].y));
2502     builder->LineTo(outerBezier.mPoints[3]);
2503   }
2504 
2505   RefPtr<Path> path = builder->Finish();
2506   mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2507 }
2508 
DrawFallbackSolidCorner(mozilla::Side aSide,Corner aCorner)2509 void nsCSSBorderRenderer::DrawFallbackSolidCorner(mozilla::Side aSide,
2510                                                   Corner aCorner) {
2511   // Render too large dashed or dotted corner with solid style, to avoid hangup
2512   // inside DashedCornerFinder and DottedCornerFinder.
2513 
2514   NS_ASSERTION(mBorderStyles[aSide] == StyleBorderStyle::Dashed ||
2515                    mBorderStyles[aSide] == StyleBorderStyle::Dotted,
2516                "Style should be dashed or dotted.");
2517 
2518   nscolor borderColor = mBorderColors[aSide];
2519   Bezier outerBezier;
2520   Bezier innerBezier;
2521   GetOuterAndInnerBezier(&outerBezier, &innerBezier, aCorner);
2522 
2523   RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
2524 
2525   builder->MoveTo(outerBezier.mPoints[0]);
2526   builder->BezierTo(outerBezier.mPoints[1], outerBezier.mPoints[2],
2527                     outerBezier.mPoints[3]);
2528   builder->LineTo(innerBezier.mPoints[3]);
2529   builder->BezierTo(innerBezier.mPoints[2], innerBezier.mPoints[1],
2530                     innerBezier.mPoints[0]);
2531   builder->LineTo(outerBezier.mPoints[0]);
2532 
2533   RefPtr<Path> path = builder->Finish();
2534   mDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
2535 
2536   if (!mPresContext->HasWarnedAboutTooLargeDashedOrDottedRadius()) {
2537     mPresContext->SetHasWarnedAboutTooLargeDashedOrDottedRadius();
2538     nsContentUtils::ReportToConsole(
2539         nsIScriptError::warningFlag, "CSS"_ns, mPresContext->Document(),
2540         nsContentUtils::eCSS_PROPERTIES,
2541         mBorderStyles[aSide] == StyleBorderStyle::Dashed
2542             ? "TooLargeDashedRadius"
2543             : "TooLargeDottedRadius");
2544   }
2545 }
2546 
AllBordersSameWidth()2547 bool nsCSSBorderRenderer::AllBordersSameWidth() {
2548   if (mBorderWidths[0] == mBorderWidths[1] &&
2549       mBorderWidths[0] == mBorderWidths[2] &&
2550       mBorderWidths[0] == mBorderWidths[3]) {
2551     return true;
2552   }
2553 
2554   return false;
2555 }
2556 
AllBordersSolid()2557 bool nsCSSBorderRenderer::AllBordersSolid() {
2558   for (const auto i : mozilla::AllPhysicalSides()) {
2559     if (mBorderStyles[i] == StyleBorderStyle::Solid ||
2560         mBorderStyles[i] == StyleBorderStyle::None ||
2561         mBorderStyles[i] == StyleBorderStyle::Hidden) {
2562       continue;
2563     }
2564     return false;
2565   }
2566 
2567   return true;
2568 }
2569 
IsVisible(StyleBorderStyle aStyle)2570 static bool IsVisible(StyleBorderStyle aStyle) {
2571   if (aStyle != StyleBorderStyle::None && aStyle != StyleBorderStyle::Hidden) {
2572     return true;
2573   }
2574   return false;
2575 }
2576 
2577 struct twoFloats {
2578   Float a, b;
2579 
operator *twoFloats2580   twoFloats operator*(const Size& aSize) const {
2581     return {a * aSize.width, b * aSize.height};
2582   }
2583 
operator *twoFloats2584   twoFloats operator*(Float aScale) const { return {a * aScale, b * aScale}; }
2585 
operator +twoFloats2586   twoFloats operator+(const Point& aPoint) const {
2587     return {a + aPoint.x, b + aPoint.y};
2588   }
2589 
operator PointtwoFloats2590   operator Point() const { return Point(a, b); }
2591 };
2592 
DrawSingleWidthSolidBorder()2593 void nsCSSBorderRenderer::DrawSingleWidthSolidBorder() {
2594   // Easy enough to deal with.
2595   Rect rect = mOuterRect;
2596   rect.Deflate(0.5);
2597 
2598   const twoFloats cornerAdjusts[4] = {
2599       {+0.5, 0}, {0, +0.5}, {-0.5, 0}, {0, -0.5}};
2600   for (const auto side : mozilla::AllPhysicalSides()) {
2601     Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
2602     Point secondCorner = rect.CWCorner(side) + cornerAdjusts[side];
2603 
2604     ColorPattern color(ToDeviceColor(mBorderColors[side]));
2605 
2606     mDrawTarget->StrokeLine(firstCorner, secondCorner, color);
2607   }
2608 }
2609 
2610 // Intersect a ray from the inner corner to the outer corner
2611 // with the border radius, yielding the intersection point.
IntersectBorderRadius(const Point & aCenter,const Size & aRadius,const Point & aInnerCorner,const Point & aCornerDirection)2612 static Point IntersectBorderRadius(const Point& aCenter, const Size& aRadius,
2613                                    const Point& aInnerCorner,
2614                                    const Point& aCornerDirection) {
2615   Point toCorner = aCornerDirection;
2616   // transform to-corner ray to unit-circle space
2617   toCorner.x /= aRadius.width;
2618   toCorner.y /= aRadius.height;
2619   // normalize to-corner ray
2620   Float cornerDist = toCorner.Length();
2621   if (cornerDist < 1.0e-6f) {
2622     return aInnerCorner;
2623   }
2624   toCorner = toCorner / cornerDist;
2625   // ray from inner corner to border radius center
2626   Point toCenter = aCenter - aInnerCorner;
2627   // transform to-center ray to unit-circle space
2628   toCenter.x /= aRadius.width;
2629   toCenter.y /= aRadius.height;
2630   // compute offset of intersection with border radius unit circle
2631   Float offset = toCenter.DotProduct(toCorner);
2632   // compute discriminant to check for intersections
2633   Float discrim = 1.0f - toCenter.DotProduct(toCenter) + offset * offset;
2634   // choose farthest intersection
2635   offset += sqrtf(std::max(discrim, 0.0f));
2636   // transform to-corner ray back out of unit-circle space
2637   toCorner.x *= aRadius.width;
2638   toCorner.y *= aRadius.height;
2639   return aInnerCorner + toCorner * offset;
2640 }
2641 
2642 // Calculate the split point and split angle for a border radius with
2643 // differing sides.
SplitBorderRadius(const Point & aCenter,const Size & aRadius,const Point & aOuterCorner,const Point & aInnerCorner,const twoFloats & aCornerMults,Float aStartAngle,Point & aSplit,Float & aSplitAngle)2644 static inline void SplitBorderRadius(const Point& aCenter, const Size& aRadius,
2645                                      const Point& aOuterCorner,
2646                                      const Point& aInnerCorner,
2647                                      const twoFloats& aCornerMults,
2648                                      Float aStartAngle, Point& aSplit,
2649                                      Float& aSplitAngle) {
2650   Point cornerDir = aOuterCorner - aInnerCorner;
2651   if (cornerDir.x == cornerDir.y && aRadius.IsSquare()) {
2652     // optimize 45-degree intersection with circle since we can assume
2653     // the circle center lies along the intersection edge
2654     aSplit = aCenter - aCornerMults * (aRadius * Float(1.0f / M_SQRT2));
2655     aSplitAngle = aStartAngle + 0.5f * M_PI / 2.0f;
2656   } else {
2657     aSplit = IntersectBorderRadius(aCenter, aRadius, aInnerCorner, cornerDir);
2658     aSplitAngle = atan2f((aSplit.y - aCenter.y) / aRadius.height,
2659                          (aSplit.x - aCenter.x) / aRadius.width);
2660   }
2661 }
2662 
2663 // Compute the size of the skirt needed, given the color alphas
2664 // of each corner side and the slope between them.
ComputeCornerSkirtSize(Float aAlpha1,Float aAlpha2,Float aSlopeY,Float aSlopeX,Float & aSizeResult,Float & aSlopeResult)2665 static void ComputeCornerSkirtSize(Float aAlpha1, Float aAlpha2, Float aSlopeY,
2666                                    Float aSlopeX, Float& aSizeResult,
2667                                    Float& aSlopeResult) {
2668   // If either side is (almost) invisible or there is no diagonal edge,
2669   // then don't try to render a skirt.
2670   if (aAlpha1 < 0.01f || aAlpha2 < 0.01f) {
2671     return;
2672   }
2673   aSlopeX = fabs(aSlopeX);
2674   aSlopeY = fabs(aSlopeY);
2675   if (aSlopeX < 1.0e-6f || aSlopeY < 1.0e-6f) {
2676     return;
2677   }
2678 
2679   // If first and second color don't match, we need to split the corner in
2680   // half. The diagonal edges created may not have full pixel coverage given
2681   // anti-aliasing, so we need to compute a small subpixel skirt edge. This
2682   // assumes each half has half coverage to start with, and that coverage
2683   // increases as the skirt is pushed over, with the end result that we want
2684   // to roughly preserve the alpha value along this edge.
2685   // Given slope m, alphas a and A, use quadratic formula to solve for S in:
2686   //   a*(1 - 0.5*(1-S)*(1-mS))*(1 - 0.5*A) + 0.5*A = A
2687   // yielding:
2688   //   S = ((1+m) - sqrt((1+m)*(1+m) + 4*m*(1 - A/(a*(1-0.5*A))))) / (2*m)
2689   // and substitute k = (1+m)/(2*m):
2690   //   S = k - sqrt(k*k + (1 - A/(a*(1-0.5*A)))/m)
2691   Float slope = aSlopeY / aSlopeX;
2692   Float slopeScale = (1.0f + slope) / (2.0f * slope);
2693   Float discrim = slopeScale * slopeScale +
2694                   (1 - aAlpha2 / (aAlpha1 * (1.0f - 0.49f * aAlpha2))) / slope;
2695   if (discrim >= 0) {
2696     aSizeResult = slopeScale - sqrtf(discrim);
2697     aSlopeResult = slope;
2698   }
2699 }
2700 
2701 // Draws a border radius with possibly different sides.
2702 // A skirt is drawn underneath the corner intersection to hide possible
2703 // seams when anti-aliased drawing is used.
DrawBorderRadius(DrawTarget * aDrawTarget,Corner c,const Point & aOuterCorner,const Point & aInnerCorner,const twoFloats & aCornerMultPrev,const twoFloats & aCornerMultNext,const Size & aCornerDims,const Size & aOuterRadius,const Size & aInnerRadius,const DeviceColor & aFirstColor,const DeviceColor & aSecondColor,Float aSkirtSize,Float aSkirtSlope)2704 static void DrawBorderRadius(
2705     DrawTarget* aDrawTarget, Corner c, const Point& aOuterCorner,
2706     const Point& aInnerCorner, const twoFloats& aCornerMultPrev,
2707     const twoFloats& aCornerMultNext, const Size& aCornerDims,
2708     const Size& aOuterRadius, const Size& aInnerRadius,
2709     const DeviceColor& aFirstColor, const DeviceColor& aSecondColor,
2710     Float aSkirtSize, Float aSkirtSlope) {
2711   // Connect edge to outer arc start point
2712   Point outerCornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
2713   // Connect edge to outer arc end point
2714   Point outerCornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
2715   // Connect edge to inner arc start point
2716   Point innerCornerStart =
2717       outerCornerStart + aCornerMultNext * (aCornerDims - aInnerRadius);
2718   // Connect edge to inner arc end point
2719   Point innerCornerEnd =
2720       outerCornerEnd + aCornerMultPrev * (aCornerDims - aInnerRadius);
2721 
2722   // Outer arc start point
2723   Point outerArcStart = aOuterCorner + aCornerMultPrev * aOuterRadius;
2724   // Outer arc end point
2725   Point outerArcEnd = aOuterCorner + aCornerMultNext * aOuterRadius;
2726   // Inner arc start point
2727   Point innerArcStart = aInnerCorner + aCornerMultPrev * aInnerRadius;
2728   // Inner arc end point
2729   Point innerArcEnd = aInnerCorner + aCornerMultNext * aInnerRadius;
2730 
2731   // Outer radius center
2732   Point outerCenter =
2733       aOuterCorner + (aCornerMultPrev + aCornerMultNext) * aOuterRadius;
2734   // Inner radius center
2735   Point innerCenter =
2736       aInnerCorner + (aCornerMultPrev + aCornerMultNext) * aInnerRadius;
2737 
2738   RefPtr<PathBuilder> builder;
2739   RefPtr<Path> path;
2740 
2741   if (aFirstColor.a > 0) {
2742     builder = aDrawTarget->CreatePathBuilder();
2743     builder->MoveTo(outerCornerStart);
2744   }
2745 
2746   if (aFirstColor != aSecondColor) {
2747     // Start and end angles of corner quadrant
2748     Float startAngle = (c * M_PI) / 2.0f - M_PI,
2749           endAngle = startAngle + M_PI / 2.0f, outerSplitAngle, innerSplitAngle;
2750     Point outerSplit, innerSplit;
2751 
2752     // Outer half-way point
2753     SplitBorderRadius(outerCenter, aOuterRadius, aOuterCorner, aInnerCorner,
2754                       aCornerMultPrev + aCornerMultNext, startAngle, outerSplit,
2755                       outerSplitAngle);
2756     // Inner half-way point
2757     if (aInnerRadius.IsEmpty()) {
2758       innerSplit = aInnerCorner;
2759       innerSplitAngle = endAngle;
2760     } else {
2761       SplitBorderRadius(innerCenter, aInnerRadius, aOuterCorner, aInnerCorner,
2762                         aCornerMultPrev + aCornerMultNext, startAngle,
2763                         innerSplit, innerSplitAngle);
2764     }
2765 
2766     // Draw first half with first color
2767     if (aFirstColor.a > 0) {
2768       AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart,
2769                        outerSplit, startAngle, outerSplitAngle);
2770       // Draw skirt as part of first half
2771       if (aSkirtSize > 0) {
2772         builder->LineTo(outerSplit + aCornerMultNext * aSkirtSize);
2773         builder->LineTo(innerSplit -
2774                         aCornerMultPrev * (aSkirtSize * aSkirtSlope));
2775       }
2776       AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerSplit,
2777                        innerArcStart, innerSplitAngle, startAngle);
2778       if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
2779         builder->LineTo(innerCornerStart);
2780       }
2781       builder->Close();
2782       path = builder->Finish();
2783       aDrawTarget->Fill(path, ColorPattern(aFirstColor));
2784     }
2785 
2786     // Draw second half with second color
2787     if (aSecondColor.a > 0) {
2788       builder = aDrawTarget->CreatePathBuilder();
2789       builder->MoveTo(outerCornerEnd);
2790       if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
2791         builder->LineTo(innerCornerEnd);
2792       }
2793       AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd,
2794                        innerSplit, endAngle, innerSplitAngle);
2795       AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerSplit,
2796                        outerArcEnd, outerSplitAngle, endAngle);
2797       builder->Close();
2798       path = builder->Finish();
2799       aDrawTarget->Fill(path, ColorPattern(aSecondColor));
2800     }
2801   } else if (aFirstColor.a > 0) {
2802     // Draw corner with single color
2803     AcuteArcToBezier(builder.get(), outerCenter, aOuterRadius, outerArcStart,
2804                      outerArcEnd);
2805     builder->LineTo(outerCornerEnd);
2806     if ((innerArcEnd - innerCornerEnd).DotProduct(aCornerMultNext) < 0) {
2807       builder->LineTo(innerCornerEnd);
2808     }
2809     AcuteArcToBezier(builder.get(), innerCenter, aInnerRadius, innerArcEnd,
2810                      innerArcStart, -kKappaFactor);
2811     if ((innerCornerStart - innerArcStart).DotProduct(aCornerMultPrev) > 0) {
2812       builder->LineTo(innerCornerStart);
2813     }
2814     builder->Close();
2815     path = builder->Finish();
2816     aDrawTarget->Fill(path, ColorPattern(aFirstColor));
2817   }
2818 }
2819 
2820 // Draw a corner with possibly different sides.
2821 // A skirt is drawn underneath the corner intersection to hide possible
2822 // seams when anti-aliased drawing is used.
DrawCorner(DrawTarget * aDrawTarget,const Point & aOuterCorner,const Point & aInnerCorner,const twoFloats & aCornerMultPrev,const twoFloats & aCornerMultNext,const Size & aCornerDims,const DeviceColor & aFirstColor,const DeviceColor & aSecondColor,Float aSkirtSize,Float aSkirtSlope)2823 static void DrawCorner(DrawTarget* aDrawTarget, const Point& aOuterCorner,
2824                        const Point& aInnerCorner,
2825                        const twoFloats& aCornerMultPrev,
2826                        const twoFloats& aCornerMultNext,
2827                        const Size& aCornerDims, const DeviceColor& aFirstColor,
2828                        const DeviceColor& aSecondColor, Float aSkirtSize,
2829                        Float aSkirtSlope) {
2830   // Corner box start point
2831   Point cornerStart = aOuterCorner + aCornerMultPrev * aCornerDims;
2832   // Corner box end point
2833   Point cornerEnd = aOuterCorner + aCornerMultNext * aCornerDims;
2834 
2835   RefPtr<PathBuilder> builder;
2836   RefPtr<Path> path;
2837 
2838   if (aFirstColor.a > 0) {
2839     builder = aDrawTarget->CreatePathBuilder();
2840     builder->MoveTo(cornerStart);
2841   }
2842 
2843   if (aFirstColor != aSecondColor) {
2844     // Draw first half with first color
2845     if (aFirstColor.a > 0) {
2846       builder->LineTo(aOuterCorner);
2847       // Draw skirt as part of first half
2848       if (aSkirtSize > 0) {
2849         builder->LineTo(aOuterCorner + aCornerMultNext * aSkirtSize);
2850         builder->LineTo(aInnerCorner -
2851                         aCornerMultPrev * (aSkirtSize * aSkirtSlope));
2852       }
2853       builder->LineTo(aInnerCorner);
2854       builder->Close();
2855       path = builder->Finish();
2856       aDrawTarget->Fill(path, ColorPattern(aFirstColor));
2857     }
2858 
2859     // Draw second half with second color
2860     if (aSecondColor.a > 0) {
2861       builder = aDrawTarget->CreatePathBuilder();
2862       builder->MoveTo(cornerEnd);
2863       builder->LineTo(aInnerCorner);
2864       builder->LineTo(aOuterCorner);
2865       builder->Close();
2866       path = builder->Finish();
2867       aDrawTarget->Fill(path, ColorPattern(aSecondColor));
2868     }
2869   } else if (aFirstColor.a > 0) {
2870     // Draw corner with single color
2871     builder->LineTo(aOuterCorner);
2872     builder->LineTo(cornerEnd);
2873     builder->LineTo(aInnerCorner);
2874     builder->Close();
2875     path = builder->Finish();
2876     aDrawTarget->Fill(path, ColorPattern(aFirstColor));
2877   }
2878 }
2879 
DrawSolidBorder()2880 void nsCSSBorderRenderer::DrawSolidBorder() {
2881   const twoFloats cornerMults[4] = {{-1, 0}, {0, -1}, {+1, 0}, {0, +1}};
2882 
2883   const twoFloats centerAdjusts[4] = {
2884       {0, +0.5}, {-0.5, 0}, {0, -0.5}, {+0.5, 0}};
2885 
2886   RectCornerRadii innerRadii;
2887   ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
2888 
2889   Rect strokeRect = mOuterRect;
2890   strokeRect.Deflate(Margin(mBorderWidths[0] / 2.0, mBorderWidths[1] / 2.0,
2891                             mBorderWidths[2] / 2.0, mBorderWidths[3] / 2.0));
2892 
2893   for (const auto i : mozilla::AllPhysicalSides()) {
2894     // We now draw the current side and the CW corner following it.
2895     // The CCW corner of this side was already drawn in the previous iteration.
2896     // The side will be drawn as an explicit stroke, and the CW corner will be
2897     // filled separately.
2898     // If the next side does not have a matching color, then we split the
2899     // corner into two halves, one of each side's color and draw both.
2900     // Thus, the CCW corner of the next side will end up drawn here.
2901 
2902     // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
2903     Corner c = Corner((i + 1) % 4);
2904     Corner prevCorner = Corner(i);
2905 
2906     // i+2 and i+3 respectively.  These are used to index into the corner
2907     // multiplier table, and were deduced by calculating out the long form
2908     // of each corner and finding a pattern in the signs and values.
2909     int i1 = (i + 1) % 4;
2910     int i2 = (i + 2) % 4;
2911     int i3 = (i + 3) % 4;
2912 
2913     Float sideWidth = 0.0f;
2914     DeviceColor firstColor, secondColor;
2915     if (IsVisible(mBorderStyles[i]) && mBorderWidths[i]) {
2916       // draw the side since it is visible
2917       sideWidth = mBorderWidths[i];
2918       firstColor = ToDeviceColor(mBorderColors[i]);
2919       // if the next side is visible, use its color for corner
2920       secondColor = IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]
2921                         ? ToDeviceColor(mBorderColors[i1])
2922                         : firstColor;
2923     } else if (IsVisible(mBorderStyles[i1]) && mBorderWidths[i1]) {
2924       // assign next side's color to both corner sides
2925       firstColor = ToDeviceColor(mBorderColors[i1]);
2926       secondColor = firstColor;
2927     } else {
2928       // neither side is visible, so nothing to do
2929       continue;
2930     }
2931 
2932     Point outerCorner = mOuterRect.AtCorner(c);
2933     Point innerCorner = mInnerRect.AtCorner(c);
2934 
2935     // start and end points of border side stroke between corners
2936     Point sideStart = mOuterRect.AtCorner(prevCorner) +
2937                       cornerMults[i2] * mBorderCornerDimensions[prevCorner];
2938     Point sideEnd = outerCorner + cornerMults[i] * mBorderCornerDimensions[c];
2939     // check if the side is visible and not inverted
2940     if (sideWidth > 0 && firstColor.a > 0 &&
2941         -(sideEnd - sideStart).DotProduct(cornerMults[i]) > 0) {
2942       mDrawTarget->StrokeLine(sideStart + centerAdjusts[i] * sideWidth,
2943                               sideEnd + centerAdjusts[i] * sideWidth,
2944                               ColorPattern(firstColor),
2945                               StrokeOptions(sideWidth));
2946     }
2947 
2948     Float skirtSize = 0.0f, skirtSlope = 0.0f;
2949     // the sides don't match, so compute a skirt
2950     if (firstColor != secondColor &&
2951         mPresContext->Type() != nsPresContext::eContext_Print) {
2952       Point cornerDir = outerCorner - innerCorner;
2953       ComputeCornerSkirtSize(
2954           firstColor.a, secondColor.a, cornerDir.DotProduct(cornerMults[i]),
2955           cornerDir.DotProduct(cornerMults[i3]), skirtSize, skirtSlope);
2956     }
2957 
2958     if (!mBorderRadii[c].IsEmpty()) {
2959       // the corner has a border radius
2960       DrawBorderRadius(mDrawTarget, c, outerCorner, innerCorner, cornerMults[i],
2961                        cornerMults[i3], mBorderCornerDimensions[c],
2962                        mBorderRadii[c], innerRadii[c], firstColor, secondColor,
2963                        skirtSize, skirtSlope);
2964     } else if (!mBorderCornerDimensions[c].IsEmpty()) {
2965       // a corner with no border radius
2966       DrawCorner(mDrawTarget, outerCorner, innerCorner, cornerMults[i],
2967                  cornerMults[i3], mBorderCornerDimensions[c], firstColor,
2968                  secondColor, skirtSize, skirtSlope);
2969     }
2970   }
2971 }
2972 
DrawBorders()2973 void nsCSSBorderRenderer::DrawBorders() {
2974   if (mAllBordersSameStyle && (mBorderStyles[0] == StyleBorderStyle::None ||
2975                                mBorderStyles[0] == StyleBorderStyle::Hidden ||
2976                                mBorderColors[0] == NS_RGBA(0, 0, 0, 0))) {
2977     // All borders are the same style, and the style is either none or hidden,
2978     // or the color is transparent.
2979     return;
2980   }
2981 
2982   if (mAllBordersSameWidth && mBorderWidths[0] == 0.0) {
2983     // Some of the mAllBordersSameWidth codepaths depend on the border
2984     // width being greater than zero.
2985     return;
2986   }
2987 
2988   AutoRestoreTransform autoRestoreTransform;
2989   Matrix mat = mDrawTarget->GetTransform();
2990 
2991   // Clamp the CTM to be pixel-aligned; we do this only
2992   // for translation-only matrices now, but we could do it
2993   // if the matrix has just a scale as well.  We should not
2994   // do it if there's a rotation.
2995   if (mat.HasNonTranslation()) {
2996     if (!mat.HasNonAxisAlignedTransform()) {
2997       // Scale + transform. Avoid stroke fast-paths so that we have a chance
2998       // of snapping to pixel boundaries.
2999       mAvoidStroke = true;
3000     }
3001   } else {
3002     mat._31 = floor(mat._31 + 0.5);
3003     mat._32 = floor(mat._32 + 0.5);
3004     autoRestoreTransform.Init(mDrawTarget);
3005     mDrawTarget->SetTransform(mat);
3006 
3007     // round mOuterRect and mInnerRect; they're already an integer
3008     // number of pixels apart and should stay that way after
3009     // rounding. We don't do this if there's a scale in the current transform
3010     // since this loses information that might be relevant when we're scaling.
3011     mOuterRect.Round();
3012     mInnerRect.Round();
3013   }
3014 
3015   // Initial values only used when the border colors/widths are all the same:
3016   ColorPattern color(ToDeviceColor(mBorderColors[eSideTop]));
3017   StrokeOptions strokeOptions(mBorderWidths[eSideTop]);  // stroke width
3018 
3019   // First there's a couple of 'special cases' that have specifically optimized
3020   // drawing paths, when none of these can be used we move on to the generalized
3021   // border drawing code.
3022   if (mAllBordersSameStyle && mAllBordersSameWidth &&
3023       mBorderStyles[0] == StyleBorderStyle::Solid && mNoBorderRadius &&
3024       !mAvoidStroke) {
3025     // Very simple case.
3026     Rect rect = mOuterRect;
3027     rect.Deflate(mBorderWidths[0] / 2.0);
3028     mDrawTarget->StrokeRect(rect, color, strokeOptions);
3029     return;
3030   }
3031 
3032   if (mAllBordersSameStyle && mBorderStyles[0] == StyleBorderStyle::Solid &&
3033       !mAvoidStroke && !mNoBorderRadius) {
3034     // Relatively simple case.
3035     RoundedRect borderInnerRect(mOuterRect, mBorderRadii);
3036     borderInnerRect.Deflate(mBorderWidths[eSideTop], mBorderWidths[eSideBottom],
3037                             mBorderWidths[eSideLeft],
3038                             mBorderWidths[eSideRight]);
3039 
3040     // Instead of stroking we just use two paths: an inner and an outer.
3041     // This allows us to draw borders that we couldn't when stroking. For
3042     // example, borders with a border width >= the border radius. (i.e. when
3043     // there are square corners on the inside)
3044     //
3045     // Further, this approach can be more efficient because the backend
3046     // doesn't need to compute an offset curve to stroke the path. We know that
3047     // the rounded parts are elipses we can offset exactly and can just compute
3048     // a new cubic approximation.
3049     RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
3050     AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
3051     AppendRoundedRectToPath(builder, borderInnerRect.rect,
3052                             borderInnerRect.corners, false);
3053     RefPtr<Path> path = builder->Finish();
3054     mDrawTarget->Fill(path, color);
3055     return;
3056   }
3057 
3058   const bool allBordersSolid = AllBordersSolid();
3059 
3060   // This leaves the border corners non-interpolated for single width borders.
3061   // Doing this is slightly faster and shouldn't be a problem visually.
3062   if (allBordersSolid && mAllBordersSameWidth && mBorderWidths[0] == 1 &&
3063       mNoBorderRadius && !mAvoidStroke) {
3064     DrawSingleWidthSolidBorder();
3065     return;
3066   }
3067 
3068   if (allBordersSolid && !mAvoidStroke) {
3069     DrawSolidBorder();
3070     return;
3071   }
3072 
3073   PrintAsString(" mOuterRect: ");
3074   PrintAsString(mOuterRect);
3075   PrintAsStringNewline();
3076   PrintAsString(" mInnerRect: ");
3077   PrintAsString(mInnerRect);
3078   PrintAsStringNewline();
3079   PrintAsFormatString(" mBorderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n",
3080                       mBorderColors[0], mBorderColors[1], mBorderColors[2],
3081                       mBorderColors[3]);
3082 
3083   // if conditioning the outside rect failed, then bail -- the outside
3084   // rect is supposed to enclose the entire border
3085   {
3086     gfxRect outerRect = ThebesRect(mOuterRect);
3087     gfxUtils::ConditionRect(outerRect);
3088     if (outerRect.IsEmpty()) {
3089       return;
3090     }
3091     mOuterRect = ToRect(outerRect);
3092 
3093     gfxRect innerRect = ThebesRect(mInnerRect);
3094     gfxUtils::ConditionRect(innerRect);
3095     mInnerRect = ToRect(innerRect);
3096   }
3097 
3098   SideBits dashedSides = SideBits::eNone;
3099   bool forceSeparateCorners = false;
3100 
3101   for (const auto i : mozilla::AllPhysicalSides()) {
3102     StyleBorderStyle style = mBorderStyles[i];
3103     if (style == StyleBorderStyle::Dashed ||
3104         style == StyleBorderStyle::Dotted) {
3105       // we need to draw things separately for dashed/dotting
3106       forceSeparateCorners = true;
3107       dashedSides |= static_cast<mozilla::SideBits>(1 << i);
3108     }
3109   }
3110 
3111   PrintAsFormatString(" mAllBordersSameStyle: %d dashedSides: 0x%02x\n",
3112                       mAllBordersSameStyle,
3113                       static_cast<unsigned int>(dashedSides));
3114 
3115   if (mAllBordersSameStyle && !forceSeparateCorners) {
3116     /* Draw everything in one go */
3117     DrawBorderSides(SideBits::eAll);
3118     PrintAsStringNewline("---------------- (1)");
3119   } else {
3120     AUTO_PROFILER_LABEL("nsCSSBorderRenderer::DrawBorders:multipass", GRAPHICS);
3121 
3122     /* We have more than one pass to go.  Draw the corners separately from the
3123      * sides. */
3124 
3125     // The corner is going to have negligible size if its two adjacent border
3126     // sides are only 1px wide and there is no border radius.  In that case we
3127     // skip the overhead of painting the corner by setting the width or height
3128     // of the corner to zero, which effectively extends one of the corner's
3129     // adjacent border sides.  We extend the longer adjacent side so that
3130     // opposite sides will be the same length, which is necessary for opposite
3131     // dashed/dotted sides to be symmetrical.
3132     //
3133     //   if width > height
3134     //     +--+--------------+--+    +--------------------+
3135     //     |  |              |  |    |                    |
3136     //     +--+--------------+--+    +--+--------------+--+
3137     //     |  |              |  |    |  |              |  |
3138     //     |  |              |  | => |  |              |  |
3139     //     |  |              |  |    |  |              |  |
3140     //     +--+--------------+--+    +--+--------------+--+
3141     //     |  |              |  |    |                    |
3142     //     +--+--------------+--+    +--------------------+
3143     //
3144     //   if width <= height
3145     //     +--+--------+--+    +--+--------+--+
3146     //     |  |        |  |    |  |        |  |
3147     //     +--+--------+--+    |  +--------+  |
3148     //     |  |        |  |    |  |        |  |
3149     //     |  |        |  |    |  |        |  |
3150     //     |  |        |  |    |  |        |  |
3151     //     |  |        |  | => |  |        |  |
3152     //     |  |        |  |    |  |        |  |
3153     //     |  |        |  |    |  |        |  |
3154     //     |  |        |  |    |  |        |  |
3155     //     +--+--------+--+    |  +--------+  |
3156     //     |  |        |  |    |  |        |  |
3157     //     +--+--------+--+    +--+--------+--+
3158     //
3159     // Note that if we have different border widths we could end up with
3160     // opposite sides of different length.  For example, if the left and
3161     // bottom borders are 2px wide instead of 1px, we will end up doing
3162     // something like:
3163     //
3164     //     +----+------------+--+    +----+---------------+
3165     //     |    |            |  |    |    |               |
3166     //     +----+------------+--+    +----+------------+--+
3167     //     |    |            |  |    |    |            |  |
3168     //     |    |            |  | => |    |            |  |
3169     //     |    |            |  |    |    |            |  |
3170     //     +----+------------+--+    +----+------------+--+
3171     //     |    |            |  |    |    |            |  |
3172     //     |    |            |  |    |    |            |  |
3173     //     +----+------------+--+    +----+------------+--+
3174     //
3175     // XXX Should we only do this optimization if |mAllBordersSameWidth| is
3176     // true?
3177     //
3178     // XXX In fact is this optimization even worth the complexity it adds to
3179     // the code?  1px wide dashed borders are not overly common, and drawing
3180     // corners for them is not that expensive.
3181     for (const auto corner : mozilla::AllPhysicalCorners()) {
3182       const mozilla::Side sides[2] = {mozilla::Side(corner), PREV_SIDE(corner)};
3183 
3184       if (!IsZeroSize(mBorderRadii[corner])) {
3185         continue;
3186       }
3187 
3188       if (mBorderWidths[sides[0]] == 1.0 && mBorderWidths[sides[1]] == 1.0) {
3189         if (mOuterRect.Width() > mOuterRect.Height()) {
3190           mBorderCornerDimensions[corner].width = 0.0;
3191         } else {
3192           mBorderCornerDimensions[corner].height = 0.0;
3193         }
3194       }
3195     }
3196 
3197     // First, the corners
3198     for (const auto corner : mozilla::AllPhysicalCorners()) {
3199       // if there's no corner, don't do all this work for it
3200       if (IsZeroSize(mBorderCornerDimensions[corner])) {
3201         continue;
3202       }
3203 
3204       const int sides[2] = {corner, PREV_SIDE(corner)};
3205       SideBits sideBits =
3206           static_cast<SideBits>((1 << sides[0]) | (1 << sides[1]));
3207 
3208       bool simpleCornerStyle = AreBorderSideFinalStylesSame(sideBits);
3209 
3210       // If we don't have anything complex going on in this corner,
3211       // then we can just fill the corner with a solid color, and avoid
3212       // the potentially expensive clip.
3213       if (simpleCornerStyle && IsZeroSize(mBorderRadii[corner]) &&
3214           IsSolidCornerStyle(mBorderStyles[sides[0]], corner)) {
3215         sRGBColor color = MakeBorderColor(
3216             mBorderColors[sides[0]],
3217             BorderColorStyleForSolidCorner(mBorderStyles[sides[0]], corner));
3218         mDrawTarget->FillRect(GetCornerRect(corner),
3219                               ColorPattern(ToDeviceColor(color)));
3220         continue;
3221       }
3222 
3223       // clip to the corner
3224       mDrawTarget->PushClipRect(GetCornerRect(corner));
3225 
3226       if (simpleCornerStyle) {
3227         // we don't need a group for this corner, the sides are the same,
3228         // but we weren't able to render just a solid block for the corner.
3229         DrawBorderSides(sideBits);
3230       } else {
3231         // Sides are different.  We could draw using OP_ADD to
3232         // get correct color blending behaviour at the seam.  We'd need
3233         // to do it in an offscreen surface to ensure that we're
3234         // always compositing on transparent black.  If the colors
3235         // don't have transparency and the current destination surface
3236         // has an alpha channel, we could just clear the region and
3237         // avoid the temporary, but that situation doesn't happen all
3238         // that often in practice (we double buffer to no-alpha
3239         // surfaces). We choose just to seam though, as the performance
3240         // advantages outway the modest easthetic improvement.
3241 
3242         for (int cornerSide = 0; cornerSide < 2; cornerSide++) {
3243           mozilla::Side side = mozilla::Side(sides[cornerSide]);
3244           StyleBorderStyle style = mBorderStyles[side];
3245 
3246           PrintAsFormatString("corner: %d cornerSide: %d side: %d style: %d\n",
3247                               corner, cornerSide, side,
3248                               static_cast<int>(style));
3249 
3250           RefPtr<Path> path = GetSideClipSubPath(side);
3251           mDrawTarget->PushClip(path);
3252 
3253           DrawBorderSides(static_cast<mozilla::SideBits>(1 << side));
3254 
3255           mDrawTarget->PopClip();
3256         }
3257       }
3258 
3259       mDrawTarget->PopClip();
3260 
3261       PrintAsStringNewline();
3262     }
3263 
3264     // in the case of a single-unit border, we already munged the
3265     // corners up above; so we can just draw the top left and bottom
3266     // right sides separately, if they're the same.
3267     //
3268     // We need to check for mNoBorderRadius, because when there is
3269     // one, FillSolidBorder always draws the full rounded rectangle
3270     // and expects there to be a clip in place.
3271     SideBits alreadyDrawnSides = SideBits::eNone;
3272     if (mOneUnitBorder && mNoBorderRadius &&
3273         (dashedSides & (SideBits::eTop | SideBits::eLeft)) == SideBits::eNone) {
3274       bool tlBordersSameStyle =
3275           AreBorderSideFinalStylesSame(SideBits::eTop | SideBits::eLeft);
3276       bool brBordersSameStyle =
3277           AreBorderSideFinalStylesSame(SideBits::eBottom | SideBits::eRight);
3278 
3279       if (tlBordersSameStyle) {
3280         DrawBorderSides(SideBits::eTop | SideBits::eLeft);
3281         alreadyDrawnSides |= (SideBits::eTop | SideBits::eLeft);
3282       }
3283 
3284       if (brBordersSameStyle &&
3285           (dashedSides & (SideBits::eBottom | SideBits::eRight)) ==
3286               SideBits::eNone) {
3287         DrawBorderSides(SideBits::eBottom | SideBits::eRight);
3288         alreadyDrawnSides |= (SideBits::eBottom | SideBits::eRight);
3289       }
3290     }
3291 
3292     // We're done with the corners, now draw the sides.
3293     for (const auto side : mozilla::AllPhysicalSides()) {
3294       // if we drew it above, skip it
3295       if (alreadyDrawnSides & static_cast<mozilla::SideBits>(1 << side)) {
3296         continue;
3297       }
3298 
3299       // If there's no border on this side, skip it
3300       if (mBorderWidths[side] == 0.0 ||
3301           mBorderStyles[side] == StyleBorderStyle::Hidden ||
3302           mBorderStyles[side] == StyleBorderStyle::None) {
3303         continue;
3304       }
3305 
3306       if (dashedSides & static_cast<mozilla::SideBits>(1 << side)) {
3307         // Dashed sides will always draw just the part ignoring the
3308         // corners for the side, so no need to clip.
3309         DrawDashedOrDottedSide(side);
3310 
3311         PrintAsStringNewline("---------------- (d)");
3312         continue;
3313       }
3314 
3315       // Undashed sides will currently draw the entire side,
3316       // including parts that would normally be covered by a corner,
3317       // so we need to clip.
3318       //
3319       // XXX Optimization -- it would be good to make this work like
3320       // DrawDashedOrDottedSide, and have a DrawOneSide function that just
3321       // draws one side and not the corners, because then we can
3322       // avoid the potentially expensive clip.
3323       mDrawTarget->PushClipRect(GetSideClipWithoutCornersRect(side));
3324 
3325       DrawBorderSides(static_cast<mozilla::SideBits>(1 << side));
3326 
3327       mDrawTarget->PopClip();
3328 
3329       PrintAsStringNewline("---------------- (*)");
3330     }
3331   }
3332 }
3333 
CreateWebRenderCommands(nsDisplayItem * aItem,wr::DisplayListBuilder & aBuilder,wr::IpcResourceUpdateQueue & aResources,const layers::StackingContextHelper & aSc)3334 void nsCSSBorderRenderer::CreateWebRenderCommands(
3335     nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
3336     wr::IpcResourceUpdateQueue& aResources,
3337     const layers::StackingContextHelper& aSc) {
3338   LayoutDeviceRect outerRect = LayoutDeviceRect::FromUnknownRect(mOuterRect);
3339   wr::LayoutRect roundedRect = wr::ToLayoutRect(outerRect);
3340   wr::LayoutRect clipRect = roundedRect;
3341   wr::BorderSide side[4];
3342   for (const auto i : mozilla::AllPhysicalSides()) {
3343     side[i] =
3344         wr::ToBorderSide(ToDeviceColor(mBorderColors[i]), mBorderStyles[i]);
3345   }
3346 
3347   wr::BorderRadius borderRadius = wr::ToBorderRadius(mBorderRadii);
3348 
3349   if (mLocalClip) {
3350     LayoutDeviceRect localClip =
3351         LayoutDeviceRect::FromUnknownRect(mLocalClip.value());
3352     clipRect = wr::ToLayoutRect(localClip.Intersect(outerRect));
3353   }
3354 
3355   Range<const wr::BorderSide> wrsides(side, 4);
3356   aBuilder.PushBorder(roundedRect, clipRect, mBackfaceIsVisible,
3357                       wr::ToBorderWidths(mBorderWidths[0], mBorderWidths[1],
3358                                          mBorderWidths[2], mBorderWidths[3]),
3359                       wrsides, borderRadius);
3360 }
3361 
3362 /* static */
3363 Maybe<nsCSSBorderImageRenderer>
CreateBorderImageRenderer(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,const nsRect & aDirtyRect,Sides aSkipSides,uint32_t aFlags,ImgDrawResult * aDrawResult)3364 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
3365     nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
3366     const nsStyleBorder& aStyleBorder, const nsRect& aDirtyRect,
3367     Sides aSkipSides, uint32_t aFlags, ImgDrawResult* aDrawResult) {
3368   MOZ_ASSERT(aDrawResult);
3369 
3370   if (aDirtyRect.IsEmpty()) {
3371     *aDrawResult = ImgDrawResult::SUCCESS;
3372     return Nothing();
3373   }
3374 
3375   nsImageRenderer imgRenderer(aForFrame, &aStyleBorder.mBorderImageSource,
3376                               aFlags);
3377   if (!imgRenderer.PrepareImage()) {
3378     *aDrawResult = imgRenderer.PrepareResult();
3379     return Nothing();
3380   }
3381 
3382   // We should always get here with the frame's border, but we may construct an
3383   // nsStyleBorder om the stack to deal with :visited and other shenaningans.
3384   //
3385   // We always copy the border image and such from the non-visited one, so
3386   // there's no need to do anything with it.
3387   MOZ_ASSERT(aStyleBorder.GetBorderImageRequest() ==
3388              aForFrame->StyleBorder()->GetBorderImageRequest());
3389 
3390   nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea, aStyleBorder,
3391                                     aSkipSides, imgRenderer);
3392   *aDrawResult = ImgDrawResult::SUCCESS;
3393   return Some(renderer);
3394 }
3395 
DrawBorderImage(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect)3396 ImgDrawResult nsCSSBorderImageRenderer::DrawBorderImage(
3397     nsPresContext* aPresContext, gfxContext& aRenderingContext,
3398     nsIFrame* aForFrame, const nsRect& aDirtyRect) {
3399   // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
3400   // in case we need it.
3401   gfxContextAutoSaveRestore autoSR;
3402 
3403   if (!mClip.IsEmpty()) {
3404     autoSR.EnsureSaved(&aRenderingContext);
3405     aRenderingContext.Clip(NSRectToSnappedRect(
3406         mClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
3407         *aRenderingContext.GetDrawTarget()));
3408   }
3409 
3410   // intrinsicSize.CanComputeConcreteSize() return false means we can not
3411   // read intrinsic size from aStyleBorder.mBorderImageSource.
3412   // In this condition, we pass imageSize(a resolved size comes from
3413   // default sizing algorithm) to renderer as the viewport size.
3414   CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
3415   Maybe<nsSize> svgViewportSize =
3416       intrinsicSize.CanComputeConcreteSize() ? Nothing() : Some(mImageSize);
3417   bool hasIntrinsicRatio = intrinsicSize.HasRatio();
3418   mImageRenderer.PurgeCacheForViewportChange(svgViewportSize,
3419                                              hasIntrinsicRatio);
3420 
3421   // These helper tables recharacterize the 'slice' and 'width' margins
3422   // in a more convenient form: they are the x/y/width/height coords
3423   // required for various bands of the border, and they have been transformed
3424   // to be relative to the innerRect (for 'slice') or the page (for 'border').
3425   enum { LEFT, MIDDLE, RIGHT, TOP = LEFT, BOTTOM = RIGHT };
3426   const nscoord borderX[3] = {
3427       mArea.x + 0,
3428       mArea.x + mWidths.left,
3429       mArea.x + mArea.width - mWidths.right,
3430   };
3431   const nscoord borderY[3] = {
3432       mArea.y + 0,
3433       mArea.y + mWidths.top,
3434       mArea.y + mArea.height - mWidths.bottom,
3435   };
3436   const nscoord borderWidth[3] = {
3437       mWidths.left,
3438       mArea.width - mWidths.left - mWidths.right,
3439       mWidths.right,
3440   };
3441   const nscoord borderHeight[3] = {
3442       mWidths.top,
3443       mArea.height - mWidths.top - mWidths.bottom,
3444       mWidths.bottom,
3445   };
3446   const int32_t sliceX[3] = {
3447       0,
3448       mSlice.left,
3449       mImageSize.width - mSlice.right,
3450   };
3451   const int32_t sliceY[3] = {
3452       0,
3453       mSlice.top,
3454       mImageSize.height - mSlice.bottom,
3455   };
3456   const int32_t sliceWidth[3] = {
3457       mSlice.left,
3458       std::max(mImageSize.width - mSlice.left - mSlice.right, 0),
3459       mSlice.right,
3460   };
3461   const int32_t sliceHeight[3] = {
3462       mSlice.top,
3463       std::max(mImageSize.height - mSlice.top - mSlice.bottom, 0),
3464       mSlice.bottom,
3465   };
3466 
3467   ImgDrawResult result = ImgDrawResult::SUCCESS;
3468 
3469   for (int i = LEFT; i <= RIGHT; i++) {
3470     for (int j = TOP; j <= BOTTOM; j++) {
3471       StyleBorderImageRepeat fillStyleH, fillStyleV;
3472       nsSize unitSize;
3473 
3474       if (i == MIDDLE && j == MIDDLE) {
3475         // Discard the middle portion unless set to fill.
3476         if (!mFill) {
3477           continue;
3478         }
3479 
3480         // css-background:
3481         //     The middle image's width is scaled by the same factor as the
3482         //     top image unless that factor is zero or infinity, in which
3483         //     case the scaling factor of the bottom is substituted, and
3484         //     failing that, the width is not scaled. The height of the
3485         //     middle image is scaled by the same factor as the left image
3486         //     unless that factor is zero or infinity, in which case the
3487         //     scaling factor of the right image is substituted, and failing
3488         //     that, the height is not scaled.
3489         gfxFloat hFactor, vFactor;
3490 
3491         if (0 < mWidths.left && 0 < mSlice.left) {
3492           vFactor = gfxFloat(mWidths.left) / mSlice.left;
3493         } else if (0 < mWidths.right && 0 < mSlice.right) {
3494           vFactor = gfxFloat(mWidths.right) / mSlice.right;
3495         } else {
3496           vFactor = 1;
3497         }
3498 
3499         if (0 < mWidths.top && 0 < mSlice.top) {
3500           hFactor = gfxFloat(mWidths.top) / mSlice.top;
3501         } else if (0 < mWidths.bottom && 0 < mSlice.bottom) {
3502           hFactor = gfxFloat(mWidths.bottom) / mSlice.bottom;
3503         } else {
3504           hFactor = 1;
3505         }
3506 
3507         unitSize.width = sliceWidth[i] * hFactor;
3508         unitSize.height = sliceHeight[j] * vFactor;
3509         fillStyleH = mRepeatModeHorizontal;
3510         fillStyleV = mRepeatModeVertical;
3511 
3512       } else if (i == MIDDLE) {  // top, bottom
3513         // Sides are always stretched to the thickness of their border,
3514         // and stretched proportionately on the other axis.
3515         gfxFloat factor;
3516         if (0 < borderHeight[j] && 0 < sliceHeight[j]) {
3517           factor = gfxFloat(borderHeight[j]) / sliceHeight[j];
3518         } else {
3519           factor = 1;
3520         }
3521 
3522         unitSize.width = sliceWidth[i] * factor;
3523         unitSize.height = borderHeight[j];
3524         fillStyleH = mRepeatModeHorizontal;
3525         fillStyleV = StyleBorderImageRepeat::Stretch;
3526 
3527       } else if (j == MIDDLE) {  // left, right
3528         gfxFloat factor;
3529         if (0 < borderWidth[i] && 0 < sliceWidth[i]) {
3530           factor = gfxFloat(borderWidth[i]) / sliceWidth[i];
3531         } else {
3532           factor = 1;
3533         }
3534 
3535         unitSize.width = borderWidth[i];
3536         unitSize.height = sliceHeight[j] * factor;
3537         fillStyleH = StyleBorderImageRepeat::Stretch;
3538         fillStyleV = mRepeatModeVertical;
3539 
3540       } else {
3541         // Corners are always stretched to fit the corner.
3542         unitSize.width = borderWidth[i];
3543         unitSize.height = borderHeight[j];
3544         fillStyleH = StyleBorderImageRepeat::Stretch;
3545         fillStyleV = StyleBorderImageRepeat::Stretch;
3546       }
3547 
3548       nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
3549       nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
3550       if (subArea.IsEmpty()) continue;
3551 
3552       nsIntRect intSubArea = subArea.ToOutsidePixels(AppUnitsPerCSSPixel());
3553       result &= mImageRenderer.DrawBorderImageComponent(
3554           aPresContext, aRenderingContext, aDirtyRect, destArea,
3555           CSSIntRect(intSubArea.x, intSubArea.y, intSubArea.width,
3556                      intSubArea.height),
3557           fillStyleH, fillStyleV, unitSize, j * (RIGHT + 1) + i,
3558           svgViewportSize, hasIntrinsicRatio);
3559     }
3560   }
3561 
3562   return result;
3563 }
3564 
CreateWebRenderCommands(nsDisplayItem * aItem,nsIFrame * aForFrame,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)3565 ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands(
3566     nsDisplayItem* aItem, nsIFrame* aForFrame,
3567     mozilla::wr::DisplayListBuilder& aBuilder,
3568     mozilla::wr::IpcResourceUpdateQueue& aResources,
3569     const mozilla::layers::StackingContextHelper& aSc,
3570     mozilla::layers::RenderRootStateManager* aManager,
3571     nsDisplayListBuilder* aDisplayListBuilder) {
3572   if (!mImageRenderer.IsReady()) {
3573     return ImgDrawResult::NOT_READY;
3574   }
3575 
3576   float widths[4];
3577   float slice[4];
3578   float outset[4];
3579   const int32_t appUnitsPerDevPixel =
3580       aForFrame->PresContext()->AppUnitsPerDevPixel();
3581   for (const auto i : mozilla::AllPhysicalSides()) {
3582     slice[i] = (float)(mSlice.Side(i)) / appUnitsPerDevPixel;
3583     widths[i] = (float)(mWidths.Side(i)) / appUnitsPerDevPixel;
3584 
3585     // The outset is already taken into account by the adjustments to mArea
3586     // in our constructor. We use mArea as our dest rect so we can just supply
3587     // zero outsets to WebRender.
3588     outset[i] = 0.0f;
3589   }
3590 
3591   LayoutDeviceRect destRect =
3592       LayoutDeviceRect::FromAppUnits(mArea, appUnitsPerDevPixel);
3593   destRect.Round();
3594   wr::LayoutRect dest = wr::ToLayoutRect(destRect);
3595 
3596   wr::LayoutRect clip = dest;
3597   if (!mClip.IsEmpty()) {
3598     LayoutDeviceRect clipRect =
3599         LayoutDeviceRect::FromAppUnits(mClip, appUnitsPerDevPixel);
3600     clip = wr::ToLayoutRect(clipRect);
3601   }
3602 
3603   ImgDrawResult drawResult = ImgDrawResult::SUCCESS;
3604   switch (mImageRenderer.GetType()) {
3605     case StyleImage::Tag::Rect:
3606     case StyleImage::Tag::Url: {
3607       RefPtr<imgIContainer> img = mImageRenderer.GetImage();
3608       if (!img || img->GetType() == imgIContainer::TYPE_VECTOR) {
3609         // Vector images will redraw each segment of the border up to 8 times.
3610         // We draw using a restricted region derived from the segment's clip and
3611         // scale the image accordingly (see ClippedImage::Draw). If we follow
3612         // this convention as is for WebRender, we will need to rasterize the
3613         // entire vector image scaled up without the restriction region, which
3614         // means our main thread CPU and memory footprints will be much higher.
3615         // Ideally we would be able to provide a raster image for each segment
3616         // of the border. For now we use fallback.
3617         return ImgDrawResult::NOT_SUPPORTED;
3618       }
3619 
3620       uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
3621 
3622       LayoutDeviceRect imageRect = LayoutDeviceRect::FromAppUnits(
3623           nsRect(nsPoint(), mImageRenderer.GetSize()), appUnitsPerDevPixel);
3624 
3625       Maybe<SVGImageContext> svgContext;
3626       Maybe<ImageIntRegion> region;
3627       gfx::IntSize decodeSize =
3628           nsLayoutUtils::ComputeImageContainerDrawingParameters(
3629               img, aForFrame, imageRect, imageRect, aSc, flags, svgContext,
3630               region);
3631 
3632       RefPtr<WebRenderImageProvider> provider;
3633       drawResult = img->GetImageProvider(aManager->LayerManager(), decodeSize,
3634                                          svgContext, region, flags,
3635                                          getter_AddRefs(provider));
3636 
3637       Maybe<wr::ImageKey> key =
3638           aManager->CommandBuilder().CreateImageProviderKey(
3639               aItem, provider, drawResult, aResources);
3640       if (key.isNothing()) {
3641         break;
3642       }
3643 
3644       auto rendering =
3645           wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
3646       if (mFill) {
3647         float epsilon = 0.0001;
3648         bool noVerticalBorders = widths[0] <= epsilon && widths[2] < epsilon;
3649         bool noHorizontalBorders = widths[1] <= epsilon && widths[3] < epsilon;
3650 
3651         // Border image with no border. It's a little silly but WebRender
3652         // currently does not handle this. We could fall back to a blob image
3653         // but there are reftests that are sensible to the test going through a
3654         // blob while the reference doesn't.
3655         if (noVerticalBorders && noHorizontalBorders) {
3656           aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), false,
3657                              rendering, key.value());
3658           break;
3659         }
3660 
3661         // Fall-back if we want to fill the middle area and opposite edges are
3662         // both empty.
3663         // TODO(bug 1609893): moving some of the repetition handling code out
3664         // of the image shader will make it easier to handle these cases
3665         // properly.
3666         if (noHorizontalBorders || noVerticalBorders) {
3667           return ImgDrawResult::NOT_SUPPORTED;
3668         }
3669       }
3670 
3671       wr::WrBorderImage params{
3672           wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
3673           key.value(),
3674           rendering,
3675           mImageSize.width / appUnitsPerDevPixel,
3676           mImageSize.height / appUnitsPerDevPixel,
3677           mFill,
3678           wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]),
3679           wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], outset[3]),
3680           wr::ToRepeatMode(mRepeatModeHorizontal),
3681           wr::ToRepeatMode(mRepeatModeVertical)};
3682 
3683       aBuilder.PushBorderImage(dest, clip, !aItem->BackfaceIsHidden(), params);
3684       break;
3685     }
3686     case StyleImage::Tag::Gradient: {
3687       const StyleGradient& gradient = *mImageRenderer.GetGradientData();
3688       nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
3689           aForFrame->PresContext(), aForFrame->Style(), gradient, mImageSize);
3690 
3691       wr::ExtendMode extendMode;
3692       nsTArray<wr::GradientStop> stops;
3693       LayoutDevicePoint lineStart;
3694       LayoutDevicePoint lineEnd;
3695       LayoutDeviceSize gradientRadius;
3696       LayoutDevicePoint gradientCenter;
3697       float gradientAngle;
3698       renderer.BuildWebRenderParameters(1.0, extendMode, stops, lineStart,
3699                                         lineEnd, gradientRadius, gradientCenter,
3700                                         gradientAngle);
3701 
3702       if (gradient.IsLinear()) {
3703         LayoutDevicePoint startPoint =
3704             LayoutDevicePoint(dest.min.x, dest.min.y) + lineStart;
3705         LayoutDevicePoint endPoint =
3706             LayoutDevicePoint(dest.min.x, dest.min.y) + lineEnd;
3707 
3708         aBuilder.PushBorderGradient(
3709             dest, clip, !aItem->BackfaceIsHidden(),
3710             wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
3711             (float)(mImageSize.width) / appUnitsPerDevPixel,
3712             (float)(mImageSize.height) / appUnitsPerDevPixel, mFill,
3713             wr::ToDeviceIntSideOffsets(slice[0], slice[1], slice[2], slice[3]),
3714             wr::ToLayoutPoint(startPoint), wr::ToLayoutPoint(endPoint), stops,
3715             extendMode,
3716             wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2],
3717                                     outset[3]));
3718       } else if (gradient.IsRadial()) {
3719         aBuilder.PushBorderRadialGradient(
3720             dest, clip, !aItem->BackfaceIsHidden(),
3721             wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
3722             mFill, wr::ToLayoutPoint(lineStart),
3723             wr::ToLayoutSize(gradientRadius), stops, extendMode,
3724             wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2],
3725                                     outset[3]));
3726       } else {
3727         MOZ_ASSERT(gradient.IsConic());
3728         aBuilder.PushBorderConicGradient(
3729             dest, clip, !aItem->BackfaceIsHidden(),
3730             wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
3731             mFill, wr::ToLayoutPoint(gradientCenter), gradientAngle, stops,
3732             extendMode,
3733             wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2],
3734                                     outset[3]));
3735       }
3736       break;
3737     }
3738     default:
3739       MOZ_ASSERT_UNREACHABLE("Unsupport border image type");
3740       drawResult = ImgDrawResult::NOT_SUPPORTED;
3741   }
3742 
3743   return drawResult;
3744 }
3745 
nsCSSBorderImageRenderer(const nsCSSBorderImageRenderer & aRhs)3746 nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
3747     const nsCSSBorderImageRenderer& aRhs)
3748     : mImageRenderer(aRhs.mImageRenderer),
3749       mImageSize(aRhs.mImageSize),
3750       mSlice(aRhs.mSlice),
3751       mWidths(aRhs.mWidths),
3752       mImageOutset(aRhs.mImageOutset),
3753       mArea(aRhs.mArea),
3754       mClip(aRhs.mClip),
3755       mRepeatModeHorizontal(aRhs.mRepeatModeHorizontal),
3756       mRepeatModeVertical(aRhs.mRepeatModeVertical),
3757       mFill(aRhs.mFill) {
3758   Unused << mImageRenderer.PrepareResult();
3759 }
3760 
operator =(const nsCSSBorderImageRenderer & aRhs)3761 nsCSSBorderImageRenderer& nsCSSBorderImageRenderer::operator=(
3762     const nsCSSBorderImageRenderer& aRhs) {
3763   mImageRenderer = aRhs.mImageRenderer;
3764   mImageSize = aRhs.mImageSize;
3765   mSlice = aRhs.mSlice;
3766   mWidths = aRhs.mWidths;
3767   mImageOutset = aRhs.mImageOutset;
3768   mArea = aRhs.mArea;
3769   mClip = aRhs.mClip;
3770   mRepeatModeHorizontal = aRhs.mRepeatModeHorizontal;
3771   mRepeatModeVertical = aRhs.mRepeatModeVertical;
3772   mFill = aRhs.mFill;
3773   Unused << mImageRenderer.PrepareResult();
3774 
3775   return *this;
3776 }
3777 
nsCSSBorderImageRenderer(nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,Sides aSkipSides,const nsImageRenderer & aImageRenderer)3778 nsCSSBorderImageRenderer::nsCSSBorderImageRenderer(
3779     nsIFrame* aForFrame, const nsRect& aBorderArea,
3780     const nsStyleBorder& aStyleBorder, Sides aSkipSides,
3781     const nsImageRenderer& aImageRenderer)
3782     : mImageRenderer(aImageRenderer) {
3783   // Determine the border image area, which by default corresponds to the
3784   // border box but can be modified by 'border-image-outset'.
3785   // Note that 'border-radius' do not apply to 'border-image' borders per
3786   // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
3787   nsMargin borderWidths(aStyleBorder.GetComputedBorder());
3788   mImageOutset = aStyleBorder.GetImageOutset();
3789   if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder) &&
3790       !aSkipSides.IsEmpty()) {
3791     mArea = nsCSSRendering::BoxDecorationRectForBorder(
3792         aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
3793     if (mArea.IsEqualEdges(aBorderArea)) {
3794       // No need for a clip, just skip the sides we don't want.
3795       borderWidths.ApplySkipSides(aSkipSides);
3796       mImageOutset.ApplySkipSides(aSkipSides);
3797       mArea.Inflate(mImageOutset);
3798     } else {
3799       // We're drawing borders around the joined continuation boxes so we need
3800       // to clip that to the slice that we want for this frame.
3801       mArea.Inflate(mImageOutset);
3802       mImageOutset.ApplySkipSides(aSkipSides);
3803       mClip = aBorderArea;
3804       mClip.Inflate(mImageOutset);
3805     }
3806   } else {
3807     mArea = aBorderArea;
3808     mArea.Inflate(mImageOutset);
3809   }
3810 
3811   // Calculate the image size used to compute slice points.
3812   CSSSizeOrRatio intrinsicSize = mImageRenderer.ComputeIntrinsicSize();
3813   mImageSize = nsImageRenderer::ComputeConcreteSize(
3814       CSSSizeOrRatio(), intrinsicSize, mArea.Size());
3815   mImageRenderer.SetPreferredSize(intrinsicSize, mImageSize);
3816 
3817   // Compute the used values of 'border-image-slice' and 'border-image-width';
3818   // we do them together because the latter can depend on the former.
3819   nsMargin slice;
3820   nsMargin border;
3821   for (const auto s : mozilla::AllPhysicalSides()) {
3822     const auto& slice = aStyleBorder.mBorderImageSlice.offsets.Get(s);
3823     int32_t imgDimension =
3824         SideIsVertical(s) ? mImageSize.width : mImageSize.height;
3825     nscoord borderDimension = SideIsVertical(s) ? mArea.width : mArea.height;
3826     double value;
3827     if (slice.IsNumber()) {
3828       value = nsPresContext::CSSPixelsToAppUnits(NS_lround(slice.AsNumber()));
3829     } else {
3830       MOZ_ASSERT(slice.IsPercentage());
3831       value = slice.AsPercentage()._0 * imgDimension;
3832     }
3833     if (value < 0) {
3834       value = 0;
3835     }
3836     if (value > imgDimension) {
3837       value = imgDimension;
3838     }
3839     mSlice.Side(s) = value;
3840 
3841     const auto& width = aStyleBorder.mBorderImageWidth.Get(s);
3842     switch (width.tag) {
3843       case StyleBorderImageSideWidth::Tag::LengthPercentage:
3844         value =
3845             std::max(0, width.AsLengthPercentage().Resolve(borderDimension));
3846         break;
3847       case StyleBorderImageSideWidth::Tag::Number:
3848         value = width.AsNumber() * borderWidths.Side(s);
3849         break;
3850       case StyleBorderImageSideWidth::Tag::Auto:
3851         value = mSlice.Side(s);
3852         break;
3853       default:
3854         MOZ_ASSERT_UNREACHABLE("unexpected CSS unit for border image area");
3855         value = 0;
3856         break;
3857     }
3858     // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
3859     // because we expect value to be non-negative.
3860     MOZ_ASSERT(value >= 0);
3861     mWidths.Side(s) = NSToCoordRoundWithClamp(value);
3862     MOZ_ASSERT(mWidths.Side(s) >= 0);
3863   }
3864 
3865   // "If two opposite border-image-width offsets are large enough that they
3866   // overlap, their used values are proportionately reduced until they no
3867   // longer overlap."
3868   uint32_t combinedBorderWidth =
3869       uint32_t(mWidths.left) + uint32_t(mWidths.right);
3870   double scaleX = combinedBorderWidth > uint32_t(mArea.width)
3871                       ? mArea.width / double(combinedBorderWidth)
3872                       : 1.0;
3873   uint32_t combinedBorderHeight =
3874       uint32_t(mWidths.top) + uint32_t(mWidths.bottom);
3875   double scaleY = combinedBorderHeight > uint32_t(mArea.height)
3876                       ? mArea.height / double(combinedBorderHeight)
3877                       : 1.0;
3878   double scale = std::min(scaleX, scaleY);
3879   if (scale < 1.0) {
3880     mWidths.left *= scale;
3881     mWidths.right *= scale;
3882     mWidths.top *= scale;
3883     mWidths.bottom *= scale;
3884     NS_ASSERTION(mWidths.left + mWidths.right <= mArea.width &&
3885                      mWidths.top + mWidths.bottom <= mArea.height,
3886                  "rounding error in width reduction???");
3887   }
3888 
3889   mRepeatModeHorizontal = aStyleBorder.mBorderImageRepeatH;
3890   mRepeatModeVertical = aStyleBorder.mBorderImageRepeatV;
3891   mFill = aStyleBorder.mBorderImageSlice.fill;
3892 }
3893