Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  SVGPathSegUtils.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "SVGPathSegUtils.h"

#include "SVGArcConverter.h"
#include "gfx2DGlue.h"
#include "mozilla/ServoStyleConsts.h"  // StylePathCommand
#include "nsMathUtils.h"
#include "nsTextFormatter.h"

using namespace mozilla::gfx;

namespace mozilla {

static constexpr double PATH_SEG_LENGTH_TOLERANCE = 0.0000001;
static constexpr uint32_t MAX_RECURSION = 10;

static double CalcDistanceBetweenPoints(const Point& aP1, const Point& aP2) {
  return NS_hypot(aP2.x - aP1.x, aP2.y - aP1.y);
}

template <std::size_t N>
using PointArray = std::array<Point, N>;

using QuadraticBezierArray = PointArray<3>;

static void SplitQuadraticBezier(const QuadraticBezierArray& aCurve,
                                 QuadraticBezierArray& aLeft,
                                 QuadraticBezierArray& aRight) {
  aLeft[0] = aCurve[0];
  aRight[2] = aCurve[2];
  aLeft[1] = (aCurve[0] + aCurve[1]) / 2;
  aRight[1] = (aCurve[1] + aCurve[2]) / 2;
  aLeft[2] = aRight[0] = (aLeft[1] + aRight[1]) / 2;
}

using CubicBezierArray = PointArray<4>;

static void SplitCubicBezier(const CubicBezierArray& aCurve,
                             CubicBezierArray& aLeft,
                             CubicBezierArray& aRight) {
  const Point tmp = (aCurve[1] + aCurve[2]) / 4;
  aLeft[0] = aCurve[0];
  aRight[3] = aCurve[3];
  aLeft[1] = (aCurve[0] + aCurve[1]) / 2;
  aRight[2] = (aCurve[2] + aCurve[3]) / 2;
  aLeft[2] = aLeft[1] / 2 + tmp;
  aRight[1] = aRight[2] / 2 + tmp;
  aLeft[3] = aRight[0] = (aLeft[2] + aRight[1]) / 2;
}

template <std::size_t N>
static double CalcBezLengthHelper(
    PointArray<N>& aCurve, uint32_t aRecursionCount,
    void (*aSplit)(const PointArray<N>&, PointArray<N>&, PointArray<N>&)) {
  PointArray<N> left, right;
  double length = 0.0;
  for (size_t i = 0; i < N - 1; i++) {
    length += CalcDistanceBetweenPoints(aCurve[i], aCurve[i + 1]);
  }
  double dist = CalcDistanceBetweenPoints(aCurve[0], aCurve[N - 1]);
  if (length - dist > PATH_SEG_LENGTH_TOLERANCE &&
      aRecursionCount < MAX_RECURSION) {
    aSplit(aCurve, left, right);
    ++aRecursionCount;
    return CalcBezLengthHelper(left, aRecursionCount, aSplit) +
           CalcBezLengthHelper(right, aRecursionCount, aSplit);
  }
  return length;
}

static inline double CalcLengthOfCubicBezier(const Point& aPos,
                                             const Point& aCP1,
                                             const Point& aCP2,
                                             const Point& aTo) {
  CubicBezierArray curve = {aPos, aCP1, aCP2, aTo};
  return CalcBezLengthHelper(curve, 0, SplitCubicBezier);
}

static inline double CalcLengthOfQuadraticBezier(const Point& aPos,
                                                 const Point& aCP,
                                                 const Point& aTo) {
  QuadraticBezierArray curve = {aPos, aCP, aTo};
  return CalcBezLengthHelper(curve, 0, SplitQuadraticBezier);
}

/* static */
void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand,
                                          SVGPathTraversalState& aState) {
  switch (aCommand.tag) {
    case StylePathCommand::Tag::Close:
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        aState.length +=
            (float)CalcDistanceBetweenPoints(aState.pos, aState.start);
        aState.cp1 = aState.cp2 = aState.start;
      }
      aState.pos = aState.start;
      break;
    case StylePathCommand::Tag::Move: {
      const Point& p = aCommand.move.point.ToGfxPoint();
      aState.start = aState.pos =
          aCommand.move.point.IsToPosition() ? p : aState.pos + p;
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        // aState.length is unchanged, since move commands don't affect path=
        // length.
        aState.cp1 = aState.cp2 = aState.start;
      }
      break;
    }
    case StylePathCommand::Tag::Line: {
      Point to = aCommand.line.point.IsToPosition()
                     ? aCommand.line.point.ToGfxPoint()
                     : aState.pos + aCommand.line.point.ToGfxPoint();
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        aState.length += (float)CalcDistanceBetweenPoints(aState.pos, to);
        aState.cp1 = aState.cp2 = to;
      }
      aState.pos = to;
      break;
    }
    case StylePathCommand::Tag::CubicCurve: {
      Point to = aCommand.cubic_curve.point.IsByCoordinate()
                     ? aState.pos + aCommand.cubic_curve.point.ToGfxPoint()
                     : aCommand.cubic_curve.point.ToGfxPoint();
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        Point cp1 = aCommand.cubic_curve.control1.ToGfxPoint(aState.pos, to);
        Point cp2 = aCommand.cubic_curve.control2.ToGfxPoint(aState.pos, to);
        aState.length +=
            (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
        aState.cp2 = cp2;
        aState.cp1 = to;
      }
      aState.pos = to;
      break;
    }
    case StylePathCommand::Tag::QuadCurve: {
      Point to = aCommand.quad_curve.point.IsByCoordinate()
                     ? aState.pos + aCommand.quad_curve.point.ToGfxPoint()
                     : aCommand.quad_curve.point.ToGfxPoint();
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        Point cp = aCommand.quad_curve.control1.ToGfxPoint(aState.pos, to);
        aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
        aState.cp1 = cp;
        aState.cp2 = to;
      }
      aState.pos = to;
      break;
    }
    case StylePathCommand::Tag::Arc: {
      const auto& arc = aCommand.arc;
      Point to = arc.point.IsToPosition() ? arc.point.ToGfxPoint()
                                          : aState.pos + arc.point.ToGfxPoint();
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        float dist = 0.0f;
        Point radii = arc.radii.ToGfxPoint();
        if (radii.x == 0.0f || radii.y == 0.0f) {
          dist = CalcDistanceBetweenPoints(aState.pos, to);
        } else {
          CubicBezierArray bez = {aState.pos, Point(), Point(), Point()};
          const bool largeArcFlag = arc.arc_size == StyleArcSize::Large;
          const bool sweepFlag = arc.arc_sweep == StyleArcSweep::Cw;
          SVGArcConverter converter(aState.pos, to, radii, arc.rotate,
                                    largeArcFlag, sweepFlag);
          while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
            dist += (float)CalcBezLengthHelper(bez, 0, SplitCubicBezier);
            bez[0] = bez[3];
          }
        }
        aState.length += dist;
        aState.cp1 = aState.cp2 = to;
      }
      aState.pos = to;
      break;
    }
    case StylePathCommand::Tag::HLine: {
      const auto x = aCommand.h_line.x.ToGfxCoord();
      Point to(aCommand.h_line.x.IsToPosition() ? x : aState.pos.x + x,
               aState.pos.y);
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        aState.length += std::abs(to.x - aState.pos.x);
        aState.cp1 = aState.cp2 = to;
      }
      aState.pos = to;
      break;
    }
    case StylePathCommand::Tag::VLine: {
      const auto y = aCommand.v_line.y.ToGfxCoord();
      Point to(aState.pos.x,
               aCommand.v_line.y.IsToPosition() ? y : aState.pos.y + y);
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        aState.length += std::abs(to.y - aState.pos.y);
        aState.cp1 = aState.cp2 = to;
      }
      aState.pos = to;
      break;
    }
    case StylePathCommand::Tag::SmoothCubic: {
      Point to = aCommand.smooth_cubic.point.IsByCoordinate()
                     ? aState.pos + aCommand.smooth_cubic.point.ToGfxPoint()
                     : aCommand.smooth_cubic.point.ToGfxPoint();
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        Point cp1 = aState.pos - (aState.cp2 - aState.pos);
        Point cp2 = aCommand.smooth_cubic.control2.ToGfxPoint(aState.pos, to);
        aState.length +=
            (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
        aState.cp2 = cp2;
        aState.cp1 = to;
      }
      aState.pos = to;
      break;
    }
    case StylePathCommand::Tag::SmoothQuad: {
      Point to = aCommand.smooth_quad.point.IsToPosition()
                     ? aCommand.smooth_quad.point.ToGfxPoint()
                     : aState.pos + aCommand.smooth_quad.point.ToGfxPoint();
      if (aState.ShouldUpdateLengthAndControlPoints()) {
        Point cp = aState.pos - (aState.cp1 - aState.pos);
        aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
        aState.cp1 = cp;
        aState.cp2 = to;
      }
      aState.pos = to;
      break;
    }
  }
}

