blob: 01a8299509499f975a785991a5d553d9a7c226ae [file] [log] [blame]
/*
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
* Copyright (C) 2013 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/svg/svg_path_string_source.h"
#include "third_party/blink/renderer/core/svg/svg_parser_utilities.h"
#include "third_party/blink/renderer/platform/geometry/float_point.h"
namespace blink {
SVGPathStringSource::SVGPathStringSource(StringView source)
: is_8bit_source_(source.Is8Bit()),
previous_command_(kPathSegUnknown),
source_(source) {
DCHECK(!source.IsNull());
if (is_8bit_source_) {
current_.character8_ = source.Characters8();
end_.character8_ = current_.character8_ + source.length();
} else {
current_.character16_ = source.Characters16();
end_.character16_ = current_.character16_ + source.length();
}
EatWhitespace();
}
void SVGPathStringSource::EatWhitespace() {
if (is_8bit_source_)
SkipOptionalSVGSpaces(current_.character8_, end_.character8_);
else
SkipOptionalSVGSpaces(current_.character16_, end_.character16_);
}
static SVGPathSegType MapLetterToSegmentType(unsigned lookahead) {
switch (lookahead) {
case 'Z':
case 'z':
return kPathSegClosePath;
case 'M':
return kPathSegMoveToAbs;
case 'm':
return kPathSegMoveToRel;
case 'L':
return kPathSegLineToAbs;
case 'l':
return kPathSegLineToRel;
case 'C':
return kPathSegCurveToCubicAbs;
case 'c':
return kPathSegCurveToCubicRel;
case 'Q':
return kPathSegCurveToQuadraticAbs;
case 'q':
return kPathSegCurveToQuadraticRel;
case 'A':
return kPathSegArcAbs;
case 'a':
return kPathSegArcRel;
case 'H':
return kPathSegLineToHorizontalAbs;
case 'h':
return kPathSegLineToHorizontalRel;
case 'V':
return kPathSegLineToVerticalAbs;
case 'v':
return kPathSegLineToVerticalRel;
case 'S':
return kPathSegCurveToCubicSmoothAbs;
case 's':
return kPathSegCurveToCubicSmoothRel;
case 'T':
return kPathSegCurveToQuadraticSmoothAbs;
case 't':
return kPathSegCurveToQuadraticSmoothRel;
default:
return kPathSegUnknown;
}
}
static bool IsNumberStart(unsigned lookahead) {
return (lookahead >= '0' && lookahead <= '9') || lookahead == '+' ||
lookahead == '-' || lookahead == '.';
}
static bool MaybeImplicitCommand(unsigned lookahead,
SVGPathSegType previous_command,
SVGPathSegType& next_command) {
// Check if the current lookahead may start a number - in which case it
// could be the start of an implicit command. The 'close' command does not
// have any parameters though and hence can't have an implicit
// 'continuation'.
if (!IsNumberStart(lookahead) || previous_command == kPathSegClosePath)
return false;
// Implicit continuations of moveto command translate to linetos.
if (previous_command == kPathSegMoveToAbs) {
next_command = kPathSegLineToAbs;
return true;
}
if (previous_command == kPathSegMoveToRel) {
next_command = kPathSegLineToRel;
return true;
}
next_command = previous_command;
return true;
}
void SVGPathStringSource::SetErrorMark(SVGParseStatus status) {
if (error_.Status() != SVGParseStatus::kNoError)
return;
size_t locus = is_8bit_source_
? current_.character8_ - source_.Characters8()
: current_.character16_ - source_.Characters16();
error_ = SVGParsingError(status, locus);
}
float SVGPathStringSource::ParseNumberWithError() {
float number_value = 0;
bool error;
if (is_8bit_source_)
error = !ParseNumber(current_.character8_, end_.character8_, number_value);
else
error =
!ParseNumber(current_.character16_, end_.character16_, number_value);
if (UNLIKELY(error))
SetErrorMark(SVGParseStatus::kExpectedNumber);
return number_value;
}
bool SVGPathStringSource::ParseArcFlagWithError() {
bool flag_value = false;
bool error;
if (is_8bit_source_)
error = !ParseArcFlag(current_.character8_, end_.character8_, flag_value);
else
error = !ParseArcFlag(current_.character16_, end_.character16_, flag_value);
if (UNLIKELY(error))
SetErrorMark(SVGParseStatus::kExpectedArcFlag);
return flag_value;
}
PathSegmentData SVGPathStringSource::ParseSegment() {
DCHECK(HasMoreData());
PathSegmentData segment;
unsigned lookahead =
is_8bit_source_ ? *current_.character8_ : *current_.character16_;
SVGPathSegType command = MapLetterToSegmentType(lookahead);
if (UNLIKELY(previous_command_ == kPathSegUnknown)) {
// First command has to be a moveto.
if (command != kPathSegMoveToRel && command != kPathSegMoveToAbs) {
SetErrorMark(SVGParseStatus::kExpectedMoveToCommand);
return segment;
}
// Consume command letter.
if (is_8bit_source_)
current_.character8_++;
else
current_.character16_++;
} else if (command == kPathSegUnknown) {
// Possibly an implicit command.
DCHECK_NE(previous_command_, kPathSegUnknown);
if (!MaybeImplicitCommand(lookahead, previous_command_, command)) {
SetErrorMark(SVGParseStatus::kExpectedPathCommand);
return segment;
}
} else {
// Valid explicit command.
if (is_8bit_source_)
current_.character8_++;
else
current_.character16_++;
}
segment.command = previous_command_ = command;
DCHECK_EQ(error_.Status(), SVGParseStatus::kNoError);
switch (segment.command) {
case kPathSegCurveToCubicRel:
case kPathSegCurveToCubicAbs:
segment.point1.SetX(ParseNumberWithError());
segment.point1.SetY(ParseNumberWithError());
FALLTHROUGH;
case kPathSegCurveToCubicSmoothRel:
case kPathSegCurveToCubicSmoothAbs:
segment.point2.SetX(ParseNumberWithError());
segment.point2.SetY(ParseNumberWithError());
FALLTHROUGH;
case kPathSegMoveToRel:
case kPathSegMoveToAbs:
case kPathSegLineToRel:
case kPathSegLineToAbs:
case kPathSegCurveToQuadraticSmoothRel:
case kPathSegCurveToQuadraticSmoothAbs:
segment.target_point.SetX(ParseNumberWithError());
segment.target_point.SetY(ParseNumberWithError());
break;
case kPathSegLineToHorizontalRel:
case kPathSegLineToHorizontalAbs:
segment.target_point.SetX(ParseNumberWithError());
break;
case kPathSegLineToVerticalRel:
case kPathSegLineToVerticalAbs:
segment.target_point.SetY(ParseNumberWithError());
break;
case kPathSegClosePath:
EatWhitespace();
break;
case kPathSegCurveToQuadraticRel:
case kPathSegCurveToQuadraticAbs:
segment.point1.SetX(ParseNumberWithError());
segment.point1.SetY(ParseNumberWithError());
segment.target_point.SetX(ParseNumberWithError());
segment.target_point.SetY(ParseNumberWithError());
break;
case kPathSegArcRel:
case kPathSegArcAbs:
segment.ArcRadii().SetX(ParseNumberWithError());
segment.ArcRadii().SetY(ParseNumberWithError());
segment.SetArcAngle(ParseNumberWithError());
segment.arc_large = ParseArcFlagWithError();
segment.arc_sweep = ParseArcFlagWithError();
segment.target_point.SetX(ParseNumberWithError());
segment.target_point.SetY(ParseNumberWithError());
break;
case kPathSegUnknown:
NOTREACHED();
}
if (UNLIKELY(error_.Status() != SVGParseStatus::kNoError))
segment.command = kPathSegUnknown;
return segment;
}
} // namespace blink