зеркало из https://github.com/mozilla/gecko-dev.git
812 строки
27 KiB
C++
812 строки
27 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 "mozilla/ArrayUtils.h" // MOZ_ARRAY_LENGTH
|
|
#include "mozilla/ServoStyleConsts.h" // StylePathCommand
|
|
#include "gfx2DGlue.h"
|
|
#include "SVGPathDataParser.h"
|
|
#include "nsMathUtils.h"
|
|
#include "nsTextFormatter.h"
|
|
|
|
using namespace mozilla::dom::SVGPathSeg_Binding;
|
|
using namespace mozilla::gfx;
|
|
|
|
namespace mozilla {
|
|
|
|
static const float PATH_SEG_LENGTH_TOLERANCE = 0.0000001f;
|
|
static const uint32_t MAX_RECURSION = 10;
|
|
|
|
/* static */
|
|
void SVGPathSegUtils::GetValueAsString(const float* aSeg, nsAString& aValue) {
|
|
// Adding new seg type? Is the formatting below acceptable for the new types?
|
|
static_assert(
|
|
NS_SVG_PATH_SEG_LAST_VALID_TYPE == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
|
|
"Update GetValueAsString for the new value.");
|
|
static_assert(NS_SVG_PATH_SEG_MAX_ARGS == 7,
|
|
"Add another case to the switch below.");
|
|
|
|
uint32_t type = DecodeType(aSeg[0]);
|
|
char16_t typeAsChar = GetPathSegTypeAsLetter(type);
|
|
|
|
// Special case arcs:
|
|
if (IsArcType(type)) {
|
|
bool largeArcFlag = aSeg[4] != 0.0f;
|
|
bool sweepFlag = aSeg[5] != 0.0f;
|
|
nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g %d,%d %g,%g", typeAsChar,
|
|
aSeg[1], aSeg[2], aSeg[3], largeArcFlag,
|
|
sweepFlag, aSeg[6], aSeg[7]);
|
|
} else {
|
|
switch (ArgCountForType(type)) {
|
|
case 0:
|
|
aValue = typeAsChar;
|
|
break;
|
|
|
|
case 1:
|
|
nsTextFormatter::ssprintf(aValue, u"%c%g", typeAsChar, aSeg[1]);
|
|
break;
|
|
|
|
case 2:
|
|
nsTextFormatter::ssprintf(aValue, u"%c%g,%g", typeAsChar, aSeg[1],
|
|
aSeg[2]);
|
|
break;
|
|
|
|
case 4:
|
|
nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g", typeAsChar, aSeg[1],
|
|
aSeg[2], aSeg[3], aSeg[4]);
|
|
break;
|
|
|
|
case 6:
|
|
nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g %g,%g", typeAsChar,
|
|
aSeg[1], aSeg[2], aSeg[3], aSeg[4], aSeg[5],
|
|
aSeg[6]);
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT(false, "Unknown segment type");
|
|
aValue = u"<unknown-segment-type>";
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static float CalcDistanceBetweenPoints(const Point& aP1, const Point& aP2) {
|
|
return NS_hypot(aP2.x - aP1.x, aP2.y - aP1.y);
|
|
}
|
|
|
|
static void SplitQuadraticBezier(const Point* aCurve, Point* aLeft,
|
|
Point* aRight) {
|
|
aLeft[0].x = aCurve[0].x;
|
|
aLeft[0].y = aCurve[0].y;
|
|
aRight[2].x = aCurve[2].x;
|
|
aRight[2].y = aCurve[2].y;
|
|
aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
|
|
aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
|
|
aRight[1].x = (aCurve[1].x + aCurve[2].x) / 2;
|
|
aRight[1].y = (aCurve[1].y + aCurve[2].y) / 2;
|
|
aLeft[2].x = aRight[0].x = (aLeft[1].x + aRight[1].x) / 2;
|
|
aLeft[2].y = aRight[0].y = (aLeft[1].y + aRight[1].y) / 2;
|
|
}
|
|
|
|
static void SplitCubicBezier(const Point* aCurve, Point* aLeft, Point* aRight) {
|
|
Point tmp;
|
|
tmp.x = (aCurve[1].x + aCurve[2].x) / 4;
|
|
tmp.y = (aCurve[1].y + aCurve[2].y) / 4;
|
|
aLeft[0].x = aCurve[0].x;
|
|
aLeft[0].y = aCurve[0].y;
|
|
aRight[3].x = aCurve[3].x;
|
|
aRight[3].y = aCurve[3].y;
|
|
aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
|
|
aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
|
|
aRight[2].x = (aCurve[2].x + aCurve[3].x) / 2;
|
|
aRight[2].y = (aCurve[2].y + aCurve[3].y) / 2;
|
|
aLeft[2].x = aLeft[1].x / 2 + tmp.x;
|
|
aLeft[2].y = aLeft[1].y / 2 + tmp.y;
|
|
aRight[1].x = aRight[2].x / 2 + tmp.x;
|
|
aRight[1].y = aRight[2].y / 2 + tmp.y;
|
|
aLeft[3].x = aRight[0].x = (aLeft[2].x + aRight[1].x) / 2;
|
|
aLeft[3].y = aRight[0].y = (aLeft[2].y + aRight[1].y) / 2;
|
|
}
|
|
|
|
static float CalcBezLengthHelper(const Point* aCurve, uint32_t aNumPts,
|
|
uint32_t aRecursionCount,
|
|
void (*aSplit)(const Point*, Point*, Point*)) {
|
|
Point left[4];
|
|
Point right[4];
|
|
float length = 0, dist;
|
|
for (uint32_t i = 0; i < aNumPts - 1; i++) {
|
|
length += CalcDistanceBetweenPoints(aCurve[i], aCurve[i + 1]);
|
|
}
|
|
dist = CalcDistanceBetweenPoints(aCurve[0], aCurve[aNumPts - 1]);
|
|
if (length - dist > PATH_SEG_LENGTH_TOLERANCE &&
|
|
aRecursionCount < MAX_RECURSION) {
|
|
aSplit(aCurve, left, right);
|
|
++aRecursionCount;
|
|
return CalcBezLengthHelper(left, aNumPts, aRecursionCount, aSplit) +
|
|
CalcBezLengthHelper(right, aNumPts, aRecursionCount, aSplit);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static inline float CalcLengthOfCubicBezier(const Point& aPos,
|
|
const Point& aCP1,
|
|
const Point& aCP2,
|
|
const Point& aTo) {
|
|
Point curve[4] = {aPos, aCP1, aCP2, aTo};
|
|
return CalcBezLengthHelper(curve, 4, 0, SplitCubicBezier);
|
|
}
|
|
|
|
static inline float CalcLengthOfQuadraticBezier(const Point& aPos,
|
|
const Point& aCP,
|
|
const Point& aTo) {
|
|
Point curve[3] = {aPos, aCP, aTo};
|
|
return CalcBezLengthHelper(curve, 3, 0, SplitQuadraticBezier);
|
|
}
|
|
|
|
static void TraverseClosePath(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start);
|
|
aState.cp1 = aState.cp2 = aState.start;
|
|
}
|
|
aState.pos = aState.start;
|
|
}
|
|
|
|
static void TraverseMovetoAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
aState.start = aState.pos = Point(aArgs[0], aArgs[1]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
// aState.length is unchanged, since move commands don't affect path length.
|
|
aState.cp1 = aState.cp2 = aState.start;
|
|
}
|
|
}
|
|
|
|
static void TraverseMovetoRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
aState.start = aState.pos += Point(aArgs[0], aArgs[1]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
// aState.length is unchanged, since move commands don't affect path length.
|
|
aState.cp1 = aState.cp2 = aState.start;
|
|
}
|
|
}
|
|
|
|
static void TraverseLinetoAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to(aArgs[0], aArgs[1]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseLinetoRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to = aState.pos + Point(aArgs[0], aArgs[1]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseLinetoHorizontalAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to(aArgs[0], aState.pos.y);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += std::fabs(to.x - aState.pos.x);
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseLinetoHorizontalRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
aState.pos.x += aArgs[0];
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += std::fabs(aArgs[0]);
|
|
aState.cp1 = aState.cp2 = aState.pos;
|
|
}
|
|
}
|
|
|
|
static void TraverseLinetoVerticalAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to(aState.pos.x, aArgs[0]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += std::fabs(to.y - aState.pos.y);
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseLinetoVerticalRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
aState.pos.y += aArgs[0];
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += std::fabs(aArgs[0]);
|
|
aState.cp1 = aState.cp2 = aState.pos;
|
|
}
|
|
}
|
|
|
|
static void TraverseCurvetoCubicAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to(aArgs[4], aArgs[5]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp1(aArgs[0], aArgs[1]);
|
|
Point cp2(aArgs[2], aArgs[3]);
|
|
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
|
|
aState.cp2 = cp2;
|
|
aState.cp1 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseCurvetoCubicSmoothAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to(aArgs[2], aArgs[3]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp1 = aState.pos - (aState.cp2 - aState.pos);
|
|
Point cp2(aArgs[0], aArgs[1]);
|
|
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
|
|
aState.cp2 = cp2;
|
|
aState.cp1 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseCurvetoCubicRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to = aState.pos + Point(aArgs[4], aArgs[5]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp1 = aState.pos + Point(aArgs[0], aArgs[1]);
|
|
Point cp2 = aState.pos + Point(aArgs[2], aArgs[3]);
|
|
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
|
|
aState.cp2 = cp2;
|
|
aState.cp1 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseCurvetoCubicSmoothRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to = aState.pos + Point(aArgs[2], aArgs[3]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp1 = aState.pos - (aState.cp2 - aState.pos);
|
|
Point cp2 = aState.pos + Point(aArgs[0], aArgs[1]);
|
|
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
|
|
aState.cp2 = cp2;
|
|
aState.cp1 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseCurvetoQuadraticAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to(aArgs[2], aArgs[3]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp(aArgs[0], aArgs[1]);
|
|
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
|
|
aState.cp1 = cp;
|
|
aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseCurvetoQuadraticSmoothAbs(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to(aArgs[0], aArgs[1]);
|
|
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;
|
|
}
|
|
|
|
static void TraverseCurvetoQuadraticRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to = aState.pos + Point(aArgs[2], aArgs[3]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp = aState.pos + Point(aArgs[0], aArgs[1]);
|
|
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
|
|
aState.cp1 = cp;
|
|
aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseCurvetoQuadraticSmoothRel(const float* aArgs,
|
|
SVGPathTraversalState& aState) {
|
|
Point to = aState.pos + Point(aArgs[0], aArgs[1]);
|
|
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;
|
|
}
|
|
|
|
static void TraverseArcAbs(const float* aArgs, SVGPathTraversalState& aState) {
|
|
Point to(aArgs[5], aArgs[6]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
float dist = 0;
|
|
Point radii(aArgs[0], aArgs[1]);
|
|
if (radii.x == 0.0f || radii.y == 0.0f) {
|
|
dist = CalcDistanceBetweenPoints(aState.pos, to);
|
|
} else {
|
|
Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)};
|
|
SVGArcConverter converter(aState.pos, to, radii, aArgs[2], aArgs[3] != 0,
|
|
aArgs[4] != 0);
|
|
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
|
|
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
|
|
bez[0] = bez[3];
|
|
}
|
|
}
|
|
aState.length += dist;
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
static void TraverseArcRel(const float* aArgs, SVGPathTraversalState& aState) {
|
|
Point to = aState.pos + Point(aArgs[5], aArgs[6]);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
float dist = 0;
|
|
Point radii(aArgs[0], aArgs[1]);
|
|
if (radii.x == 0.0f || radii.y == 0.0f) {
|
|
dist = CalcDistanceBetweenPoints(aState.pos, to);
|
|
} else {
|
|
Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)};
|
|
SVGArcConverter converter(aState.pos, to, radii, aArgs[2], aArgs[3] != 0,
|
|
aArgs[4] != 0);
|
|
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
|
|
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
|
|
bez[0] = bez[3];
|
|
}
|
|
}
|
|
aState.length += dist;
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
}
|
|
|
|
using TraverseFunc = void (*)(const float*, SVGPathTraversalState&);
|
|
|
|
static TraverseFunc gTraverseFuncTable[NS_SVG_PATH_SEG_TYPE_COUNT] = {
|
|
nullptr, // 0 == PATHSEG_UNKNOWN
|
|
TraverseClosePath,
|
|
TraverseMovetoAbs,
|
|
TraverseMovetoRel,
|
|
TraverseLinetoAbs,
|
|
TraverseLinetoRel,
|
|
TraverseCurvetoCubicAbs,
|
|
TraverseCurvetoCubicRel,
|
|
TraverseCurvetoQuadraticAbs,
|
|
TraverseCurvetoQuadraticRel,
|
|
TraverseArcAbs,
|
|
TraverseArcRel,
|
|
TraverseLinetoHorizontalAbs,
|
|
TraverseLinetoHorizontalRel,
|
|
TraverseLinetoVerticalAbs,
|
|
TraverseLinetoVerticalRel,
|
|
TraverseCurvetoCubicSmoothAbs,
|
|
TraverseCurvetoCubicSmoothRel,
|
|
TraverseCurvetoQuadraticSmoothAbs,
|
|
TraverseCurvetoQuadraticSmoothRel};
|
|
|
|
/* static */
|
|
void SVGPathSegUtils::TraversePathSegment(const float* aData,
|
|
SVGPathTraversalState& aState) {
|
|
static_assert(
|
|
MOZ_ARRAY_LENGTH(gTraverseFuncTable) == NS_SVG_PATH_SEG_TYPE_COUNT,
|
|
"gTraverseFuncTable is out of date");
|
|
uint32_t type = DecodeType(aData[0]);
|
|
gTraverseFuncTable[type](aData + 1, aState);
|
|
}
|
|
|
|
// Basically, this is just a variant version of the above TraverseXXX functions.
|
|
// We just put those function inside this and use StylePathCommand instead.
|
|
// This function and the above ones should be dropped by Bug 1388931.
|
|
/* static */
|
|
void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand,
|
|
SVGPathTraversalState& aState) {
|
|
auto toGfxPoint = [](const StyleCoordPair& aPair) {
|
|
return Point(aPair._0, aPair._1);
|
|
};
|
|
|
|
switch (aCommand.tag) {
|
|
case StylePathCommand::Tag::ClosePath:
|
|
TraverseClosePath(nullptr, aState);
|
|
break;
|
|
case StylePathCommand::Tag::MoveTo: {
|
|
const Point& p = toGfxPoint(aCommand.move_to.point);
|
|
aState.start = aState.pos =
|
|
aCommand.move_to.absolute == StyleIsAbsolute::Yes ? 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::LineTo: {
|
|
Point to = aCommand.line_to.absolute == StyleIsAbsolute::Yes
|
|
? toGfxPoint(aCommand.line_to.point)
|
|
: aState.pos + toGfxPoint(aCommand.line_to.point);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::CurveTo: {
|
|
const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No;
|
|
Point to = isRelative ? aState.pos + toGfxPoint(aCommand.curve_to.point)
|
|
: toGfxPoint(aCommand.curve_to.point);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp1 = toGfxPoint(aCommand.curve_to.control1);
|
|
Point cp2 = toGfxPoint(aCommand.curve_to.control2);
|
|
if (isRelative) {
|
|
cp1 += aState.pos;
|
|
cp2 += aState.pos;
|
|
}
|
|
aState.length +=
|
|
(float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
|
|
aState.cp2 = cp2;
|
|
aState.cp1 = to;
|
|
}
|
|
aState.pos = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::QuadBezierCurveTo: {
|
|
const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No;
|
|
Point to =
|
|
isRelative
|
|
? aState.pos + toGfxPoint(aCommand.quad_bezier_curve_to.point)
|
|
: toGfxPoint(aCommand.quad_bezier_curve_to.point);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp = isRelative
|
|
? aState.pos +
|
|
toGfxPoint(aCommand.quad_bezier_curve_to.control1)
|
|
: toGfxPoint(aCommand.quad_bezier_curve_to.control1);
|
|
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
|
|
aState.cp1 = cp;
|
|
aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::EllipticalArc: {
|
|
Point to = aCommand.elliptical_arc.absolute == StyleIsAbsolute::Yes
|
|
? toGfxPoint(aCommand.elliptical_arc.point)
|
|
: aState.pos + toGfxPoint(aCommand.elliptical_arc.point);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
const auto& arc = aCommand.elliptical_arc;
|
|
float dist = 0;
|
|
Point radii(arc.rx, arc.ry);
|
|
if (radii.x == 0.0f || radii.y == 0.0f) {
|
|
dist = CalcDistanceBetweenPoints(aState.pos, to);
|
|
} else {
|
|
Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)};
|
|
SVGArcConverter converter(aState.pos, to, radii, arc.angle,
|
|
arc.large_arc_flag._0, arc.sweep_flag._0);
|
|
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
|
|
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
|
|
bez[0] = bez[3];
|
|
}
|
|
}
|
|
aState.length += dist;
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::HorizontalLineTo: {
|
|
Point to(aCommand.horizontal_line_to.absolute == StyleIsAbsolute::Yes
|
|
? aCommand.horizontal_line_to.x
|
|
: aState.pos.x + aCommand.horizontal_line_to.x,
|
|
aState.pos.y);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += std::fabs(to.x - aState.pos.x);
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::VerticalLineTo: {
|
|
Point to(aState.pos.x,
|
|
aCommand.vertical_line_to.absolute == StyleIsAbsolute::Yes
|
|
? aCommand.vertical_line_to.y
|
|
: aState.pos.y + aCommand.vertical_line_to.y);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
aState.length += std::fabs(to.y - aState.pos.y);
|
|
aState.cp1 = aState.cp2 = to;
|
|
}
|
|
aState.pos = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::SmoothCurveTo: {
|
|
const bool isRelative =
|
|
aCommand.smooth_curve_to.absolute == StyleIsAbsolute::No;
|
|
Point to = isRelative
|
|
? aState.pos + toGfxPoint(aCommand.smooth_curve_to.point)
|
|
: toGfxPoint(aCommand.smooth_curve_to.point);
|
|
if (aState.ShouldUpdateLengthAndControlPoints()) {
|
|
Point cp1 = aState.pos - (aState.cp2 - aState.pos);
|
|
Point cp2 =
|
|
isRelative
|
|
? aState.pos + toGfxPoint(aCommand.smooth_curve_to.control2)
|
|
: toGfxPoint(aCommand.smooth_curve_to.control2);
|
|
aState.length +=
|
|
(float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
|
|
aState.cp2 = cp2;
|
|
aState.cp1 = to;
|
|
}
|
|
aState.pos = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
|
|
Point to = aCommand.smooth_curve_to.absolute == StyleIsAbsolute::Yes
|
|
? toGfxPoint(aCommand.smooth_curve_to.point)
|
|
: aState.pos + toGfxPoint(aCommand.smooth_curve_to.point);
|
|
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;
|
|
}
|
|
case StylePathCommand::Tag::Unknown:
|
|
MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
};
|
|
|
|
Maybe<EdgeDir> GetDirection(Point v) {
|
|
if (!std::isfinite(v.x) || !std::isfinite(v.y)) {
|
|
return Nothing();
|
|
}
|
|
|
|
bool x = fabs(v.x) > 0.001;
|
|
bool y = fabs(v.y) > 0.001;
|
|
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);
|
|
}
|
|
|
|
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;
|
|
// Index of the next corner.
|
|
uint32_t idx;
|
|
EdgeDir dirs[4];
|
|
|
|
bool Edge(Point from, 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 >= 4) {
|
|
// 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;
|
|
idx += 1;
|
|
currentDir = dir;
|
|
}
|
|
|
|
min.x = fmin(min.x, to.x);
|
|
min.y = fmin(min.y, to.y);
|
|
max.x = fmax(max.x, to.x);
|
|
max.y = fmax(max.y, to.y);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EndSubpath() {
|
|
if (idx != 4) {
|
|
return false;
|
|
}
|
|
|
|
if (dirs[0] != OppositeDirection(dirs[2]) ||
|
|
dirs[1] != OppositeDirection(dirs[3])) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
bool ApproxEqual(gfx::Point a, gfx::Point b) {
|
|
auto v = b - a;
|
|
return fabs(v.x) < 0.001 && fabs(v.y) < 0.001;
|
|
}
|
|
|
|
Maybe<gfx::Rect> SVGPathToAxisAlignedRect(Span<const StylePathCommand> aPath) {
|
|
Point pathStart(0.0, 0.0);
|
|
Point segStart(0.0, 0.0);
|
|
IsRectHelper helper = {
|
|
Point(0.0, 0.0),
|
|
Point(0.0, 0.0),
|
|
EdgeDir::NONE,
|
|
0,
|
|
{EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE},
|
|
};
|
|
|
|
auto ToGfxPoint = [](const StyleCoordPair& aPair) {
|
|
return Point(aPair._0, aPair._1);
|
|
};
|
|
|
|
for (const StylePathCommand& cmd : aPath) {
|
|
switch (cmd.tag) {
|
|
case StylePathCommand::Tag::MoveTo: {
|
|
Point to = ToGfxPoint(cmd.move_to.point);
|
|
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 (!ApproxEqual(pathStart, segStart)) {
|
|
// 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_to.absolute == StyleIsAbsolute::No) {
|
|
to = segStart + to;
|
|
}
|
|
|
|
pathStart = to;
|
|
segStart = to;
|
|
if (helper.idx == 0) {
|
|
helper.min = to;
|
|
helper.max = to;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::ClosePath: {
|
|
if (!helper.Edge(segStart, pathStart)) {
|
|
return Nothing();
|
|
}
|
|
if (!helper.EndSubpath()) {
|
|
return Nothing();
|
|
}
|
|
pathStart = segStart;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::LineTo: {
|
|
Point to = ToGfxPoint(cmd.line_to.point);
|
|
if (cmd.line_to.absolute == StyleIsAbsolute::No) {
|
|
to = segStart + to;
|
|
}
|
|
|
|
if (!helper.Edge(segStart, to)) {
|
|
return Nothing();
|
|
}
|
|
segStart = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::HorizontalLineTo: {
|
|
Point to = gfx::Point(cmd.horizontal_line_to.x, segStart.y);
|
|
if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) {
|
|
to.x += segStart.x;
|
|
}
|
|
|
|
if (!helper.Edge(segStart, to)) {
|
|
return Nothing();
|
|
}
|
|
segStart = to;
|
|
break;
|
|
}
|
|
case StylePathCommand::Tag::VerticalLineTo: {
|
|
Point to = gfx::Point(segStart.x, cmd.vertical_line_to.y);
|
|
if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) {
|
|
to.y += segStart.y;
|
|
}
|
|
|
|
if (!helper.Edge(segStart, to)) {
|
|
return Nothing();
|
|
}
|
|
segStart = to;
|
|
break;
|
|
}
|
|
default:
|
|
return Nothing();
|
|
}
|
|
}
|
|
|
|
if (!ApproxEqual(pathStart, segStart)) {
|
|
// Same situation as with moveto regarding stroking not fullly 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
|