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