// Possible directions of an edge that doesn't immediately disqualify the path
// as a rectangle.
enum class EdgeDir {
  LEFT,
  RIGHT,
  UP,
  DOWN,
  // NONE represents (almost) zero-length edges, they should be ignored.
  NONE,
};

static Maybe<EdgeDir> GetDirection(const Point& v) {
  if (!v.IsFinite()) {
    return Nothing();
  }

  // We may be dealing with very small rects scaled up so make
  // adjust the threshold based on the magnitude of the sides.
  float threshold = std::min((std::abs(v.x) + std::abs(v.y)) * 0.000010.001);

  bool x = std::abs(v.x) > threshold;
  bool y = std::abs(v.y) > threshold;
  if (x && y) {
    return Nothing();
  }

  if (!x && !y) {
    return Some(EdgeDir::NONE);
  }

  if (x) {
    return Some(v.x > 0.0 ? EdgeDir::RIGHT : EdgeDir::LEFT);
  }

  return Some(v.y > 0.0 ? EdgeDir::DOWN : EdgeDir::UP);
}

static EdgeDir OppositeDirection(EdgeDir dir) {
  switch (dir) {
    case EdgeDir::LEFT:
      return EdgeDir::RIGHT;
    case EdgeDir::RIGHT:
      return EdgeDir::LEFT;
    case EdgeDir::UP:
      return EdgeDir::DOWN;
    case EdgeDir::DOWN:
      return EdgeDir::UP;
    default:
      return EdgeDir::NONE;
  }
}

