tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ShapeUtils.cpp (9941B)


      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 "mozilla/ShapeUtils.h"
      8 
      9 #include <cstdlib>
     10 
     11 #include "mozilla/SVGContentUtils.h"
     12 #include "mozilla/gfx/2D.h"
     13 #include "mozilla/gfx/PathHelpers.h"
     14 #include "nsCSSRendering.h"
     15 #include "nsLayoutUtils.h"
     16 #include "nsMargin.h"
     17 #include "nsStyleStruct.h"
     18 
     19 namespace mozilla {
     20 
     21 nscoord ShapeUtils::ComputeShapeRadius(const StyleShapeRadius& aType,
     22                                       const nscoord aCenter,
     23                                       const nscoord aPosMin,
     24                                       const nscoord aPosMax) {
     25  MOZ_ASSERT(aType.IsFarthestSide() || aType.IsClosestSide());
     26  nscoord dist1 = std::abs(aPosMin - aCenter);
     27  nscoord dist2 = std::abs(aPosMax - aCenter);
     28  nscoord length = 0;
     29  if (aType.IsFarthestSide()) {
     30    length = dist1 > dist2 ? dist1 : dist2;
     31  } else {
     32    length = dist1 > dist2 ? dist2 : dist1;
     33  }
     34  return length;
     35 }
     36 
     37 nsPoint ShapeUtils::ComputePosition(const StylePosition& aPosition,
     38                                    const nsRect& aRefBox) {
     39  nsPoint topLeft, anchor;
     40  nsSize size(aRefBox.Size());
     41  nsImageRenderer::ComputeObjectAnchorPoint(aPosition, size, size, &topLeft,
     42                                            &anchor);
     43  return anchor + aRefBox.TopLeft();
     44 }
     45 
     46 nsPoint ShapeUtils::ComputeCircleOrEllipseCenter(
     47    const StyleBasicShape& aBasicShape, const nsRect& aRefBox) {
     48  MOZ_ASSERT(aBasicShape.IsCircle() || aBasicShape.IsEllipse(),
     49             "The basic shape must be circle() or ellipse!");
     50 
     51  const auto& position = aBasicShape.IsCircle()
     52                             ? aBasicShape.AsCircle().position
     53                             : aBasicShape.AsEllipse().position;
     54  // If position is not specified, we use 50% 50%.
     55  if (position.IsAuto()) {
     56    return ComputePosition(StylePosition::FromPercentage(0.5), aRefBox);
     57  }
     58 
     59  MOZ_ASSERT(position.IsPosition());
     60  return ComputePosition(position.AsPosition(), aRefBox);
     61 }
     62 
     63 nscoord ShapeUtils::ComputeCircleRadius(const StyleBasicShape& aBasicShape,
     64                                        const nsPoint& aCenter,
     65                                        const nsRect& aRefBox) {
     66  MOZ_ASSERT(aBasicShape.IsCircle(), "The basic shape must be circle()!");
     67  const auto& radius = aBasicShape.AsCircle().radius;
     68  if (radius.IsLength()) {
     69    return radius.AsLength().Resolve([&] {
     70      // We resolve percent <shape-radius> value for circle() as defined here:
     71      // https://drafts.csswg.org/css-shapes/#funcdef-circle
     72      double referenceLength = SVGContentUtils::ComputeNormalizedHypotenuse(
     73          aRefBox.width, aRefBox.height);
     74      return NSToCoordRound(referenceLength);
     75    });
     76  }
     77 
     78  nscoord horizontal =
     79      ComputeShapeRadius(radius, aCenter.x, aRefBox.x, aRefBox.XMost());
     80  nscoord vertical =
     81      ComputeShapeRadius(radius, aCenter.y, aRefBox.y, aRefBox.YMost());
     82  return radius.IsFarthestSide() ? std::max(horizontal, vertical)
     83                                 : std::min(horizontal, vertical);
     84 }
     85 
     86 nsSize ShapeUtils::ComputeEllipseRadii(const StyleBasicShape& aBasicShape,
     87                                       const nsPoint& aCenter,
     88                                       const nsRect& aRefBox) {
     89  MOZ_ASSERT(aBasicShape.IsEllipse(), "The basic shape must be ellipse()!");
     90  const auto& ellipse = aBasicShape.AsEllipse();
     91  nsSize radii;
     92  if (ellipse.semiaxis_x.IsLength()) {
     93    radii.width = ellipse.semiaxis_x.AsLength().Resolve(aRefBox.width);
     94  } else {
     95    radii.width = ComputeShapeRadius(ellipse.semiaxis_x, aCenter.x, aRefBox.x,
     96                                     aRefBox.XMost());
     97  }
     98 
     99  if (ellipse.semiaxis_y.IsLength()) {
    100    radii.height = ellipse.semiaxis_y.AsLength().Resolve(aRefBox.height);
    101  } else {
    102    radii.height = ComputeShapeRadius(ellipse.semiaxis_y, aCenter.y, aRefBox.y,
    103                                      aRefBox.YMost());
    104  }
    105 
    106  return radii;
    107 }
    108 
    109 /* static */
    110 nsRect ShapeUtils::ComputeInsetRect(
    111    const StyleRect<LengthPercentage>& aStyleRect, const nsRect& aRefBox) {
    112  const nsMargin inset(aStyleRect._0.Resolve(aRefBox.Height()),
    113                       aStyleRect._1.Resolve(aRefBox.Width()),
    114                       aStyleRect._2.Resolve(aRefBox.Height()),
    115                       aStyleRect._3.Resolve(aRefBox.Width()));
    116 
    117  const nscoord x = aRefBox.X() + inset.left;
    118  const nscoord y = aRefBox.Y() + inset.top;
    119  // All <basic-shape-rect> functions are converted into inset() at the
    120  // computing time, and it seems other browsers just clamp the width/height to
    121  // 0 if the dimension (i.e. top+bottom or left+right) is larger than 100%.
    122  // This is identical to flooring right/bottom values in rect(). Therefore,
    123  // here we also floor right/bottom (i.e. make sure the width/height is not
    124  // negative) to match the behavior of other browsers and the spec of rect().
    125  // https://github.com/w3c/csswg-drafts/issues/10870
    126  const nscoord width = std::max(0, aRefBox.Width() - inset.LeftRight());
    127  const nscoord height = std::max(0, aRefBox.Height() - inset.TopBottom());
    128  return nsRect(x, y, width, height);
    129 }
    130 
    131 /* static */
    132 bool ShapeUtils::ComputeRectRadii(const StyleBorderRadius& aBorderRadius,
    133                                  const nsRect& aRefBox, const nsRect& aRect,
    134                                  nsRectCornerRadii& aRadii) {
    135  return nsIFrame::ComputeBorderRadii(aBorderRadius, aRefBox.Size(),
    136                                      aRect.Size(), Sides(), aRadii);
    137 }
    138 
    139 /* static */
    140 nsTArray<nsPoint> ShapeUtils::ComputePolygonVertices(
    141    const StyleBasicShape& aBasicShape, const nsRect& aRefBox) {
    142  MOZ_ASSERT(aBasicShape.IsPolygon(), "The basic shape must be polygon()!");
    143 
    144  auto coords = aBasicShape.AsPolygon().coordinates.AsSpan();
    145  nsTArray<nsPoint> vertices(coords.Length());
    146  for (const StylePolygonCoord<LengthPercentage>& point : coords) {
    147    vertices.AppendElement(nsPoint(point._0.Resolve(aRefBox.width),
    148                                   point._1.Resolve(aRefBox.height)) +
    149                           aRefBox.TopLeft());
    150  }
    151  return vertices;
    152 }
    153 
    154 /* static */
    155 static inline gfx::Point ConvertToGfxPoint(const nsPoint& aPoint,
    156                                           nscoord aAppUnitsPerPixel) {
    157  return {static_cast<gfx::Float>(aPoint.x) /
    158              static_cast<gfx::Float>(aAppUnitsPerPixel),
    159          static_cast<gfx::Float>(aPoint.y) /
    160              static_cast<gfx::Float>(aAppUnitsPerPixel)};
    161 }
    162 
    163 /* static */
    164 already_AddRefed<gfx::Path> ShapeUtils::BuildCirclePath(
    165    const StyleBasicShape& aShape, const nsRect& aRefBox,
    166    const nsPoint& aCenter, nscoord aAppUnitsPerPixel,
    167    gfx::PathBuilder* aPathBuilder) {
    168  const nscoord r = ComputeCircleRadius(aShape, aCenter, aRefBox);
    169  aPathBuilder->Arc(
    170      ConvertToGfxPoint(aCenter, aAppUnitsPerPixel),
    171      static_cast<float>(r) / static_cast<float>(aAppUnitsPerPixel), 0.0,
    172      gfx::Float(2.0 * M_PI));
    173  aPathBuilder->Close();
    174  return aPathBuilder->Finish();
    175 }
    176 
    177 static inline gfx::Size ConvertToGfxSize(const nsSize& aSize,
    178                                         nscoord aAppUnitsPerPixel) {
    179  return {static_cast<gfx::Float>(aSize.width) /
    180              static_cast<gfx::Float>(aAppUnitsPerPixel),
    181          static_cast<gfx::Float>(aSize.height) /
    182              static_cast<gfx::Float>(aAppUnitsPerPixel)};
    183 }
    184 
    185 /* static */
    186 already_AddRefed<gfx::Path> ShapeUtils::BuildEllipsePath(
    187    const StyleBasicShape& aShape, const nsRect& aRefBox,
    188    const nsPoint& aCenter, nscoord aAppUnitsPerPixel,
    189    gfx::PathBuilder* aPathBuilder) {
    190  const nsSize radii = ComputeEllipseRadii(aShape, aCenter, aRefBox);
    191  EllipseToBezier(aPathBuilder, ConvertToGfxPoint(aCenter, aAppUnitsPerPixel),
    192                  ConvertToGfxSize(radii, aAppUnitsPerPixel));
    193  aPathBuilder->Close();
    194  return aPathBuilder->Finish();
    195 }
    196 
    197 /* static */
    198 already_AddRefed<gfx::Path> ShapeUtils::BuildPolygonPath(
    199    const StyleBasicShape& aShape, const nsRect& aRefBox,
    200    nscoord aAppUnitsPerPixel, gfx::PathBuilder* aPathBuilder) {
    201  nsTArray<nsPoint> vertices = ComputePolygonVertices(aShape, aRefBox);
    202  if (vertices.IsEmpty()) {
    203    MOZ_ASSERT_UNREACHABLE(
    204        "ComputePolygonVertices() should've given us some vertices!");
    205  } else {
    206    aPathBuilder->MoveTo(NSPointToPoint(vertices[0], aAppUnitsPerPixel));
    207    for (size_t i = 1; i < vertices.Length(); ++i) {
    208      aPathBuilder->LineTo(NSPointToPoint(vertices[i], aAppUnitsPerPixel));
    209    }
    210  }
    211  aPathBuilder->Close();
    212  return aPathBuilder->Finish();
    213 }
    214 
    215 /* static */
    216 already_AddRefed<gfx::Path> ShapeUtils::BuildInsetPath(
    217    const StyleBasicShape& aShape, const nsRect& aRefBox,
    218    nscoord aAppUnitsPerPixel, gfx::PathBuilder* aPathBuilder) {
    219  const nsRect insetRect = ComputeInsetRect(aShape.AsRect().rect, aRefBox);
    220  nsRectCornerRadii appUnitsRadii;
    221  const bool hasRadii = ComputeRectRadii(aShape.AsRect().round, aRefBox,
    222                                         insetRect, appUnitsRadii);
    223  return BuildRectPath(insetRect, hasRadii ? &appUnitsRadii : nullptr, aRefBox,
    224                       aAppUnitsPerPixel, aPathBuilder);
    225 }
    226 
    227 /* static */
    228 already_AddRefed<gfx::Path> ShapeUtils::BuildRectPath(
    229    const nsRect& aRect, const nsRectCornerRadii* aRadii, const nsRect& aRefBox,
    230    nscoord aAppUnitsPerPixel, gfx::PathBuilder* aPathBuilder) {
    231  const gfx::Rect insetRectPixels = NSRectToRect(aRect, aAppUnitsPerPixel);
    232  if (aRadii) {
    233    gfx::RectCornerRadii corners;
    234    nsCSSRendering::ComputePixelRadii(*aRadii, aAppUnitsPerPixel, &corners);
    235    AppendRoundedRectToPath(aPathBuilder, insetRectPixels, corners, true);
    236  } else {
    237    AppendRectToPath(aPathBuilder, insetRectPixels, true);
    238  }
    239  return aPathBuilder->Finish();
    240 }
    241 
    242 }  // namespace mozilla