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