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
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
|