struct IsRectHelper {
  Point min;
  Point max;
  EdgeDir currentDir = EdgeDir::NONE;
  // Index of the next corner.
  uint32_t idx = 0;
  std::array<EdgeDir, 4> dirs;

  IsRectHelper() { dirs.fill(EdgeDir::NONE); }

  bool Edge(const Point& from, const Point& to) {
    auto edge = to - from;

    auto maybeDir = GetDirection(edge);
    if (maybeDir.isNothing()) {
      return false;
    }

    EdgeDir dir = maybeDir.value();

    if (dir == EdgeDir::NONE) {
      // zero-length edges aren't an issue.
      return true;
    }

    if (dir != currentDir) {
      // The edge forms a corner with the previous edge.
      if (idx >= dirs.size()) {
        // We are at the 5th corner, can't be a rectangle.
        return false;
      }

      if (dir == OppositeDirection(currentDir)) {
        // Can turn left or right but not a full 180 degrees.
        return false;
      }

      dirs[idx++] = dir;
      currentDir = dir;
    }

    min.x = std::min(min.x, to.x);
    min.y = std::min(min.y, to.y);
    max.x = std::max(max.x, to.x);
    max.y = std::max(max.y, to.y);

    return true;
  }

  bool EndSubpath() const {
    if (idx != dirs.size()) {
      return false;
    }

    if (dirs[0] != OppositeDirection(dirs[2]) ||
        dirs[1] != OppositeDirection(dirs[3])) {
      return false;
    }

    return true;
  }
};

