feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgPngLoader.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
void PngLoader::clear()
{
png_image_free(image);
free(image);
image = nullptr;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
PngLoader::PngLoader() : ImageLoader(FileType::Png)
{
image = static_cast<png_imagep>(calloc(1, sizeof(png_image)));
image->version = PNG_IMAGE_VERSION;
image->opaque = NULL;
}
PngLoader::~PngLoader()
{
clear();
free((void*)surface.buf32);
}
bool PngLoader::open(const string& path)
{
image->opaque = NULL;
if (!png_image_begin_read_from_file(image, path.c_str())) return false;
w = (float)image->width;
h = (float)image->height;
return true;
}
bool PngLoader::open(const char* data, uint32_t size, bool copy)
{
image->opaque = NULL;
if (!png_image_begin_read_from_memory(image, data, size)) return false;
w = (float)image->width;
h = (float)image->height;
return true;
}
bool PngLoader::read()
{
if (!LoadModule::read()) return true;
if (w == 0 || h == 0) return false;
if (cs == ColorSpace::ARGB8888 || cs == ColorSpace::ARGB8888S) {
image->format = PNG_FORMAT_BGRA;
surface.cs = ColorSpace::ARGB8888;
} else {
image->format = PNG_FORMAT_RGBA;
surface.cs = ColorSpace::ABGR8888;
}
auto buffer = static_cast<png_bytep>(malloc(PNG_IMAGE_SIZE((*image))));
if (!png_image_finish_read(image, NULL, buffer, 0, NULL)) {
free(buffer);
return false;
}
//setup the surface
surface.buf32 = reinterpret_cast<uint32_t*>(buffer);
surface.stride = (uint32_t)w;
surface.w = (uint32_t)w;
surface.h = (uint32_t)h;
surface.channelSize = sizeof(uint32_t);
//TODO: we can acquire a pre-multiplied image. See "png_structrp"
surface.premultiplied = false;
clear();
return true;
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_PNG_LOADER_H_
#define _TVG_PNG_LOADER_H_
#include <png.h>
#include "tvgLoader.h"
class PngLoader : public ImageLoader
{
public:
PngLoader();
~PngLoader();
bool open(const string& path) override;
bool open(const char* data, uint32_t size, bool copy) override;
bool read() override;
private:
void clear();
png_imagep image = nullptr;
};
#endif //_TVG_PNG_LOADER_H_

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory.h>
#include <webp/decode.h>
#include "tvgWebpLoader.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
void WebpLoader::run(unsigned tid)
{
//TODO: acquire the current colorspace format & pre-multiplied alpha image.
surface.buf8 = WebPDecodeBGRA(data, size, nullptr, nullptr);
surface.stride = (uint32_t)w;
surface.w = (uint32_t)w;
surface.h = (uint32_t)h;
surface.channelSize = sizeof(uint32_t);
surface.cs = ColorSpace::ARGB8888;
surface.premultiplied = false;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
WebpLoader::WebpLoader() : ImageLoader(FileType::Webp)
{
}
WebpLoader::~WebpLoader()
{
this->done();
if (freeData) free(data);
data = nullptr;
size = 0;
freeData = false;
WebPFree(surface.buf8);
}
bool WebpLoader::open(const string& path)
{
auto webpFile = fopen(path.c_str(), "rb");
if (!webpFile) return false;
auto ret = false;
//determine size
if (fseek(webpFile, 0, SEEK_END) < 0) goto finalize;
if (((size = ftell(webpFile)) < 1)) goto finalize;
if (fseek(webpFile, 0, SEEK_SET)) goto finalize;
data = (unsigned char *) malloc(size);
if (!data) goto finalize;
freeData = true;
if (fread(data, size, 1, webpFile) < 1) goto finalize;
int width, height;
if (!WebPGetInfo(data, size, &width, &height)) goto finalize;
w = static_cast<float>(width);
h = static_cast<float>(height);
ret = true;
finalize:
fclose(webpFile);
return ret;
}
bool WebpLoader::open(const char* data, uint32_t size, bool copy)
{
if (copy) {
this->data = (unsigned char *) malloc(size);
if (!this->data) return false;
memcpy((unsigned char *)this->data, data, size);
freeData = true;
} else {
this->data = (unsigned char *) data;
freeData = false;
}
int width, height;
if (!WebPGetInfo(this->data, size, &width, &height)) return false;
w = static_cast<float>(width);
h = static_cast<float>(height);
surface.cs = ColorSpace::ARGB8888;
this->size = size;
return true;
}
bool WebpLoader::read()
{
if (!LoadModule::read()) return true;
if (!data || w == 0 || h == 0) return false;
TaskScheduler::request(this);
return true;
}
Surface* WebpLoader::bitmap()
{
this->done();
return ImageLoader::bitmap();
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_WEBP_LOADER_H_
#define _TVG_WEBP_LOADER_H_
#include "tvgLoader.h"
#include "tvgTaskScheduler.h"
class WebpLoader : public ImageLoader, public Task
{
public:
WebpLoader();
~WebpLoader();
bool open(const string& path) override;
bool open(const char* data, uint32_t size, bool copy) override;
bool read() override;
Surface* bitmap() override;
private:
void run(unsigned tid) override;
unsigned char* data = nullptr;
unsigned long size = 0;
bool freeData = false;
};
#endif //_TVG_WEBP_LOADER_H_

View file

@ -0,0 +1,132 @@
/*
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory.h>
#include "tvgJpgLoader.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
void JpgLoader::clear()
{
jpgdDelete(decoder);
if (freeData) free(data);
decoder = nullptr;
data = nullptr;
freeData = false;
}
void JpgLoader::run(unsigned tid)
{
surface.buf8 = jpgdDecompress(decoder);
surface.stride = static_cast<uint32_t>(w);
surface.w = static_cast<uint32_t>(w);
surface.h = static_cast<uint32_t>(h);
surface.cs = ColorSpace::ARGB8888;
surface.channelSize = sizeof(uint32_t);
surface.premultiplied = true;
clear();
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
JpgLoader::JpgLoader() : ImageLoader(FileType::Jpg)
{
}
JpgLoader::~JpgLoader()
{
clear();
free(surface.buf8);
}
bool JpgLoader::open(const string& path)
{
int width, height;
decoder = jpgdHeader(path.c_str(), &width, &height);
if (!decoder) return false;
w = static_cast<float>(width);
h = static_cast<float>(height);
return true;
}
bool JpgLoader::open(const char* data, uint32_t size, bool copy)
{
if (copy) {
this->data = (char *) malloc(size);
if (!this->data) return false;
memcpy((char *)this->data, data, size);
freeData = true;
} else {
this->data = (char *) data;
freeData = false;
}
int width, height;
decoder = jpgdHeader(this->data, size, &width, &height);
if (!decoder) return false;
w = static_cast<float>(width);
h = static_cast<float>(height);
return true;
}
bool JpgLoader::read()
{
if (!LoadModule::read()) return true;
if (!decoder || w == 0 || h == 0) return false;
TaskScheduler::request(this);
return true;
}
bool JpgLoader::close()
{
if (!LoadModule::close()) return false;
this->done();
return true;
}
Surface* JpgLoader::bitmap()
{
this->done();
return ImageLoader::bitmap();
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_JPG_LOADER_H_
#define _TVG_JPG_LOADER_H_
#include "tvgLoader.h"
#include "tvgTaskScheduler.h"
#include "tvgJpgd.h"
class JpgLoader : public ImageLoader, public Task
{
private:
jpeg_decoder* decoder = nullptr;
char* data = nullptr;
bool freeData = false;
void clear();
void run(unsigned tid) override;
public:
JpgLoader();
~JpgLoader();
bool open(const string& path) override;
bool open(const char* data, uint32_t size, bool copy) override;
bool read() override;
bool close() override;
Surface* bitmap() override;
};
#endif //_TVG_JPG_LOADER_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// jpgd.h - C++ class for JPEG decompression.
// Public domain, Rich Geldreich <richgel99@gmail.com>
#ifndef _TVG_JPGD_H_
#define _TVG_JPGD_H_
class jpeg_decoder;
jpeg_decoder* jpgdHeader(const char* data, int size, int* width, int* height);
jpeg_decoder* jpgdHeader(const char* filename, int* width, int* height);
unsigned char* jpgdDecompress(jpeg_decoder* decoder);
void jpgdDelete(jpeg_decoder* decoder);
#endif //_TVG_JPGD_H_

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <fstream>
#include <string.h>
#include "tvgLoader.h"
#include "tvgRawLoader.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
RawLoader::RawLoader() : ImageLoader(FileType::Raw)
{
}
RawLoader::~RawLoader()
{
if (copy) free(surface.buf32);
}
bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool copy)
{
if (!LoadModule::read()) return true;
if (!data || w == 0 || h == 0) return false;
this->w = (float)w;
this->h = (float)h;
this->copy = copy;
if (copy) {
surface.buf32 = (uint32_t*)malloc(sizeof(uint32_t) * w * h);
if (!surface.buf32) return false;
memcpy((void*)surface.buf32, data, sizeof(uint32_t) * w * h);
}
else surface.buf32 = const_cast<uint32_t*>(data);
//setup the surface
surface.stride = w;
surface.w = w;
surface.h = h;
surface.cs = ColorSpace::ARGB8888;
surface.channelSize = sizeof(uint32_t);
surface.premultiplied = true;
return true;
}
bool RawLoader::read()
{
LoadModule::read();
return true;
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_RAW_LOADER_H_
#define _TVG_RAW_LOADER_H_
class RawLoader : public ImageLoader
{
public:
bool copy = false;
RawLoader();
~RawLoader();
using LoadModule::open;
bool open(const uint32_t* data, uint32_t w, uint32_t h, bool copy);
bool read() override;
};
#endif //_TVG_RAW_LOADER_H_

View file

@ -0,0 +1,265 @@
/*
* Copyright (c) 2022 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgSvgCssStyle.h"
#include <cstring>
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static bool _isImportanceApplicable(SvgStyleFlags &toFlagsImportance, SvgStyleFlags fromFlagsImportance, SvgStyleFlags flag)
{
if (!(toFlagsImportance & flag) && (fromFlagsImportance & flag)) {
return true;
}
return false;
}
static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
{
if (from == nullptr) return;
//Copy the properties of 'from' only if they were explicitly set (not the default ones).
if ((from->curColorSet && !(to->flags & SvgStyleFlags::Color)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Color)) {
to->color = from->color;
to->curColorSet = true;
to->flags = (to->flags | SvgStyleFlags::Color);
if (from->flagsImportance & SvgStyleFlags::Color) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Color);
}
}
if (((from->flags & SvgStyleFlags::PaintOrder) && !(to->flags & SvgStyleFlags::PaintOrder)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::PaintOrder)) {
to->paintOrder = from->paintOrder;
to->flags = (to->flags | SvgStyleFlags::PaintOrder);
if (from->flagsImportance & SvgStyleFlags::PaintOrder) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::PaintOrder);
}
}
if (((from->flags & SvgStyleFlags::Display) && !(to->flags & SvgStyleFlags::Display)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Display)) {
to->display = from->display;
to->flags = (to->flags | SvgStyleFlags::Display);
if (from->flagsImportance & SvgStyleFlags::Display) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Display);
}
}
//Fill
if (((from->fill.flags & SvgFillFlags::Paint) && !(to->flags & SvgStyleFlags::Fill)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Fill)) {
to->fill.paint.color = from->fill.paint.color;
to->fill.paint.none = from->fill.paint.none;
to->fill.paint.curColor = from->fill.paint.curColor;
if (from->fill.paint.url) {
if (to->fill.paint.url) free(to->fill.paint.url);
to->fill.paint.url = strdup(from->fill.paint.url);
}
to->fill.flags = (to->fill.flags | SvgFillFlags::Paint);
to->flags = (to->flags | SvgStyleFlags::Fill);
if (from->flagsImportance & SvgStyleFlags::Fill) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Fill);
}
}
if (((from->fill.flags & SvgFillFlags::Opacity) && !(to->flags & SvgStyleFlags::FillOpacity)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::FillOpacity)) {
to->fill.opacity = from->fill.opacity;
to->fill.flags = (to->fill.flags | SvgFillFlags::Opacity);
to->flags = (to->flags | SvgStyleFlags::FillOpacity);
if (from->flagsImportance & SvgStyleFlags::FillOpacity) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::FillOpacity);
}
}
if (((from->fill.flags & SvgFillFlags::FillRule) && !(to->flags & SvgStyleFlags::FillRule)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::FillRule)) {
to->fill.fillRule = from->fill.fillRule;
to->fill.flags = (to->fill.flags | SvgFillFlags::FillRule);
to->flags = (to->flags | SvgStyleFlags::FillRule);
if (from->flagsImportance & SvgStyleFlags::FillRule) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::FillRule);
}
}
//Stroke
if (((from->stroke.flags & SvgStrokeFlags::Paint) && !(to->flags & SvgStyleFlags::Stroke)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Stroke)) {
to->stroke.paint.color = from->stroke.paint.color;
to->stroke.paint.none = from->stroke.paint.none;
to->stroke.paint.curColor = from->stroke.paint.curColor;
if (from->stroke.paint.url) {
if (to->stroke.paint.url) free(to->stroke.paint.url);
to->stroke.paint.url = strdup(from->stroke.paint.url);
}
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Paint);
to->flags = (to->flags | SvgStyleFlags::Stroke);
if (from->flagsImportance & SvgStyleFlags::Stroke) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Stroke);
}
}
if (((from->stroke.flags & SvgStrokeFlags::Opacity) && !(to->flags & SvgStyleFlags::StrokeOpacity)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeOpacity)) {
to->stroke.opacity = from->stroke.opacity;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Opacity);
to->flags = (to->flags | SvgStyleFlags::StrokeOpacity);
if (from->flagsImportance & SvgStyleFlags::StrokeOpacity) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeOpacity);
}
}
if (((from->stroke.flags & SvgStrokeFlags::Width) && !(to->flags & SvgStyleFlags::StrokeWidth)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeWidth)) {
to->stroke.width = from->stroke.width;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Width);
to->flags = (to->flags | SvgStyleFlags::StrokeWidth);
if (from->flagsImportance & SvgStyleFlags::StrokeWidth) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeWidth);
}
}
if (((from->stroke.flags & SvgStrokeFlags::Dash) && !(to->flags & SvgStyleFlags::StrokeDashArray)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeDashArray)) {
if (from->stroke.dash.array.count > 0) {
to->stroke.dash.array.clear();
to->stroke.dash.array.reserve(from->stroke.dash.array.count);
for (uint32_t i = 0; i < from->stroke.dash.array.count; ++i) {
to->stroke.dash.array.push(from->stroke.dash.array[i]);
}
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Dash);
to->flags = (to->flags | SvgStyleFlags::StrokeDashArray);
if (from->flagsImportance & SvgStyleFlags::StrokeDashArray) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeDashArray);
}
}
}
if (((from->stroke.flags & SvgStrokeFlags::Cap) && !(to->flags & SvgStyleFlags::StrokeLineCap)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeLineCap)) {
to->stroke.cap = from->stroke.cap;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Cap);
to->flags = (to->flags | SvgStyleFlags::StrokeLineCap);
if (from->flagsImportance & SvgStyleFlags::StrokeLineCap) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeLineCap);
}
}
if (((from->stroke.flags & SvgStrokeFlags::Join) && !(to->flags & SvgStyleFlags::StrokeLineJoin)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeLineJoin)) {
to->stroke.join = from->stroke.join;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Join);
to->flags = (to->flags | SvgStyleFlags::StrokeLineJoin);
if (from->flagsImportance & SvgStyleFlags::StrokeLineJoin) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeLineJoin);
}
}
//Opacity
//TODO: it can be set to be 255 and shouldn't be changed by attribute 'opacity'
if ((from->opacity < 255 && !(to->flags & SvgStyleFlags::Opacity)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Opacity)) {
to->opacity = from->opacity;
to->flags = (to->flags | SvgStyleFlags::Opacity);
if (from->flagsImportance & SvgStyleFlags::Opacity) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Opacity);
}
}
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
void cssCopyStyleAttr(SvgNode* to, const SvgNode* from)
{
//Copy matrix attribute
if (from->transform && !(to->style->flags & SvgStyleFlags::Transform)) {
to->transform = (Matrix*)malloc(sizeof(Matrix));
if (to->transform) {
*to->transform = *from->transform;
to->style->flags = (to->style->flags | SvgStyleFlags::Transform);
}
}
//Copy style attribute
_copyStyle(to->style, from->style);
if (from->style->clipPath.url) {
if (to->style->clipPath.url) free(to->style->clipPath.url);
to->style->clipPath.url = strdup(from->style->clipPath.url);
}
if (from->style->mask.url) {
if (to->style->mask.url) free(to->style->mask.url);
to->style->mask.url = strdup(from->style->mask.url);
}
}
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title, SvgNodeType type)
{
if (!style) return nullptr;
auto child = style->child.data;
for (uint32_t i = 0; i < style->child.count; ++i, ++child) {
if ((*child)->type == type) {
if ((!title && !(*child)->id) || (title && (*child)->id && !strcmp((*child)->id, title))) return (*child);
}
}
return nullptr;
}
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title)
{
if (!style || !title) return nullptr;
auto child = style->child.data;
for (uint32_t i = 0; i < style->child.count; ++i, ++child) {
if ((*child)->type == SvgNodeType::CssStyle) {
if ((*child)->id && !strcmp((*child)->id, title)) return (*child);
}
}
return nullptr;
}
void cssUpdateStyle(SvgNode* doc, SvgNode* style)
{
if (doc->child.count > 0) {
auto child = doc->child.data;
for (uint32_t i = 0; i < doc->child.count; ++i, ++child) {
if (auto cssNode = cssFindStyleNode(style, nullptr, (*child)->type)) {
cssCopyStyleAttr(*child, cssNode);
}
cssUpdateStyle(*child, style);
}
}
}
void cssApplyStyleToPostponeds(Array<SvgNodeIdPair>& postponeds, SvgNode* style)
{
for (uint32_t i = 0; i < postponeds.count; ++i) {
auto nodeIdPair = postponeds[i];
//css styling: tag.name has higher priority than .name
if (auto cssNode = cssFindStyleNode(style, nodeIdPair.id, nodeIdPair.node->type)) {
cssCopyStyleAttr(nodeIdPair.node, cssNode);
}
if (auto cssNode = cssFindStyleNode(style, nodeIdPair.id)) {
cssCopyStyleAttr(nodeIdPair.node, cssNode);
}
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SVG_CSS_STYLE_H_
#define _TVG_SVG_CSS_STYLE_H_
#include "tvgSvgLoaderCommon.h"
void cssCopyStyleAttr(SvgNode* to, const SvgNode* from);
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title, SvgNodeType type);
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title);
void cssUpdateStyle(SvgNode* doc, SvgNode* style);
void cssApplyStyleToPostponeds(Array<SvgNodeIdPair>& postponeds, SvgNode* style);
#endif //_TVG_SVG_CSS_STYLE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SVG_LOADER_H_
#define _TVG_SVG_LOADER_H_
#include "tvgTaskScheduler.h"
#include "tvgSvgLoaderCommon.h"
class SvgLoader : public ImageLoader, public Task
{
public:
string filePath;
string svgPath = "";
char* content = nullptr;
uint32_t size = 0;
SvgLoaderData loaderData;
Scene* root = nullptr;
bool copy = false;
SvgLoader();
~SvgLoader();
bool open(const string& path) override;
bool open(const char* data, uint32_t size, bool copy) override;
bool resize(Paint* paint, float w, float h) override;
bool read() override;
bool close() override;
Paint* paint() override;
private:
SvgViewFlag viewFlag = SvgViewFlag::None;
AspectRatioAlign align = AspectRatioAlign::XMidYMid;
AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet;
float vx = 0;
float vy = 0;
float vw = 0;
float vh = 0;
bool header();
void clear(bool all = true);
void run(unsigned tid) override;
};
#endif //_TVG_SVG_LOADER_H_

View file

@ -0,0 +1,587 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SVG_LOADER_COMMON_H_
#define _TVG_SVG_LOADER_COMMON_H_
#include "tvgCommon.h"
#include "tvgArray.h"
struct SvgNode;
struct SvgStyleGradient;
//NOTE: Please update simpleXmlNodeTypeToString() as well.
enum class SvgNodeType
{
Doc,
G,
Defs,
Animation,
Arc,
Circle,
Ellipse,
Image,
Line,
Path,
Polygon,
Polyline,
Rect,
Text,
TextArea,
Tspan,
Use,
Video,
ClipPath,
Mask,
CssStyle,
Symbol,
Unknown
};
/*
// TODO - remove?
enum class SvgLengthType
{
Percent,
Px,
Pc,
Pt,
Mm,
Cm,
In,
};
*/
enum class SvgFillFlags
{
Paint = 0x01,
Opacity = 0x02,
Gradient = 0x04,
FillRule = 0x08,
ClipPath = 0x16
};
constexpr bool operator &(SvgFillFlags a, SvgFillFlags b)
{
return int(a) & int(b);
}
constexpr SvgFillFlags operator |(SvgFillFlags a, SvgFillFlags b)
{
return SvgFillFlags(int(a) | int(b));
}
enum class SvgStrokeFlags
{
Paint = 0x1,
Opacity = 0x2,
Gradient = 0x4,
Scale = 0x8,
Width = 0x10,
Cap = 0x20,
Join = 0x40,
Dash = 0x80,
Miterlimit = 0x100,
DashOffset = 0x200
};
constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b)
{
return int(a) & int(b);
}
constexpr SvgStrokeFlags operator |(SvgStrokeFlags a, SvgStrokeFlags b)
{
return SvgStrokeFlags(int(a) | int(b));
}
enum class SvgGradientType
{
Linear,
Radial
};
enum class SvgStyleFlags
{
Color = 0x01,
Fill = 0x02,
FillRule = 0x04,
FillOpacity = 0x08,
Opacity = 0x010,
Stroke = 0x20,
StrokeWidth = 0x40,
StrokeLineJoin = 0x80,
StrokeLineCap = 0x100,
StrokeOpacity = 0x200,
StrokeDashArray = 0x400,
Transform = 0x800,
ClipPath = 0x1000,
Mask = 0x2000,
MaskType = 0x4000,
Display = 0x8000,
PaintOrder = 0x10000,
StrokeMiterlimit = 0x20000,
StrokeDashOffset = 0x40000,
};
constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b)
{
return int(a) & int(b);
}
constexpr SvgStyleFlags operator |(SvgStyleFlags a, SvgStyleFlags b)
{
return SvgStyleFlags(int(a) | int(b));
}
enum class SvgStopStyleFlags
{
StopDefault = 0x0,
StopOpacity = 0x01,
StopColor = 0x02
};
constexpr bool operator &(SvgStopStyleFlags a, SvgStopStyleFlags b)
{
return int(a) & int(b);
}
constexpr SvgStopStyleFlags operator |(SvgStopStyleFlags a, SvgStopStyleFlags b)
{
return SvgStopStyleFlags(int(a) | int(b));
}
enum class SvgGradientFlags
{
None = 0x0,
GradientUnits = 0x1,
SpreadMethod = 0x2,
X1 = 0x4,
X2 = 0x8,
Y1 = 0x10,
Y2 = 0x20,
Cx = 0x40,
Cy = 0x80,
R = 0x100,
Fx = 0x200,
Fy = 0x400,
Fr = 0x800
};
constexpr bool operator &(SvgGradientFlags a, SvgGradientFlags b)
{
return int(a) & int(b);
}
constexpr SvgGradientFlags operator |(SvgGradientFlags a, SvgGradientFlags b)
{
return SvgGradientFlags(int(a) | int(b));
}
enum class SvgFillRule
{
Winding = 0,
OddEven = 1
};
enum class SvgMaskType
{
Luminance = 0,
Alpha
};
//Length type to recalculate %, pt, pc, mm, cm etc
enum class SvgParserLengthType
{
Vertical,
Horizontal,
//In case of, for example, radius of radial gradient
Other
};
enum class SvgViewFlag
{
None = 0x0,
Width = 0x01, //viewPort width
Height = 0x02, //viewPort height
Viewbox = 0x04, //viewBox x,y,w,h - used only if all 4 are correctly set
WidthInPercent = 0x08,
HeightInPercent = 0x10
};
constexpr bool operator &(SvgViewFlag a, SvgViewFlag b)
{
return static_cast<int>(a) & static_cast<int>(b);
}
constexpr SvgViewFlag operator |(SvgViewFlag a, SvgViewFlag b)
{
return SvgViewFlag(int(a) | int(b));
}
constexpr SvgViewFlag operator ^(SvgViewFlag a, SvgViewFlag b)
{
return SvgViewFlag(int(a) ^ int(b));
}
enum class AspectRatioAlign
{
None,
XMinYMin,
XMidYMin,
XMaxYMin,
XMinYMid,
XMidYMid,
XMaxYMid,
XMinYMax,
XMidYMax,
XMaxYMax
};
enum class AspectRatioMeetOrSlice
{
Meet,
Slice
};
struct SvgDocNode
{
float w; //unit: point or in percentage see: SvgViewFlag
float h; //unit: point or in percentage see: SvgViewFlag
float vx;
float vy;
float vw;
float vh;
SvgViewFlag viewFlag;
SvgNode* defs;
SvgNode* style;
AspectRatioAlign align;
AspectRatioMeetOrSlice meetOrSlice;
};
struct SvgGNode
{
};
struct SvgDefsNode
{
Array<SvgStyleGradient*> gradients;
};
struct SvgSymbolNode
{
float w, h;
float vx, vy, vw, vh;
AspectRatioAlign align;
AspectRatioMeetOrSlice meetOrSlice;
bool overflowVisible;
bool hasViewBox;
bool hasWidth;
bool hasHeight;
};
struct SvgUseNode
{
float x, y, w, h;
bool isWidthSet;
bool isHeightSet;
SvgNode* symbol;
};
struct SvgEllipseNode
{
float cx;
float cy;
float rx;
float ry;
};
struct SvgCircleNode
{
float cx;
float cy;
float r;
};
struct SvgRectNode
{
float x;
float y;
float w;
float h;
float rx;
float ry;
bool hasRx;
bool hasRy;
};
struct SvgLineNode
{
float x1;
float y1;
float x2;
float y2;
};
struct SvgImageNode
{
float x, y, w, h;
char* href;
};
struct SvgPathNode
{
char* path;
};
struct SvgPolygonNode
{
Array<float> pts;
};
struct SvgClipNode
{
bool userSpace;
};
struct SvgMaskNode
{
SvgMaskType type;
bool userSpace;
};
struct SvgCssStyleNode
{
};
struct SvgTextNode
{
char* text;
char* fontFamily;
float x, y;
float fontSize;
};
struct SvgLinearGradient
{
float x1;
float y1;
float x2;
float y2;
bool isX1Percentage;
bool isY1Percentage;
bool isX2Percentage;
bool isY2Percentage;
};
struct SvgRadialGradient
{
float cx;
float cy;
float fx;
float fy;
float r;
float fr;
bool isCxPercentage;
bool isCyPercentage;
bool isFxPercentage;
bool isFyPercentage;
bool isRPercentage;
bool isFrPercentage;
};
struct SvgComposite
{
char *url;
SvgNode* node;
bool applying; //flag for checking circular dependency.
};
struct SvgColor
{
uint8_t r;
uint8_t g;
uint8_t b;
};
struct SvgPaint
{
SvgStyleGradient* gradient;
char *url;
SvgColor color;
bool none;
bool curColor;
};
struct SvgDash
{
Array<float> array;
float offset;
};
struct SvgStyleGradient
{
SvgGradientType type;
char* id;
char* ref;
FillSpread spread;
SvgRadialGradient* radial;
SvgLinearGradient* linear;
Matrix* transform;
Array<Fill::ColorStop> stops;
SvgGradientFlags flags;
bool userSpace;
void clear()
{
stops.reset();
free(transform);
free(radial);
free(linear);
free(ref);
free(id);
}
};
struct SvgStyleFill
{
SvgFillFlags flags;
SvgPaint paint;
int opacity;
FillRule fillRule;
};
struct SvgStyleStroke
{
SvgStrokeFlags flags;
SvgPaint paint;
int opacity;
float scale;
float width;
float centered;
StrokeCap cap;
StrokeJoin join;
float miterlimit;
SvgDash dash;
};
struct SvgStyleProperty
{
SvgStyleFill fill;
SvgStyleStroke stroke;
SvgComposite clipPath;
SvgComposite mask;
int opacity;
SvgColor color;
char* cssClass;
SvgStyleFlags flags;
SvgStyleFlags flagsImportance; //indicates the importance of the flag - if set, higher priority is applied (https://drafts.csswg.org/css-cascade-4/#importance)
bool curColorSet;
bool paintOrder; //true if default (fill, stroke), false otherwise
bool display;
};
struct SvgNode
{
SvgNodeType type;
SvgNode* parent;
Array<SvgNode*> child;
char *id;
SvgStyleProperty *style;
Matrix* transform;
union {
SvgGNode g;
SvgDocNode doc;
SvgDefsNode defs;
SvgUseNode use;
SvgCircleNode circle;
SvgEllipseNode ellipse;
SvgPolygonNode polygon;
SvgPolygonNode polyline;
SvgRectNode rect;
SvgPathNode path;
SvgLineNode line;
SvgImageNode image;
SvgMaskNode mask;
SvgClipNode clip;
SvgCssStyleNode cssStyle;
SvgSymbolNode symbol;
SvgTextNode text;
} node;
~SvgNode();
};
struct SvgParser
{
SvgNode* node;
SvgStyleGradient* styleGrad;
Fill::ColorStop gradStop;
SvgStopStyleFlags flags;
struct
{
float x, y, w, h;
} global;
struct
{
bool parsedFx;
bool parsedFy;
} gradient;
};
struct SvgNodeIdPair
{
SvgNode* node;
char *id;
};
enum class OpenedTagType : uint8_t
{
Other = 0,
Style,
Text
};
struct SvgLoaderData
{
Array<SvgNode*> stack;
SvgNode* doc = nullptr;
SvgNode* def = nullptr; //also used to store nested graphic nodes
SvgNode* cssStyle = nullptr;
Array<SvgStyleGradient*> gradients;
SvgStyleGradient* latestGradient = nullptr; //For stops
SvgParser* svgParse = nullptr;
Array<SvgNodeIdPair> cloneNodes;
Array<SvgNodeIdPair> nodesToStyle;
Array<char*> images; //embedded images
int level = 0;
bool result = false;
OpenedTagType openedTag = OpenedTagType::Other;
SvgNode* currentGraphicsNode = nullptr;
};
struct Box
{
float x, y, w, h;
};
#endif

View file

@ -0,0 +1,571 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* Copyright notice for the EFL:
* Copyright (C) EFL developers (see AUTHORS)
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
#include <cstring>
#include <ctype.h>
#include "tvgMath.h"
#include "tvgShape.h"
#include "tvgSvgLoaderCommon.h"
#include "tvgSvgPath.h"
#include "tvgStr.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static char* _skipComma(const char* content)
{
while (*content && isspace(*content)) {
content++;
}
if (*content == ',') return (char*)content + 1;
return (char*)content;
}
static bool _parseNumber(char** content, float* number)
{
char* end = NULL;
*number = strToFloat(*content, &end);
//If the start of string is not number
if ((*content) == end) return false;
//Skip comma if any
*content = _skipComma(end);
return true;
}
static bool _parseFlag(char** content, int* number)
{
char* end = NULL;
if (*(*content) != '0' && *(*content) != '1') return false;
*number = *(*content) - '0';
*content += 1;
end = *content;
*content = _skipComma(end);
return true;
}
void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep)
{
float cxp, cyp, cx, cy;
float sx, sy;
float cosPhi, sinPhi;
float dx2, dy2;
float x1p, y1p;
float x1p2, y1p2;
float rx2, ry2;
float lambda;
float c;
float at;
float theta1, deltaTheta;
float nat;
float delta, bcp;
float cosPhiRx, cosPhiRy;
float sinPhiRx, sinPhiRy;
float cosTheta1, sinTheta1;
int segments;
//Some helpful stuff is available here:
//http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
sx = cur->x;
sy = cur->y;
//Correction of out-of-range radii, see F6.6.1 (step 2)
rx = fabsf(rx);
ry = fabsf(ry);
angle = mathDeg2Rad(angle);
cosPhi = cosf(angle);
sinPhi = sinf(angle);
dx2 = (sx - x) / 2.0f;
dy2 = (sy - y) / 2.0f;
x1p = cosPhi * dx2 + sinPhi * dy2;
y1p = cosPhi * dy2 - sinPhi * dx2;
x1p2 = x1p * x1p;
y1p2 = y1p * y1p;
rx2 = rx * rx;
ry2 = ry * ry;
lambda = (x1p2 / rx2) + (y1p2 / ry2);
//Correction of out-of-range radii, see F6.6.2 (step 4)
if (lambda > 1.0f) {
//See F6.6.3
float lambdaRoot = sqrtf(lambda);
rx *= lambdaRoot;
ry *= lambdaRoot;
//Update rx2 and ry2
rx2 = rx * rx;
ry2 = ry * ry;
}
c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
//Check if there is no possible solution
//(i.e. we can't do a square root of a negative value)
if (c < 0.0f) {
//Scale uniformly until we have a single solution
//(see F6.2) i.e. when c == 0.0
float scale = sqrtf(1.0f - c / (rx2 * ry2));
rx *= scale;
ry *= scale;
//Update rx2 and ry2
rx2 = rx * rx;
ry2 = ry * ry;
//Step 2 (F6.5.2) - simplified since c == 0.0
cxp = 0.0f;
cyp = 0.0f;
//Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
cx = 0.0f;
cy = 0.0f;
} else {
//Complete c calculation
c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
//Inverse sign if Fa == Fs
if (largeArc == sweep) c = -c;
//Step 2 (F6.5.2)
cxp = c * (rx * y1p / ry);
cyp = c * (-ry * x1p / rx);
//Step 3 (F6.5.3 first part)
cx = cosPhi * cxp - sinPhi * cyp;
cy = sinPhi * cxp + cosPhi * cyp;
}
//Step 3 (F6.5.3 second part) we now have the center point of the ellipse
cx += (sx + x) / 2.0f;
cy += (sy + y) / 2.0f;
//Sstep 4 (F6.5.4)
//We dont' use arccos (as per w3c doc), see
//http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
//Note: atan2 (0.0, 1.0) == 0.0
at = mathAtan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
nat = mathAtan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
if (sweep) {
//Ensure delta theta < 0 or else add 360 degrees
if (deltaTheta < 0.0f) deltaTheta += 2.0f * MATH_PI;
} else {
//Ensure delta theta > 0 or else substract 360 degrees
if (deltaTheta > 0.0f) deltaTheta -= 2.0f * MATH_PI;
}
//Add several cubic bezier to approximate the arc
//(smaller than 90 degrees)
//We add one extra segment because we want something
//Smaller than 90deg (i.e. not 90 itself)
segments = static_cast<int>(fabsf(deltaTheta / MATH_PI2) + 1.0f);
delta = deltaTheta / segments;
//http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
cosPhiRx = cosPhi * rx;
cosPhiRy = cosPhi * ry;
sinPhiRx = sinPhi * rx;
sinPhiRy = sinPhi * ry;
cosTheta1 = cosf(theta1);
sinTheta1 = sinf(theta1);
for (int i = 0; i < segments; ++i) {
//End angle (for this segment) = current + delta
float c1x, c1y, ex, ey, c2x, c2y;
float theta2 = theta1 + delta;
float cosTheta2 = cosf(theta2);
float sinTheta2 = sinf(theta2);
Point p[3];
//First control point (based on start point sx,sy)
c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1);
c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1);
//End point (for this segment)
ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
//Second control point (based on end point ex,ey)
c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2);
c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2);
cmds->push(PathCommand::CubicTo);
p[0] = {c1x, c1y};
p[1] = {c2x, c2y};
p[2] = {ex, ey};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = p[1];
*cur = p[2];
//Next start point is the current end point (same for angle)
sx = ex;
sy = ey;
theta1 = theta2;
//Avoid recomputations
cosTheta1 = cosTheta2;
sinTheta1 = sinTheta2;
}
}
static int _numberCount(char cmd)
{
int count = 0;
switch (cmd) {
case 'M':
case 'm':
case 'L':
case 'l':
case 'T':
case 't': {
count = 2;
break;
}
case 'C':
case 'c':
case 'E':
case 'e': {
count = 6;
break;
}
case 'H':
case 'h':
case 'V':
case 'v': {
count = 1;
break;
}
case 'S':
case 's':
case 'Q':
case 'q': {
count = 4;
break;
}
case 'A':
case 'a': {
count = 7;
break;
}
default:
break;
}
return count;
}
static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic, bool* closed)
{
switch (cmd) {
case 'm':
case 'l':
case 'c':
case 's':
case 'q':
case 't': {
for (int i = 0; i < count - 1; i += 2) {
arr[i] = arr[i] + cur->x;
arr[i + 1] = arr[i + 1] + cur->y;
}
break;
}
case 'h': {
arr[0] = arr[0] + cur->x;
break;
}
case 'v': {
arr[0] = arr[0] + cur->y;
break;
}
case 'a': {
arr[5] = arr[5] + cur->x;
arr[6] = arr[6] + cur->y;
break;
}
default: {
break;
}
}
switch (cmd) {
case 'm':
case 'M': {
Point p = {arr[0], arr[1]};
cmds->push(PathCommand::MoveTo);
pts->push(p);
*cur = {arr[0], arr[1]};
*startPoint = {arr[0], arr[1]};
break;
}
case 'l':
case 'L': {
Point p = {arr[0], arr[1]};
cmds->push(PathCommand::LineTo);
pts->push(p);
*cur = {arr[0], arr[1]};
break;
}
case 'c':
case 'C': {
Point p[3];
cmds->push(PathCommand::CubicTo);
p[0] = {arr[0], arr[1]};
p[1] = {arr[2], arr[3]};
p[2] = {arr[4], arr[5]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = p[1];
*cur = p[2];
*isQuadratic = false;
break;
}
case 's':
case 'S': {
Point p[3], ctrl;
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
!(*isQuadratic)) {
ctrl.x = 2 * cur->x - curCtl->x;
ctrl.y = 2 * cur->y - curCtl->y;
} else {
ctrl = *cur;
}
cmds->push(PathCommand::CubicTo);
p[0] = ctrl;
p[1] = {arr[0], arr[1]};
p[2] = {arr[2], arr[3]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = p[1];
*cur = p[2];
*isQuadratic = false;
break;
}
case 'q':
case 'Q': {
Point p[3];
float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0 / 3.0);
float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0 / 3.0);
float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0 / 3.0);
float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0 / 3.0);
cmds->push(PathCommand::CubicTo);
p[0] = {ctrl_x0, ctrl_y0};
p[1] = {ctrl_x1, ctrl_y1};
p[2] = {arr[2], arr[3]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = {arr[0], arr[1]};
*cur = p[2];
*isQuadratic = true;
break;
}
case 't':
case 'T': {
Point p[3], ctrl;
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
*isQuadratic) {
ctrl.x = 2 * cur->x - curCtl->x;
ctrl.y = 2 * cur->y - curCtl->y;
} else {
ctrl = *cur;
}
float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0 / 3.0);
float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0 / 3.0);
float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0 / 3.0);
float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0 / 3.0);
cmds->push(PathCommand::CubicTo);
p[0] = {ctrl_x0, ctrl_y0};
p[1] = {ctrl_x1, ctrl_y1};
p[2] = {arr[0], arr[1]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = {ctrl.x, ctrl.y};
*cur = p[2];
*isQuadratic = true;
break;
}
case 'h':
case 'H': {
Point p = {arr[0], cur->y};
cmds->push(PathCommand::LineTo);
pts->push(p);
cur->x = arr[0];
break;
}
case 'v':
case 'V': {
Point p = {cur->x, arr[0]};
cmds->push(PathCommand::LineTo);
pts->push(p);
cur->y = arr[0];
break;
}
case 'z':
case 'Z': {
cmds->push(PathCommand::Close);
*cur = *startPoint;
*closed = true;
break;
}
case 'a':
case 'A': {
if (mathZero(arr[0]) || mathZero(arr[1])) {
Point p = {arr[5], arr[6]};
cmds->push(PathCommand::LineTo);
pts->push(p);
*cur = {arr[5], arr[6]};
} else if (!mathEqual(cur->x, arr[5]) || !mathEqual(cur->y, arr[6])) {
_pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], fabsf(arr[0]), fabsf(arr[1]), arr[2], arr[3], arr[4]);
*cur = *curCtl = {arr[5], arr[6]};
*isQuadratic = false;
}
break;
}
default: {
return false;
}
}
return true;
}
static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* closed)
{
int large, sweep;
path = _skipComma(path);
if (isalpha(*path)) {
*cmd = *path;
path++;
*count = _numberCount(*cmd);
} else {
if (*cmd == 'm') *cmd = 'l';
else if (*cmd == 'M') *cmd = 'L';
else {
if (*closed) return nullptr;
}
}
if (*count == 7) {
//Special case for arc command
if (_parseNumber(&path, &arr[0])) {
if (_parseNumber(&path, &arr[1])) {
if (_parseNumber(&path, &arr[2])) {
if (_parseFlag(&path, &large)) {
if (_parseFlag(&path, &sweep)) {
if (_parseNumber(&path, &arr[5])) {
if (_parseNumber(&path, &arr[6])) {
arr[3] = (float)large;
arr[4] = (float)sweep;
return path;
}
}
}
}
}
}
}
*count = 0;
return NULL;
}
for (int i = 0; i < *count; i++) {
if (!_parseNumber(&path, &arr[i])) {
*count = 0;
return NULL;
}
path = _skipComma(path);
}
return path;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
bool svgPathToShape(const char* svgPath, Shape* shape)
{
float numberArray[7];
int numberCount = 0;
Point cur = { 0, 0 };
Point curCtl = { 0, 0 };
Point startPoint = { 0, 0 };
char cmd = 0;
bool isQuadratic = false;
bool closed = false;
char* path = (char*)svgPath;
auto& pts = P(shape)->rs.path.pts;
auto& cmds = P(shape)->rs.path.cmds;
auto lastCmds = cmds.count;
while ((path[0] != '\0')) {
path = _nextCommand(path, &cmd, numberArray, &numberCount, &closed);
if (!path) break;
closed = false;
if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic, &closed)) break;
}
if (cmds.count > lastCmds && cmds[lastCmds] != PathCommand::MoveTo) return false;
return true;
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SVG_PATH_H_
#define _TVG_SVG_PATH_H_
#include <tvgCommon.h>
bool svgPathToShape(const char* svgPath, Shape* shape);
#endif //_TVG_SVG_PATH_H_

