You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
7.1 KiB

#include "pch.h"
#include "PathView.h"
#if __has_include("PathView.g.cpp")
#include "PathView.g.cpp"
#endif
#include <winrt/Microsoft.Graphics.Canvas.Svg.h>
#include "JSValueXaml.h"
#include "Utils.h"
using namespace winrt;
using namespace Microsoft::Graphics::Canvas;
using namespace Microsoft::ReactNative;
namespace winrt::RNSVG::implementation {
void PathView::UpdateProperties(IJSValueReader const &reader, bool forceUpdate, bool invalidate) {
const JSValueObject &propertyMap{JSValue::ReadObjectFrom(reader)};
for (auto const &pair : propertyMap) {
auto const &propertyName{pair.first};
auto const &propertyValue{pair.second};
if (propertyName == "d") {
m_commands.clear();
m_segmentData.clear();
if (propertyValue.IsNull()) {
m_d.clear();
} else {
m_d = propertyValue.AsString();
ParsePath();
}
}
}
__super::UpdateProperties(reader, forceUpdate, invalidate);
}
void PathView::CreateGeometry(UI::Xaml::CanvasControl const &canvas) {
auto const &resourceCreator{canvas.try_as<ICanvasResourceCreator>()};
Svg::CanvasSvgDocument doc{resourceCreator};
auto const &path{doc.CreatePathAttribute(m_segmentData, m_commands)};
Geometry(path.CreatePathGeometry(FillRule()));
}
void PathView::ParsePath() {
char prev_cmd = ' ';
size_t i{0};
auto length{m_d.length()};
while (i < length) {
SkipSpaces(i);
if (i > length) {
break;
}
bool has_prev_cmd{prev_cmd != ' '};
char first_char = m_d.at(i);
if (!has_prev_cmd && first_char != 'M' && first_char != 'm') {
throw hresult_invalid_argument(L"First segment must be a MoveTo.");
}
bool is_implicit_move_to{false};
char cmd = ' ';
if (IsCommand(first_char)) {
cmd = first_char;
m_commands.push_back(m_cmds[cmd]);
++i;
} else if (has_prev_cmd && IsNumberStart(first_char)) {
if (prev_cmd == 'Z' || prev_cmd == 'z') {
throw hresult_invalid_argument(L"ClosePath cannot be followed by a number.");
}
if (prev_cmd == 'M' || prev_cmd == 'm') {
// If a MoveTo is followed by multiple pairs of coordinates,
// the subsequent pairs are treated as implicit LineTo commands.
is_implicit_move_to = true;
if (IsUpper(prev_cmd)) {
cmd = 'L';
m_commands.push_back(m_cmds[cmd]);
} else {
cmd = 'l';
m_commands.push_back(m_cmds[cmd]);
}
} else {
cmd = prev_cmd;
m_commands.push_back(m_cmds[cmd]);
}
} else {
throw hresult_invalid_argument(L"Unexpected character: " + first_char);
}
bool absolute{IsUpper(cmd)};
switch (cmd) {
case 'm':
case 'M':
case 'l':
case 'L':
case 't':
case 'T':
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
break;
case 'h':
case 'H':
case 'v':
case 'V':
m_segmentData.push_back(ParseListNumber(i));
break;
case 's':
case 'S':
case 'q':
case 'Q':
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
break;
case 'c':
case 'C':
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
break;
case 'a':
case 'A':
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseFlag(i));
m_segmentData.push_back(ParseFlag(i));
m_segmentData.push_back(ParseListNumber(i));
m_segmentData.push_back(ParseListNumber(i));
break;
case 'z':
case 'Z':
break;
default:
throw hresult_invalid_argument(L"Unexpected command.");
}
if (is_implicit_move_to) {
if (absolute) {
prev_cmd = 'M';
} else {
prev_cmd = 'm';
}
} else {
prev_cmd = cmd;
}
}
}
void PathView::SkipSpaces(size_t &index) {
while (index < m_d.length() && IsSpace(m_d.at(index))) {
++index;
}
}
void PathView::SkipDigits(size_t& index) {
while (index < m_d.length() && IsDigit(m_d.at(index))) {
++index;
}
}
void PathView::SkipListSeparator(size_t& index) {
if (index < m_d.length() && m_d.at(index) == ',') {
++index;
}
}
bool PathView::IsCommand(char const &cmd) {
return m_cmds.find(cmd) != m_cmds.end();
}
bool PathView::IsNumberStart(char const& c) {
return IsDigit(c) || c == '.' || c == '-' || c == '+';
}
bool PathView::IsDigit(char const &c) {
return std::isdigit(static_cast<unsigned char>(c));
}
bool PathView::IsUpper(char const &c) {
return std::isupper(static_cast<unsigned char>(c));
}
bool PathView::IsSpace(char const& c) {
return std::isspace(static_cast<unsigned char>(c));
}
float PathView::ParseListNumber(size_t &index) {
if (index == m_d.length()) {
throw hresult_invalid_argument(L"Unexpected end.");
}
float result{ParseNumber(index)};
SkipSpaces(index);
SkipListSeparator(index);
return result;
}
float PathView::ParseNumber(size_t &index) {
SkipSpaces(index);
if (index == m_d.length()) {
throw hresult_invalid_argument(L"Unexpected end.");
}
size_t start = index;
char c = m_d.at(start);
// Consume sign.
if (c == '-' || c == '+') {
++index;
c = m_d.at(index);
}
// Consume integer.
if (IsDigit(c)) {
SkipDigits(index);
if (index < m_d.length()) {
c = m_d.at(index);
}
} else if (c != '.') {
throw hresult_invalid_argument(L"Invalid number formating character.");
}
// Consume fraction.
if (c == '.') {
++index;
SkipDigits(index);
if (index < m_d.length()) {
c = m_d.at(index);
}
}
if ((c == 'e' || c == 'E') && ((index + 1) < m_d.length())) {
char c2 = m_d.at(index + 1);
// Check for 'em'/'ex'
if (c2 != 'm' && c2 != 'x') {
++index;
c = m_d.at(index);
if (c == '+' || c == '-') {
++index;
SkipDigits(index);
} else if (IsDigit(c)) {
SkipDigits(index);
} else {
throw hresult_invalid_argument(L"Invalid number formating character.");
}
}
}
auto num{m_d.substr(start, index)};
auto result{std::stof(num, nullptr)};
if (std::isinf(result) || std::isnan(result)) {
throw hresult_invalid_argument(L"Invalid number.");
}
return result;
}
float PathView::ParseFlag(size_t& index) {
SkipSpaces(index);
char c = m_d.at(index);
switch (c) {
case '0':
case '1': {
++index;
if (index < m_d.length() && m_d.at(index) == ',') {
++index;
}
SkipSpaces(index);
break;
}
default:
throw hresult_invalid_argument(L"Unexpected flag.");
}
return static_cast<float>(c - '0');
}
} // namespace winrt::RNSVG::implementation