Maybe<gfx::Rect> SVGPathSegUtils::SVGPathToAxisAlignedRect(
    Span<const StylePathCommand> aPath) {
  Point pathStart;
  Point segStart;
  IsRectHelper helper;
  static constexpr float kEpsilon = 0.001f;

  for (const StylePathCommand& cmd : aPath) {
    switch (cmd.tag) {
      case StylePathCommand::Tag::Move: {
        Point to = cmd.move.point.ToGfxPoint();
        if (helper.idx != 0) {
          // This is overly strict since empty moveto sequences such as "M 10 12
          // M 3 2 M 0 0" render nothing, but I expect it won't make us miss a
          // lot of rect-shaped paths in practice and lets us avoidhandling
          // special caps for empty sub-paths like "M 0 0 L 0 0" and "M 1 2 Z".
          return Nothing();
        }

        if (!pathStart.WithinEpsilonOf(segStart, kEpsilon)) {
          // If we were only interested in filling we could auto-close here
          // by calling helper.Edge like in the ClosePath case and detect some
          // unclosed paths as rectangles.
          //
          // For example:
          //  - "M 1 0 L 0 0 L 0 1 L 1 1 L 1 0" are both rects for filling and
          //  stroking.
          //  - "M 1 0 L 0 0 L 0 1 L 1 1" fills a rect but the stroke is shaped
          //  like a C.
          return Nothing();
        }

        if (helper.idx != 0 && !helper.EndSubpath()) {
          return Nothing();
        }

        if (cmd.move.point.IsByCoordinate()) {
          to = segStart + to;
        }

        pathStart = to;
        segStart = to;
        if (helper.idx == 0) {
          helper.min = to;
          helper.max = to;
        }

        break;
      }
      case StylePathCommand::Tag::Close: {
        if (!helper.Edge(segStart, pathStart)) {
          return Nothing();
        }
        if (!helper.EndSubpath()) {
          return Nothing();
        }
        pathStart = segStart;
        break;
      }
      case StylePathCommand::Tag::Line: {
        Point to = cmd.line.point.ToGfxPoint();
        if (cmd.line.point.IsByCoordinate()) {
          to = segStart + to;
        }

        if (!helper.Edge(segStart, to)) {
          return Nothing();
        }
        segStart = to;
        break;
      }
      case StylePathCommand::Tag::HLine: {
        Point to = gfx::Point(cmd.h_line.x.ToGfxCoord(), segStart.y);
        if (cmd.h_line.x.IsByCoordinate()) {
          to.x += segStart.x;
        }

        if (!helper.Edge(segStart, to)) {
          return Nothing();
        }
        segStart = to;
        break;
      }
      case StylePathCommand::Tag::VLine: {
        Point to = gfx::Point(segStart.x, cmd.v_line.y.ToGfxCoord());
        if (cmd.v_line.y.IsByCoordinate()) {
          to.y += segStart.y;
        }

        if (!helper.Edge(segStart, to)) {
          return Nothing();
        }
        segStart = to;
        break;
      }
      default:
        return Nothing();
    }
  }

  if (!pathStart.WithinEpsilonOf(segStart, kEpsilon)) {
    // Same situation as with moveto regarding stroking not fully closed path
    // even though the fill is a rectangle.
    return Nothing();
  }

  if (!helper.EndSubpath()) {
    return Nothing();
  }

  auto size = helper.max - helper.min;
  return Some(Rect(helper.min, Size(size.x, size.y)));
}

}  // namespace mozilla

Messung V0.5 in Prozent
C=94 H=98 G=95

¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet am  2026-08-23) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002