tor-browser

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

PathHelpers.h (14656B)


      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 #ifndef MOZILLA_GFX_PATHHELPERS_H_
      8 #define MOZILLA_GFX_PATHHELPERS_H_
      9 
     10 #include "2D.h"
     11 #include "UserData.h"
     12 
     13 #include <cmath>
     14 
     15 namespace mozilla {
     16 namespace gfx {
     17 
     18 const int32_t sPointCount[] = {1, 1, 3, 2, 0, 0};
     19 
     20 // Kappa constant for 90-degree angle
     21 const Float kKappaFactor = 0.55191497064665766025f;
     22 
     23 // Calculate kappa constant for partial curve. The sign of angle in the
     24 // tangent will actually ensure this is negative for a counter clockwise
     25 // sweep, so changing signs later isn't needed.
     26 inline Float ComputeKappaFactor(Float aAngle) {
     27  return (4.0f / 3.0f) * tanf(aAngle / 4.0f);
     28 }
     29 
     30 /**
     31 * Draws a partial arc <= 90 degrees given exact start and end points.
     32 * Assumes that it is continuing from an already specified start point.
     33 */
     34 template <typename T>
     35 inline void PartialArcToBezier(T* aSink, const Point& aStartOffset,
     36                               const Point& aEndOffset,
     37                               const Matrix& aTransform,
     38                               Float aKappaFactor = kKappaFactor) {
     39  Point cp1 =
     40      aStartOffset + Point(-aStartOffset.y, aStartOffset.x) * aKappaFactor;
     41 
     42  Point cp2 = aEndOffset + Point(aEndOffset.y, -aEndOffset.x) * aKappaFactor;
     43 
     44  aSink->BezierTo(aTransform.TransformPoint(cp1),
     45                  aTransform.TransformPoint(cp2),
     46                  aTransform.TransformPoint(aEndOffset));
     47 }
     48 
     49 /**
     50 * Draws an acute arc (<= 90 degrees) given exact start and end points.
     51 * Specialized version avoiding kappa calculation.
     52 */
     53 template <typename T>
     54 inline void AcuteArcToBezier(T* aSink, const Point& aOrigin,
     55                             const Size& aRadius, const Point& aStartPoint,
     56                             const Point& aEndPoint,
     57                             Float aKappaFactor = kKappaFactor) {
     58  aSink->LineTo(aStartPoint);
     59  if (!aRadius.IsEmpty()) {
     60    Float kappaX = aKappaFactor * aRadius.width / aRadius.height;
     61    Float kappaY = aKappaFactor * aRadius.height / aRadius.width;
     62    Point startOffset = aStartPoint - aOrigin;
     63    Point endOffset = aEndPoint - aOrigin;
     64    aSink->BezierTo(
     65        aStartPoint + Point(-startOffset.y * kappaX, startOffset.x * kappaY),
     66        aEndPoint + Point(endOffset.y * kappaX, -endOffset.x * kappaY),
     67        aEndPoint);
     68  } else if (aEndPoint != aStartPoint) {
     69    aSink->LineTo(aEndPoint);
     70  }
     71 }
     72 
     73 /**
     74 * Draws an acute arc (<= 90 degrees) given exact start and end points.
     75 */
     76 template <typename T>
     77 inline void AcuteArcToBezier(T* aSink, const Point& aOrigin,
     78                             const Size& aRadius, const Point& aStartPoint,
     79                             const Point& aEndPoint, Float aStartAngle,
     80                             Float aEndAngle) {
     81  AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint,
     82                   ComputeKappaFactor(aEndAngle - aStartAngle));
     83 }
     84 
     85 template <typename T>
     86 void ArcToBezier(T* aSink, const Point& aOrigin, const Size& aRadius,
     87                 float aStartAngle, float aEndAngle, bool aAntiClockwise,
     88                 float aRotation = 0.0f, const Matrix& aTransform = Matrix()) {
     89  Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f;
     90 
     91  // Calculate the total arc we're going to sweep.
     92  Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection;
     93 
     94  // Clockwise we always sweep from the smaller to the larger angle, ccw
     95  // it's vice versa.
     96  if (arcSweepLeft < 0) {
     97    // Rerverse sweep is modulo'd into range rather than clamped.
     98    arcSweepLeft = Float(2.0f * M_PI) + fmodf(arcSweepLeft, Float(2.0f * M_PI));
     99    // Recalculate the start angle to land closer to end angle.
    100    aStartAngle = aEndAngle - arcSweepLeft * sweepDirection;
    101  } else if (arcSweepLeft > Float(2.0f * M_PI)) {
    102    // Sweeping more than 2 * pi is a full circle.
    103    arcSweepLeft = Float(2.0f * M_PI);
    104  }
    105 
    106  Float currentStartAngle = aStartAngle;
    107  Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle));
    108  Matrix transform = Matrix::Scaling(aRadius.width, aRadius.height);
    109  if (aRotation != 0.0f) {
    110    transform *= Matrix::Rotation(aRotation);
    111  }
    112  transform.PostTranslate(aOrigin);
    113  transform *= aTransform;
    114  aSink->LineTo(transform.TransformPoint(currentStartOffset));
    115 
    116  while (arcSweepLeft > 0) {
    117    Float currentEndAngle =
    118        currentStartAngle +
    119        std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection;
    120    Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle));
    121 
    122    PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform,
    123                       ComputeKappaFactor(currentEndAngle - currentStartAngle));
    124 
    125    // We guarantee here the current point is the start point of the next
    126    // curve segment.
    127    arcSweepLeft -= Float(M_PI / 2.0f);
    128    currentStartAngle = currentEndAngle;
    129    currentStartOffset = currentEndOffset;
    130  }
    131 }
    132 
    133 /* This is basically the ArcToBezier with the parameters for drawing a circle
    134 * inlined which vastly simplifies it and avoids a bunch of transcedental
    135 * function calls which should make it faster. */
    136 template <typename T>
    137 void EllipseToBezier(T* aSink, const Point& aOrigin, const Size& aRadius) {
    138  Matrix transform(aRadius.width, 0, 0, aRadius.height, aOrigin.x, aOrigin.y);
    139  Point currentStartOffset(1, 0);
    140 
    141  aSink->LineTo(transform.TransformPoint(currentStartOffset));
    142 
    143  for (int i = 0; i < 4; i++) {
    144    // cos(x+pi/2) == -sin(x)
    145    // sin(x+pi/2) == cos(x)
    146    Point currentEndOffset(-currentStartOffset.y, currentStartOffset.x);
    147 
    148    PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform);
    149 
    150    // We guarantee here the current point is the start point of the next
    151    // curve segment.
    152    currentStartOffset = currentEndOffset;
    153  }
    154 }
    155 
    156 inline already_AddRefed<Path> MakeEmptyPath(const DrawTarget& aDrawTarget) {
    157  RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
    158  return builder->Finish();
    159 }
    160 
    161 /**
    162 * Appends a path representing a rectangle to the path being built by
    163 * aPathBuilder.
    164 *
    165 * aRect           The rectangle to append.
    166 * aDrawClockwise  If set to true, the path will start at the left of the top
    167 *                 left edge and draw clockwise. If set to false the path will
    168 *                 start at the right of the top left edge and draw counter-
    169 *                 clockwise.
    170 */
    171 GFX2D_API void AppendRectToPath(PathBuilder* aPathBuilder, const Rect& aRect,
    172                                bool aDrawClockwise = true);
    173 
    174 inline already_AddRefed<Path> MakePathForRect(const DrawTarget& aDrawTarget,
    175                                              const Rect& aRect,
    176                                              bool aDrawClockwise = true) {
    177  RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
    178  AppendRectToPath(builder, aRect, aDrawClockwise);
    179  return builder->Finish();
    180 }
    181 
    182 /**
    183 * Appends a path representing a rounded rectangle to the path being built by
    184 * aPathBuilder.
    185 *
    186 * aRect           The rectangle to append.
    187 * aCornerRadii    Contains the radii of the top-left, top-right, bottom-right
    188 *                 and bottom-left corners, in that order.
    189 * aDrawClockwise  If set to true, the path will start at the left of the top
    190 *                 left edge and draw clockwise. If set to false the path will
    191 *                 start at the right of the top left edge and draw counter-
    192 *                 clockwise.
    193 */
    194 GFX2D_API void AppendRoundedRectToPath(
    195    PathBuilder* aPathBuilder, const Rect& aRect, const RectCornerRadii& aRadii,
    196    bool aDrawClockwise = true, const Maybe<Matrix>& aTransform = Nothing());
    197 
    198 inline already_AddRefed<Path> MakePathForRoundedRect(
    199    const DrawTarget& aDrawTarget, const Rect& aRect,
    200    const RectCornerRadii& aRadii, bool aDrawClockwise = true) {
    201  RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
    202  AppendRoundedRectToPath(builder, aRect, aRadii, aDrawClockwise);
    203  return builder->Finish();
    204 }
    205 
    206 /**
    207 * Appends a path representing an ellipse to the path being built by
    208 * aPathBuilder.
    209 *
    210 * The ellipse extends aDimensions.width / 2.0 in the horizontal direction
    211 * from aCenter, and aDimensions.height / 2.0 in the vertical direction.
    212 */
    213 GFX2D_API void AppendEllipseToPath(PathBuilder* aPathBuilder,
    214                                   const Point& aCenter,
    215                                   const Size& aDimensions);
    216 
    217 inline already_AddRefed<Path> MakePathForEllipse(const DrawTarget& aDrawTarget,
    218                                                 const Point& aCenter,
    219                                                 const Size& aDimensions) {
    220  RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
    221  AppendEllipseToPath(builder, aCenter, aDimensions);
    222  return builder->Finish();
    223 }
    224 
    225 inline already_AddRefed<Path> MakePathForCircle(const DrawTarget& aDrawTarget,
    226                                                const Point& aCenter,
    227                                                float aRadius) {
    228  RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
    229  builder->Arc(aCenter, aRadius, 0.0f, Float(2.0 * M_PI));
    230  builder->Close();
    231  return builder->Finish();
    232 }
    233 
    234 /**
    235 * If aDrawTarget's transform only contains a translation, and if this line is
    236 * a horizontal or vertical line, this function will snap the line's vertices
    237 * to align with the device pixel grid so that stroking the line with a one
    238 * pixel wide stroke will result in a crisp line that is not antialiased over
    239 * two pixels across its width.
    240 *
    241 * @return Returns true if this function snaps aRect's vertices, else returns
    242 *   false.
    243 */
    244 GFX2D_API bool SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2,
    245                                                 const DrawTarget& aDrawTarget,
    246                                                 Float aLineWidth);
    247 
    248 /**
    249 * This function paints each edge of aRect separately, snapping the edges using
    250 * SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths
    251 * helps ensure not only that the stroke spans a single row of device pixels if
    252 * possible, but also that the ends of stroke dashes start and end on device
    253 * pixels too.
    254 */
    255 GFX2D_API void StrokeSnappedEdgesOfRect(const Rect& aRect,
    256                                        DrawTarget& aDrawTarget,
    257                                        const ColorPattern& aColor,
    258                                        const StrokeOptions& aStrokeOptions);
    259 
    260 /**
    261 * Return the margin, in device space, by which a stroke can extend beyond the
    262 * rendered shape.
    263 * @param  aStrokeOptions The stroke options that the stroke is drawn with.
    264 * @param  aTransform     The user space to device space transform.
    265 * @return                The stroke margin.
    266 */
    267 GFX2D_API Margin MaxStrokeExtents(const StrokeOptions& aStrokeOptions,
    268                                  const Matrix& aTransform);
    269 
    270 extern UserDataKey sDisablePixelSnapping;
    271 
    272 /**
    273 * If aDrawTarget's transform only contains a translation or, if
    274 * aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this
    275 * function will convert aRect to device space and snap it to device pixels.
    276 * This function returns true if aRect is modified, otherwise it returns false.
    277 *
    278 * Note that the snapping is such that filling the rect using a DrawTarget
    279 * which has the identity matrix as its transform will result in crisp edges.
    280 * (That is, aRect will have integer values, aligning its edges between pixel
    281 * boundaries.)  If on the other hand you stroking the rect with an odd valued
    282 * stroke width then the edges of the stroke will be antialiased (assuming an
    283 * AntialiasMode that does antialiasing).
    284 *
    285 * Empty snaps are those which result in a rectangle of 0 area.  If they are
    286 * disallowed, an axis is left unsnapped if the rounding process results in a
    287 * length of 0.
    288 */
    289 inline bool UserToDevicePixelSnapped(Rect& aRect, const DrawTarget& aDrawTarget,
    290                                     bool aAllowScaleOr90DegreeRotate = false,
    291                                     bool aAllowEmptySnaps = true) {
    292  if (aDrawTarget.GetUserData(&sDisablePixelSnapping)) {
    293    return false;
    294  }
    295 
    296  Matrix mat = aDrawTarget.GetTransform();
    297 
    298  const Float epsilon = 0.0000001f;
    299 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
    300  if (!aAllowScaleOr90DegreeRotate &&
    301      (!WITHIN_E(mat._11, 1.f) || !WITHIN_E(mat._22, 1.f) ||
    302       !WITHIN_E(mat._12, 0.f) || !WITHIN_E(mat._21, 0.f))) {
    303    // We have non-translation, but only translation is allowed.
    304    return false;
    305  }
    306 #undef WITHIN_E
    307 
    308  Point p1 = mat.TransformPoint(aRect.TopLeft());
    309  Point p2 = mat.TransformPoint(aRect.TopRight());
    310  Point p3 = mat.TransformPoint(aRect.BottomRight());
    311 
    312  // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
    313  // two opposite corners define the entire rectangle. So check if
    314  // the axis-aligned rectangle with opposite corners p1 and p3
    315  // define an axis-aligned rectangle whose other corners are p2 and p4.
    316  // We actually only need to check one of p2 and p4, since an affine
    317  // transform maps parallelograms to parallelograms.
    318  if (p2 == Point(p1.x, p3.y) || p2 == Point(p3.x, p1.y)) {
    319    Point p1r = p1;
    320    Point p3r = p3;
    321    p1r.Round();
    322    p3r.Round();
    323    if (aAllowEmptySnaps || p1r.x != p3r.x) {
    324      p1.x = p1r.x;
    325      p3.x = p3r.x;
    326    }
    327    if (aAllowEmptySnaps || p1r.y != p3r.y) {
    328      p1.y = p1r.y;
    329      p3.y = p3r.y;
    330    }
    331 
    332    aRect.MoveTo(Point(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
    333    aRect.SizeTo(Size(std::max(p1.x, p3.x) - aRect.X(),
    334                      std::max(p1.y, p3.y) - aRect.Y()));
    335    return true;
    336  }
    337 
    338  return false;
    339 }
    340 
    341 /**
    342 * This function has the same behavior as UserToDevicePixelSnapped except that
    343 * aRect is not transformed to device space.
    344 */
    345 inline bool MaybeSnapToDevicePixels(Rect& aRect, const DrawTarget& aDrawTarget,
    346                                    bool aAllowScaleOr90DegreeRotate = false,
    347                                    bool aAllowEmptySnaps = true) {
    348  if (UserToDevicePixelSnapped(aRect, aDrawTarget, aAllowScaleOr90DegreeRotate,
    349                               aAllowEmptySnaps)) {
    350    // Since UserToDevicePixelSnapped returned true we know there is no
    351    // rotation/skew in 'mat', so we can just use TransformBounds() here.
    352    Matrix mat = aDrawTarget.GetTransform();
    353    mat.Invert();
    354    aRect = mat.TransformBounds(aRect);
    355    return true;
    356  }
    357  return false;
    358 }
    359 
    360 }  // namespace gfx
    361 }  // namespace mozilla
    362 
    363 #endif /* MOZILLA_GFX_PATHHELPERS_H_ */