View file

@ -0,0 +1,954 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgMath.h" /* to include math.h before cstring */
#include <cstring>
#include <string>
#include "tvgShape.h"
#include "tvgCompressor.h"
#include "tvgPaint.h"
#include "tvgFill.h"
#include "tvgStr.h"
#include "tvgSvgLoaderCommon.h"
#include "tvgSvgSceneBuilder.h"
#include "tvgSvgPath.h"
#include "tvgSvgUtil.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform);
static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr);
static inline bool _isGroupType(SvgNodeType type)
{
if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath || type == SvgNodeType::Symbol) return true;
return false;
}
//According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
//a stroke width should be ignored for bounding box calculations
static Box _boundingBox(const Shape* shape)
{
float x, y, w, h;
shape->bounds(&x, &y, &w, &h, false);
if (auto strokeW = shape->strokeWidth()) {
x += 0.5f * strokeW;
y += 0.5f * strokeW;
w -= strokeW;
h -= strokeW;
}
return {x, y, w, h};
}
static Box _boundingBox(const Text* text)
{
float x, y, w, h;
text->bounds(&x, &y, &w, &h, false);
return {x, y, w, h};
}
static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
{
gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
gradTransf->e12 *= mBBox->e11;
gradTransf->e11 *= mBBox->e11;
gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
gradTransf->e22 *= mBBox->e22;
gradTransf->e21 *= mBBox->e22;
}
static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
{
Fill::ColorStop* stops;
int stopCount = 0;
auto fillGrad = LinearGradient::gen();
bool isTransform = (g->transform ? true : false);
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (isTransform) finalTransform = *g->transform;
if (g->userSpace) {
g->linear->x1 = g->linear->x1 * vBox.w;
g->linear->y1 = g->linear->y1 * vBox.h;
g->linear->x2 = g->linear->x2 * vBox.w;
g->linear->y2 = g->linear->y2 * vBox.h;
} else {
Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
if (isTransform) _transformMultiply(&m, &finalTransform);
else {
finalTransform = m;
isTransform = true;
}
}
if (isTransform) fillGrad->transform(finalTransform);
fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
fillGrad->spread(g->spread);
//Update the stops
stopCount = g->stops.count;
if (stopCount > 0) {
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
if (!stops) return fillGrad;
auto prevOffset = 0.0f;
for (uint32_t i = 0; i < g->stops.count; ++i) {
auto colorStop = &g->stops[i];
//Use premultiplied color
stops[i].r = colorStop->r;
stops[i].g = colorStop->g;
stops[i].b = colorStop->b;
stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
stops[i].offset = colorStop->offset;
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
else if (colorStop->offset > 1) stops[i].offset = 1;
prevOffset = stops[i].offset;
}
fillGrad->colorStops(stops, stopCount);
free(stops);
}
return fillGrad;
}
static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
{
Fill::ColorStop *stops;
int stopCount = 0;
auto fillGrad = RadialGradient::gen();
bool isTransform = (g->transform ? true : false);
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (isTransform) finalTransform = *g->transform;
if (g->userSpace) {
//The radius scalling is done according to the Units section:
//https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
g->radial->cx = g->radial->cx * vBox.w;
g->radial->cy = g->radial->cy * vBox.h;
g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
g->radial->fx = g->radial->fx * vBox.w;
g->radial->fy = g->radial->fy * vBox.h;
g->radial->fr = g->radial->fr * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
} else {
Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
if (isTransform) _transformMultiply(&m, &finalTransform);
else {
finalTransform = m;
isTransform = true;
}
}
if (isTransform) fillGrad->transform(finalTransform);
P(fillGrad)->radial(g->radial->cx, g->radial->cy, g->radial->r, g->radial->fx, g->radial->fy, g->radial->fr);
fillGrad->spread(g->spread);
//Update the stops
stopCount = g->stops.count;
if (stopCount > 0) {
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
if (!stops) return fillGrad;
auto prevOffset = 0.0f;
for (uint32_t i = 0; i < g->stops.count; ++i) {
auto colorStop = &g->stops[i];
//Use premultiplied color
stops[i].r = colorStop->r;
stops[i].g = colorStop->g;
stops[i].b = colorStop->b;
stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
stops[i].offset = colorStop->offset;
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
else if (colorStop->offset > 1) stops[i].offset = 1;
prevOffset = stops[i].offset;
}
fillGrad->colorStops(stops, stopCount);
free(stops);
}
return fillGrad;
}
//The SVG standard allows only for 'use' nodes that point directly to a basic shape.
static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
{
if (node->child.count != 1) return false;
auto child = *(node->child.data);
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (node->transform) finalTransform = *node->transform;
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
finalTransform *= m;
}
if (child->transform) finalTransform = *child->transform * finalTransform;
return _appendClipShape(loaderData, child, shape, vBox, svgPath, mathIdentity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform);
}
static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, bool clip)
{
if (node->type == SvgNodeType::Use) {
return _appendClipUseNode(loaderData, node, shape, vBox, svgPath);
}
return _appendClipShape(loaderData, node, shape, vBox, svgPath, nullptr);
}
static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const SvgNode* compNode, SvgNodeType type)
{
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
//The initial mask transformation ignored according to the SVG standard.
if (node->transform && type != SvgNodeType::Mask) {
m = *node->transform;
}
if (compNode->transform) {
m *= *compNode->transform;
}
if (!compNode->node.clip.userSpace) {
float x, y, w, h;
P(paint)->bounds(&x, &y, &w, &h, false, false);
Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1};
m *= mBBox;
}
return m;
}
static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
{
/* ClipPath */
/* Do not drop in Circular Dependency for ClipPath.
Composition can be applied recursively if its children nodes have composition target to this one. */
if (node->style->clipPath.applying) {
TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
} else {
auto compNode = node->style->clipPath.node;
if (compNode && compNode->child.count > 0) {
node->style->clipPath.applying = true;
auto comp = Shape::gen();
auto child = compNode->child.data;
auto valid = false; //Composite only when valid shapes exist
for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
if (_appendClipChild(loaderData, *child, comp.get(), vBox, svgPath, compNode->child.count > 1)) valid = true;
}
if (valid) {
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath);
comp->transform(finalTransform);
paint->composite(std::move(comp), CompositeMethod::ClipPath);
}
node->style->clipPath.applying = false;
}
}
/* Mask */
/* Do not drop in Circular Dependency for Mask.
Composition can be applied recursively if its children nodes have composition target to this one. */
if (node->style->mask.applying) {
TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
} else {
auto compNode = node->style->mask.node;
if (compNode && compNode->child.count > 0) {
node->style->mask.applying = true;
bool isMaskWhite = true;
if (auto comp = _sceneBuildHelper(loaderData, compNode, vBox, svgPath, true, 0, &isMaskWhite)) {
if (!compNode->node.mask.userSpace) {
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::Mask);
comp->transform(finalTransform);
} else {
if (node->transform) comp->transform(*node->transform);
}
if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) {
paint->composite(std::move(comp), CompositeMethod::LumaMask);
} else {
paint->composite(std::move(comp), CompositeMethod::AlphaMask);
}
}
node->style->mask.applying = false;
}
}
}
static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath, bool clip)
{
SvgStyleProperty* style = node->style;
//Clip transformation is applied directly to the path in the _appendClipShape function
if (node->transform && !clip) vg->transform(*node->transform);
if (node->type == SvgNodeType::Doc || !node->style->display) return;
//If fill property is nullptr then do nothing
if (style->fill.paint.none) {
//Do nothing
} else if (style->fill.paint.gradient) {
Box bBox = vBox;
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
vg->fill(std::move(linear));
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
vg->fill(std::move(radial));
}
} else if (style->fill.paint.url) {
//TODO: Apply the color pointed by url
TVGLOG("SVG", "The fill's url not supported.");
} else if (style->fill.paint.curColor) {
//Apply the current style color
vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
} else {
//Apply the fill color
vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
}
//Apply the fill rule
vg->fill((tvg::FillRule)style->fill.fillRule);
//Rendering order
vg->order(!style->paintOrder);
//Apply node opacity
if (style->opacity < 255) vg->opacity(style->opacity);
if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
//Apply the stroke style property
vg->stroke(style->stroke.width);
vg->stroke(style->stroke.cap);
vg->stroke(style->stroke.join);
vg->strokeMiterlimit(style->stroke.miterlimit);
if (style->stroke.dash.array.count > 0) {
P(vg)->strokeDash(style->stroke.dash.array.data, style->stroke.dash.array.count, style->stroke.dash.offset);
}
//If stroke property is nullptr then do nothing
if (style->stroke.paint.none) {
vg->stroke(0.0f);
} else if (style->stroke.paint.gradient) {
Box bBox = vBox;
if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg);
if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
vg->stroke(std::move(linear));
} else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
vg->stroke(std::move(radial));
}
} else if (style->stroke.paint.url) {
//TODO: Apply the color pointed by url
TVGLOG("SVG", "The stroke's url not supported.");
} else if (style->stroke.paint.curColor) {
//Apply the current style color
vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
} else {
//Apply the stroke color
vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
}
_applyComposition(loaderData, vg, node, vBox, svgPath);
}
static unique_ptr<Shape> _shapeBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath)
{
auto shape = Shape::gen();
if (_appendShape(loaderData, node, shape.get(), vBox, svgPath)) return shape;
else return nullptr;
}
static bool _recognizeShape(SvgNode* node, Shape* shape)
{
switch (node->type) {
case SvgNodeType::Path: {
if (node->node.path.path) {
if (!svgPathToShape(node->node.path.path, shape)) {
TVGERR("SVG", "Invalid path information.");
return false;
}
}
break;
}
case SvgNodeType::Ellipse: {
shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
break;
}
case SvgNodeType::Polygon: {
if (node->node.polygon.pts.count < 2) break;
auto pts = node->node.polygon.pts.begin();
shape->moveTo(pts[0], pts[1]);
for (pts += 2; pts < node->node.polygon.pts.end(); pts += 2) {
shape->lineTo(pts[0], pts[1]);
}
shape->close();
break;
}
case SvgNodeType::Polyline: {
if (node->node.polyline.pts.count < 2) break;
auto pts = node->node.polyline.pts.begin();
shape->moveTo(pts[0], pts[1]);
for (pts += 2; pts < node->node.polyline.pts.end(); pts += 2) {
shape->lineTo(pts[0], pts[1]);
}
break;
}
case SvgNodeType::Circle: {
shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
break;
}
case SvgNodeType::Rect: {
shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry);
break;
}
case SvgNodeType::Line: {
shape->moveTo(node->node.line.x1, node->node.line.y1);
shape->lineTo(node->node.line.x2, node->node.line.y2);
break;
}
default: {
return false;
}
}
return true;
}
static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
{
if (!_recognizeShape(node, shape)) return false;
_applyProperty(loaderData, node, shape, vBox, svgPath, false);
return true;
}
static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform)
{
//The 'transform' matrix has higher priority than the node->transform, since it already contains it
auto m = transform ? transform : (node->transform ? node->transform : nullptr);
uint32_t currentPtsCnt = 0;
if (m) {
const Point *tmp = nullptr;
currentPtsCnt = shape->pathCoords(&tmp);
}
if (!_recognizeShape(node, shape)) return false;
if (m) {
const Point *pts = nullptr;
auto ptsCnt = shape->pathCoords(&pts);
auto p = const_cast<Point*>(pts) + currentPtsCnt;
while (currentPtsCnt++ < ptsCnt) {
*p *= *m;
++p;
}
}
_applyProperty(loaderData, node, shape, vBox, svgPath, true);
return true;
}
enum class imageMimeTypeEncoding
{
base64 = 0x1,
utf8 = 0x2
};
constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
}
constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
return (static_cast<int>(a) & static_cast<int>(b));
}
static constexpr struct
{
const char* name;
int sz;
imageMimeTypeEncoding encoding;
} imageMimeTypes[] = {
{"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
{"png", sizeof("png"), imageMimeTypeEncoding::base64},
{"webp", sizeof("webp"), imageMimeTypeEncoding::base64},
{"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
};
static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
*href += sizeof("image/") - 1;
//RFC2397 data:[<mediatype>][;base64],<data>
//mediatype := [ type "/" subtype ] *( ";" parameter )
//parameter := attribute "=" value
for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
*href += imageMimeTypes[i].sz - 1;
*mimetype = imageMimeTypes[i].name;
while (**href && **href != ',') {
while (**href && **href != ';') ++(*href);
if (!**href) return false;
++(*href);
if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
*href += sizeof("base64,") - 1;
*encoding = imageMimeTypeEncoding::base64;
return true; //valid base64
}
}
if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
*href += sizeof("utf8,") - 1;
*encoding = imageMimeTypeEncoding::utf8;
return true; //valid utf8
}
}
}
//no encoding defined
if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
++(*href);
*encoding = imageMimeTypeEncoding::utf8;
return true; //allow no encoding defined if utf8 expected
}
return false;
}
}
return false;
}
#include "tvgTaskScheduler.h"
static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath)
{
if (!node->node.image.href || !strlen(node->node.image.href)) return nullptr;
auto picture = Picture::gen();
TaskScheduler::async(false); //force to load a picture on the same thread
const char* href = node->node.image.href;
if (!strncmp(href, "data:", sizeof("data:") - 1)) {
href += sizeof("data:") - 1;
const char* mimetype;
imageMimeTypeEncoding encoding;
if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
char *decoded = nullptr;
if (encoding == imageMimeTypeEncoding::base64) {
auto size = b64Decode(href, strlen(href), &decoded);
if (picture->load(decoded, size, mimetype, false) != Result::Success) {
free(decoded);
TaskScheduler::async(true);
return nullptr;
}
} else {
auto size = svgUtilURLDecode(href, &decoded);
if (picture->load(decoded, size, mimetype, false) != Result::Success) {
free(decoded);
TaskScheduler::async(true);
return nullptr;
}
}
loaderData.images.push(decoded);
} else {
if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
//TODO: protect against recursive svg image loading
//Temporarily disable embedded svg:
const char *dot = strrchr(href, '.');
if (dot && !strcmp(dot, ".svg")) {
TVGLOG("SVG", "Embedded svg file is disabled.");
TaskScheduler::async(true);
return nullptr;
}
string imagePath = href;
if (strncmp(href, "/", 1)) {
auto last = svgPath.find_last_of("/");
imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1)) + imagePath;
}
if (picture->load(imagePath) != Result::Success) {
TaskScheduler::async(true);
return nullptr;
}
}
TaskScheduler::async(true);
float w, h;
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) {
auto sx = node->node.image.w / w;
auto sy = node->node.image.h / h;
m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
}
if (node->transform) m = *node->transform * m;
picture->transform(m);
_applyComposition(loaderData, picture.get(), node, vBox, svgPath);
return picture;
}
static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, float width, float height, const Box& box)
{
auto sx = width / box.w;
auto sy = height / box.h;
auto tvx = box.x * sx;
auto tvy = box.y * sy;
if (align == AspectRatioAlign::None)
return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
//Scale
if (meetOrSlice == AspectRatioMeetOrSlice::Meet) {
if (sx < sy) sy = sx;
else sx = sy;
} else {
if (sx < sy) sx = sy;
else sy = sx;
}
//Align
tvx = box.x * sx;
tvy = box.y * sy;
auto tvw = box.w * sx;
auto tvh = box.h * sy;
switch (align) {
case AspectRatioAlign::XMinYMin: {
break;
}
case AspectRatioAlign::XMidYMin: {
tvx -= (width - tvw) * 0.5f;
break;
}
case AspectRatioAlign::XMaxYMin: {
tvx -= width - tvw;
break;
}
case AspectRatioAlign::XMinYMid: {
tvy -= (height - tvh) * 0.5f;
break;
}
case AspectRatioAlign::XMidYMid: {
tvx -= (width - tvw) * 0.5f;
tvy -= (height - tvh) * 0.5f;
break;
}
case AspectRatioAlign::XMaxYMid: {
tvx -= width - tvw;
tvy -= (height - tvh) * 0.5f;
break;
}
case AspectRatioAlign::XMinYMax: {
tvy -= height - tvh;
break;
}
case AspectRatioAlign::XMidYMax: {
tvx -= (width - tvw) * 0.5f;
tvy -= height - tvh;
break;
}
case AspectRatioAlign::XMaxYMax: {
tvx -= width - tvw;
tvy -= height - tvh;
break;
}
default: {
break;
}
}
return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
}
static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
{
unique_ptr<Scene> finalScene;
auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite);
// mUseTransform = mUseTransform * mTranslate
Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (node->transform) mUseTransform = *node->transform;
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
mUseTransform *= mTranslate;
}
if (node->node.use.symbol) {
auto symbol = node->node.use.symbol->node.symbol;
auto width = (symbol.hasWidth ? symbol.w : vBox.w);
if (node->node.use.isWidthSet) width = node->node.use.w;
auto height = (symbol.hasHeight ? symbol.h : vBox.h);;
if (node->node.use.isHeightSet) height = node->node.use.h;
auto vw = (symbol.hasViewBox ? symbol.vw : width);
auto vh = (symbol.hasViewBox ? symbol.vh : height);
Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) {
Box box = {symbol.vx, symbol.vy, vw, vh};
mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
} else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) {
mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
}
// mSceneTransform = mUseTransform * mSymbolTransform * mViewBox
Matrix mSceneTransform = mViewBox;
if (node->node.use.symbol->transform) {
mSceneTransform = *node->node.use.symbol->transform * mViewBox;
}
mSceneTransform = mUseTransform * mSceneTransform;
scene->transform(mSceneTransform);
if (node->node.use.symbol->node.symbol.overflowVisible) {
finalScene = std::move(scene);
} else {
auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, width, height, 0, 0);
// mClipTransform = mUseTransform * mSymbolTransform
Matrix mClipTransform = mUseTransform;
if (node->node.use.symbol->transform) {
mClipTransform = mUseTransform * *node->node.use.symbol->transform;
}
viewBoxClip->transform(mClipTransform);
auto compositeLayer = Scene::gen();
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(std::move(scene));
auto root = Scene::gen();
root->push(std::move(compositeLayer));
finalScene = std::move(root);
}
} else {
scene->transform(mUseTransform);
finalScene = std::move(scene);
}
return finalScene;
}
static void _applyTextFill(SvgStyleProperty* style, Text* text, const Box& vBox)
{
//If fill property is nullptr then do nothing
if (style->fill.paint.none) {
//Do nothing
} else if (style->fill.paint.gradient) {
Box bBox = vBox;
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(text);
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
text->fill(std::move(linear));
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
text->fill(std::move(radial));
}
} else if (style->fill.paint.url) {
//TODO: Apply the color pointed by url
TVGLOG("SVG", "The fill's url not supported.");
} else if (style->fill.paint.curColor) {
//Apply the current style color
text->fill(style->color.r, style->color.g, style->color.b);
text->opacity(style->fill.opacity);
} else {
//Apply the fill color
text->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b);
text->opacity(style->fill.opacity);
}
}
static unique_ptr<Text> _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath)
{
auto textNode = &node->node.text;
if (!textNode->text) return nullptr;
auto text = Text::gen();
Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (node->transform) textTransform = *node->transform;
mathTranslateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize);
text->transform(textTransform);
//TODO: handle def values of font and size as used in a system?
const float ptPerPx = 0.75f; //1 pt = 1/72; 1 in = 96 px; -> 72/96 = 0.75
auto fontSizePt = textNode->fontSize * ptPerPx;
if (textNode->fontFamily) text->font(textNode->fontFamily, fontSizePt);
text->text(textNode->text);
_applyTextFill(node->style, text.get(), vBox);
_applyComposition(loaderData, text.get(), node, vBox, svgPath);
return text;
}
static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite)
{
/* Exception handling: Prevent invalid SVG data input.
The size is the arbitrary value, we need an experimental size. */
if (depth > 2192) {
TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth);
return nullptr;
}
if (_isGroupType(node->type) || mask) {
auto scene = Scene::gen();
// For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper()
if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform);
if (node->style->display && node->style->opacity != 0) {
auto child = node->child.data;
for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
if (_isGroupType((*child)->type)) {
if ((*child)->type == SvgNodeType::Use)
scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1, isMaskWhite));
else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use))
scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1, isMaskWhite));
} else if ((*child)->type == SvgNodeType::Image) {
auto image = _imageBuildHelper(loaderData, *child, vBox, svgPath);
if (image) {
scene->push(std::move(image));
if (isMaskWhite) *isMaskWhite = false;
}
} else if ((*child)->type == SvgNodeType::Text) {
auto text = _textBuildHelper(loaderData, *child, vBox, svgPath);
if (text) scene->push(std::move(text));
} else if ((*child)->type != SvgNodeType::Mask) {
auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath);
if (shape) {
if (isMaskWhite) {
uint8_t r, g, b;
shape->fillColor(&r, &g, &b);
if (shape->fill() || r < 255 || g < 255 || b < 255 || shape->strokeFill() ||
(shape->strokeColor(&r, &g, &b) == Result::Success && (r < 255 || g < 255 || b < 255))) {
*isMaskWhite = false;
}
}
scene->push(std::move(shape));
}
}
}
_applyComposition(loaderData, scene.get(), node, vBox, svgPath);
scene->opacity(node->style->opacity);
}
return scene;
}
return nullptr;
}
static void _updateInvalidViewSize(const Scene* scene, Box& vBox, float& w, float& h, SvgViewFlag viewFlag)
{
bool validWidth = (viewFlag & SvgViewFlag::Width);
bool validHeight = (viewFlag & SvgViewFlag::Height);
float x, y;
scene->bounds(&x, &y, &vBox.w, &vBox.h, false);
if (!validWidth && !validHeight) {
vBox.x = x;
vBox.y = y;
} else {
if (validWidth) vBox.w = w;
if (validHeight) vBox.h = h;
}
//the size would have 1x1 or percentage values.
if (!validWidth) w *= vBox.w;
if (!validHeight) h *= vBox.h;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag)
{
//TODO: aspect ratio is valid only if viewBox was set
if (!loaderData.doc || (loaderData.doc->type != SvgNodeType::Doc)) return nullptr;
auto docNode = _sceneBuildHelper(loaderData, loaderData.doc, vBox, svgPath, false, 0);
if (!(viewFlag & SvgViewFlag::Viewbox)) _updateInvalidViewSize(docNode.get(), vBox, w, h, viewFlag);
if (!mathEqual(w, vBox.w) || !mathEqual(h, vBox.h)) {
Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
docNode->transform(m);
} else if (!mathZero(vBox.x) || !mathZero(vBox.y)) {
docNode->translate(-vBox.x, -vBox.y);
}
auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, w, h);
auto compositeLayer = Scene::gen();
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(std::move(docNode));
auto root = Scene::gen();
root->push(std::move(compositeLayer));
loaderData.doc->node.doc.vx = vBox.x;
loaderData.doc->node.doc.vy = vBox.y;
loaderData.doc->node.doc.vw = vBox.w;
loaderData.doc->node.doc.vh = vBox.h;
loaderData.doc->node.doc.w = w;
loaderData.doc->node.doc.h = h;
return root.release();
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SVG_SCENE_BUILDER_H_
#define _TVG_SVG_SCENE_BUILDER_H_
#include "tvgCommon.h"
Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag);
#endif //_TVG_SVG_SCENE_BUILDER_H_

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <cstring>
#include "tvgSvgUtil.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static uint8_t _hexCharToDec(const char c)
{
if (c >= 'a') return c - 'a' + 10;
else if (c >= 'A') return c - 'A' + 10;
else return c - '0';
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
size_t svgUtilURLDecode(const char *src, char** dst)
{
if (!src) return 0;
auto length = strlen(src);
if (length == 0) return 0;
char* decoded = (char*)malloc(sizeof(char) * length + 1);
char a, b;
int idx =0;
while (*src) {
if (*src == '%' &&
((a = src[1]) && (b = src[2])) &&
(isxdigit(a) && isxdigit(b))) {
decoded[idx++] = (_hexCharToDec(a) << 4) + _hexCharToDec(b);
src+=3;
} else if (*src == '+') {
decoded[idx++] = ' ';
src++;
} else {
decoded[idx++] = *src++;
}
}
decoded[idx] = '\0';
*dst = decoded;
return idx + 1;
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SVG_UTIL_H_
#define _TVG_SVG_UTIL_H_
#include "tvgCommon.h"
size_t svgUtilURLDecode(const char *src, char** dst);
#endif //_TVG_SVG_UTIL_H_

View file

@ -0,0 +1,589 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <cstring>
#include <ctype.h>
#include <string>
#ifdef _WIN32
#include <malloc.h>
#elif defined(__linux__)
#include <alloca.h>
#else
#include <stdlib.h>
#endif
#include "tvgXmlParser.h"
#include "tvgStr.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue)
{
#ifdef THORVG_LOG_ENABLED
const auto attributesNum = 6;
const struct
{
const char* tag;
bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*)
const char* value;
} attributes[] = {
{"id", false, nullptr},
{"data-name", false, nullptr},
{"overflow", false, "visible"},
{"version", false, nullptr},
{"xmlns", true, nullptr},
{"xml:space", false, nullptr},
};
for (unsigned int i = 0; i < attributesNum; ++i) {
if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) {
if (attributes[i].value && tagValue) {
if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) {
return true;
} else continue;
}
return true;
}
}
return false;
#endif
return true;
}
static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd)
{
for (; itr < itrEnd; itr++) {
if (isspace((unsigned char)*itr)) break;
}
return itr;
}
static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd)
{
for (; itr < itrEnd; itr++) {
if (!isspace((unsigned char)*itr)) break;
}
return itr;
}
static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart)
{
for (itr--; itr > itrStart; itr--) {
if (!isspace((unsigned char)*itr)) break;
}
return itr + 1;
}
static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd)
{
auto p = itr;
while (itr < itrEnd && *itr == '&') {
for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) {
itr += xmlEntityLength[i];
break;
}
}
if (itr == p) break;
p = itr;
}
return itr;
}
static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart)
{
auto p = itr;
while (itr > itrStart && *(itr - 1) == ';') {
for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
if (itr - xmlEntityLength[i] > itrStart &&
strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) {
itr -= xmlEntityLength[i];
break;
}
}
if (itr == p) break;
p = itr;
}
return itr;
}
static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd)
{
itr = _simpleXmlSkipWhiteSpace(itr, itrEnd);
auto p = itr;
while (true) {
if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr;
else break;
if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr;
else break;
}
return itr;
}
static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart)
{
itr = _simpleXmlUnskipWhiteSpace(itr, itrStart);
auto p = itr;
while (true) {
if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr;
else break;
if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr;
else break;
}
return itr;
}
static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd)
{
return (const char*)memchr(itr, '<', itrEnd - itr);
}
static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd)
{
bool insideQuote[2] = {false, false}; // 0: ", 1: '
for (; itr < itrEnd; itr++) {
if (*itr == '"' && !insideQuote[1]) insideQuote[0] = !insideQuote[0];
if (*itr == '\'' && !insideQuote[0]) insideQuote[1] = !insideQuote[1];
if (!insideQuote[0] && !insideQuote[1]) {
if ((*itr == '>') || (*itr == '<'))
return itr;
}
}
return nullptr;
}
static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd)
{
for (; itr < itrEnd; itr++) {
if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
}
return nullptr;
}
static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd)
{
for (; itr < itrEnd; itr++) {
if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
}
return nullptr;
}
static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd)
{
for (; itr < itrEnd; itr++) {
if (*itr == '>') return itr;
}
return nullptr;
}
static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff)
{
toff = 0;
if (itr[1] == '/') {
toff = 1;
return SimpleXMLType::Close;
} else if (itr[1] == '?') {
toff = 1;
return SimpleXMLType::Processing;
} else if (itr[1] == '!') {
if ((itr + sizeof("<!DOCTYPE>") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) {
toff = sizeof("!DOCTYPE") - 1;
return SimpleXMLType::Doctype;
} else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) {
toff = sizeof("![CDATA[") - 1;
return SimpleXMLType::CData;
} else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) {
toff = sizeof("!--") - 1;
return SimpleXMLType::Comment;
} else if (itr + sizeof("<!>") - 1 < itrEnd) {
toff = sizeof("!") - 1;
return SimpleXMLType::DoctypeChild;
}
return SimpleXMLType::Open;
}
return SimpleXMLType::Open;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
{
#ifdef THORVG_LOG_ENABLED
static const char* TYPE_NAMES[] = {
"Svg",
"G",
"Defs",
"Animation",
"Arc",
"Circle",
"Ellipse",
"Image",
"Line",
"Path",
"Polygon",
"Polyline",
"Rect",
"Text",
"TextArea",
"Tspan",
"Use",
"Video",
"ClipPath",
"Mask",
"Symbol",
"Unknown",
};
return TYPE_NAMES[(int) type];
#endif
return nullptr;
}
bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
{
#ifdef THORVG_LOG_ENABLED
const auto elementsNum = 1;
const char* const elements[] = { "title" };
for (unsigned int i = 0; i < elementsNum; ++i) {
if (!strncmp(tagName, elements[i], strlen(tagName))) {
return true;
}
}
return false;
#else
return true;
#endif
}
bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
{
const char *itr = buf, *itrEnd = buf + bufLength;
char* tmpBuf = (char*)malloc(bufLength + 1);
if (!buf || !func || !tmpBuf) goto error;
while (itr < itrEnd) {
const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
const char *key, *keyEnd, *value, *valueEnd;
char* tval;
if (p == itrEnd) goto success;
key = p;
for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
}
if (keyEnd == itrEnd) goto error;
if (keyEnd == key) { // There is no key. This case is invalid, but explores the following syntax.
itr = keyEnd + 1;
continue;
}
if (*keyEnd == '=') value = keyEnd + 1;
else {
value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
if (!value) goto error;
value++;
}
keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
if (value == itrEnd) goto error;
if ((*value == '"') || (*value == '\'')) {
valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
if (!valueEnd) goto error;
value++;
} else {
valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
}
itr = valueEnd + 1;
value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
memcpy(tmpBuf, key, keyEnd - key);
tmpBuf[keyEnd - key] = '\0';
tval = tmpBuf + (keyEnd - key) + 1;
int i = 0;
while (value < valueEnd) {
value = _simpleXmlSkipXmlEntities(value, valueEnd);
tval[i++] = *value;
value++;
}
tval[i] = '\0';
if (!func((void*)data, tmpBuf, tval)) {
if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", tmpBuf, tval ? tval : "NONE");
}
}
}
success:
free(tmpBuf);
return true;
error:
free(tmpBuf);
return false;
}
bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
{
const char *itr = buf, *itrEnd = buf + bufLength;
if (!buf || !func) return false;
while (itr < itrEnd) {
if (itr[0] == '<') {
//Invalid case
if (itr + 1 >= itrEnd) return false;
size_t toff = 0;
SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
const char* p;
if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
if (p) {
//Invalid case: '<' nested
if (*p == '<' && type != SimpleXMLType::Doctype) return false;
const char *start, *end;
start = itr + 1 + toff;
end = p;
switch (type) {
case SimpleXMLType::Open: {
if (p[-1] == '/') {
type = SimpleXMLType::OpenEmpty;
end--;
}
break;
}
case SimpleXMLType::CData: {
if (!memcmp(p - 2, "]]", 2)) end -= 2;
break;
}
case SimpleXMLType::Processing: {
if (p[-1] == '?') end--;
break;
}
case SimpleXMLType::Comment: {
if (!memcmp(p - 2, "--", 2)) end -= 2;
break;
}
default: {
break;
}
}
if (strip && (type != SimpleXMLType::CData)) {
start = _skipWhiteSpacesAndXmlEntities(start, end);
end = _unskipWhiteSpacesAndXmlEntities(end, start);
}
if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
itr = p + 1;
} else {
return false;
}
} else {
const char *p, *end;
if (strip) {
p = itr;
p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
if (p) {
if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
itr = p;
}
}
p = _simpleXmlFindStartTag(itr, itrEnd);
if (!p) p = itrEnd;
end = p;
if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
itr = p;
}
}
return true;
}
bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
{
const char* end;
char* key;
char* val;
char* next;
if (!buf) return false;
end = buf + bufLength;
key = (char*)alloca(end - buf + 1);
val = (char*)alloca(end - buf + 1);
if (buf == end) return true;
do {
char* sep = (char*)strchr(buf, ':');
next = (char*)strchr(buf, ';');
if (sep >= end) {
next = nullptr;
sep = nullptr;
}
if (next >= end) next = nullptr;
key[0] = '\0';
val[0] = '\0';
if (next == nullptr && sep != nullptr) {
memcpy(key, buf, sep - buf);
key[sep - buf] = '\0';
memcpy(val, sep + 1, end - sep - 1);
val[end - sep - 1] = '\0';
} else if (sep < next && sep != nullptr) {
memcpy(key, buf, sep - buf);
key[sep - buf] = '\0';
memcpy(val, sep + 1, next - sep - 1);
val[next - sep - 1] = '\0';
} else if (next) {
memcpy(key, buf, next - buf);
key[next - buf] = '\0';
}
if (key[0]) {
key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
if (!func((void*)data, key, val)) {
if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", key, val ? val : "NONE");
}
}
}
buf = next + 1;
} while (next != nullptr);
return true;
}
/*
* Supported formats:
* tag {}, .name {}, tag.name{}
*/
const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength)
{
if (!buf) return nullptr;
*tag = *name = nullptr;
*attrsLength = 0;
auto itr = _simpleXmlSkipWhiteSpace(buf, buf + bufLength);
auto itrEnd = (const char*)memchr(buf, '{', bufLength);
if (!itrEnd || itr == itrEnd) return nullptr;
auto nextElement = (const char*)memchr(itrEnd, '}', bufLength - (itrEnd - buf));
if (!nextElement) return nullptr;
*attrs = itrEnd + 1;
*attrsLength = nextElement - *attrs;
const char *p;
itrEnd = _simpleXmlUnskipWhiteSpace(itrEnd, itr);
if (*(itrEnd - 1) == '.') return nullptr;
for (p = itr; p < itrEnd; p++) {
if (*p == '.') break;
}
if (p == itr) *tag = strdup("all");
else *tag = strDuplicate(itr, p - itr);
if (p == itrEnd) *name = nullptr;
else *name = strDuplicate(p + 1, itrEnd - p - 1);
return (nextElement ? nextElement + 1 : nullptr);
}
const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
{
const char *itr = buf, *itrEnd = buf + bufLength;
for (; itr < itrEnd; itr++) {
if (!isspace((unsigned char)*itr)) {
//User skip tagname and already gave it the attributes.
if (*itr == '=') return buf;
} else {
itr = _simpleXmlUnskipXmlEntities(itr, buf);
if (itr == itrEnd) return nullptr;
return itr;
}
}
return nullptr;
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SIMPLE_XML_PARSER_H_
#define _TVG_SIMPLE_XML_PARSER_H_
#include "tvgSvgLoaderCommon.h"
#define NUMBER_OF_XML_ENTITIES 9
const char* const xmlEntity[] = {"&#10;", "&quot;", "&nbsp;", "&apos;", "&amp;", "&lt;", "&gt;", "&#035;", "&#039;"};
const int xmlEntityLength[] = {5, 6, 6, 6, 5, 4, 4, 6, 6};
enum class SimpleXMLType
{
Open = 0, //!< \<tag attribute="value"\>
OpenEmpty, //!< \<tag attribute="value" /\>
Close, //!< \</tag\>
Data, //!< tag text data
CData, //!< \<![cdata[something]]\>
Error, //!< error contents
Processing, //!< \<?xml ... ?\> \<?php .. ?\>
Doctype, //!< \<!doctype html
Comment, //!< \<!-- something --\>
Ignored, //!< whatever is ignored by parser, like whitespace
DoctypeChild //!< \<!doctype_child
};
typedef bool (*simpleXMLCb)(void* data, SimpleXMLType type, const char* content, unsigned int length);
typedef bool (*simpleXMLAttributeCb)(void* data, const char* key, const char* value);
bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data);
bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data);
bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data);
const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength);
const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength);
bool isIgnoreUnsupportedLogElements(const char* tagName);
const char* simpleXmlNodeTypeToString(SvgNodeType type);
#endif //_TVG_SIMPLE_XML_PARSER_H_