svg: code refactoring

--cyclomatic complexity, no logical changes.
This commit is contained in:
Hermet Park 2024-10-15 12:49:14 +09:00
parent 6357da3b97
commit 8492a63052

View file

@ -22,10 +22,8 @@
#include "tvgMath.h" /* to include math.h before cstring */ #include "tvgMath.h" /* to include math.h before cstring */
#include <cstring> #include <cstring>
#include <string>
#include "tvgShape.h" #include "tvgShape.h"
#include "tvgCompressor.h" #include "tvgCompressor.h"
#include "tvgPaint.h"
#include "tvgFill.h" #include "tvgFill.h"
#include "tvgStr.h" #include "tvgStr.h"
#include "tvgSvgLoaderCommon.h" #include "tvgSvgLoaderCommon.h"
@ -89,7 +87,6 @@ static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity) static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
{ {
Fill::ColorStop* stops; Fill::ColorStop* stops;
int stopCount = 0;
auto fillGrad = LinearGradient::gen(); auto fillGrad = LinearGradient::gen();
auto isTransform = (g->transform ? true : false); auto isTransform = (g->transform ? true : false);
auto& finalTransform = fillGrad->transform(); auto& finalTransform = fillGrad->transform();
@ -110,27 +107,25 @@ static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient*
fillGrad->spread(g->spread); fillGrad->spread(g->spread);
//Update the stops //Update the stops
stopCount = g->stops.count; if (g->stops.count == 0) return fillGrad;
if (stopCount > 0) {
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); stops = (Fill::ColorStop*)malloc(g->stops.count * sizeof(Fill::ColorStop));
if (!stops) return fillGrad; auto prevOffset = 0.0f;
auto prevOffset = 0.0f; for (uint32_t i = 0; i < g->stops.count; ++i) {
for (uint32_t i = 0; i < g->stops.count; ++i) { auto colorStop = &g->stops[i];
auto colorStop = &g->stops[i]; //Use premultiplied color
//Use premultiplied color stops[i].r = colorStop->r;
stops[i].r = colorStop->r; stops[i].g = colorStop->g;
stops[i].g = colorStop->g; stops[i].b = colorStop->b;
stops[i].b = colorStop->b; stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255); stops[i].offset = colorStop->offset;
stops[i].offset = colorStop->offset; //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; else if (colorStop->offset > 1) stops[i].offset = 1;
else if (colorStop->offset > 1) stops[i].offset = 1; prevOffset = stops[i].offset;
prevOffset = stops[i].offset;
}
fillGrad->colorStops(stops, stopCount);
free(stops);
} }
fillGrad->colorStops(stops, g->stops.count);
free(stops);
return fillGrad; return fillGrad;
} }
@ -138,7 +133,6 @@ static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient*
static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity) static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
{ {
Fill::ColorStop *stops; Fill::ColorStop *stops;
int stopCount = 0;
auto fillGrad = RadialGradient::gen(); auto fillGrad = RadialGradient::gen();
auto isTransform = (g->transform ? true : false); auto isTransform = (g->transform ? true : false);
auto& finalTransform = fillGrad->transform(); auto& finalTransform = fillGrad->transform();
@ -163,27 +157,25 @@ static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient*
fillGrad->spread(g->spread); fillGrad->spread(g->spread);
//Update the stops //Update the stops
stopCount = g->stops.count; if (g->stops.count == 0) return fillGrad;
if (stopCount > 0) {
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); stops = (Fill::ColorStop*)malloc(g->stops.count * sizeof(Fill::ColorStop));
if (!stops) return fillGrad; auto prevOffset = 0.0f;
auto prevOffset = 0.0f; for (uint32_t i = 0; i < g->stops.count; ++i) {
for (uint32_t i = 0; i < g->stops.count; ++i) { auto colorStop = &g->stops[i];
auto colorStop = &g->stops[i]; //Use premultiplied color
//Use premultiplied color stops[i].r = colorStop->r;
stops[i].r = colorStop->r; stops[i].g = colorStop->g;
stops[i].g = colorStop->g; stops[i].b = colorStop->b;
stops[i].b = colorStop->b; stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255); stops[i].offset = colorStop->offset;
stops[i].offset = colorStop->offset; //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; else if (colorStop->offset > 1) stops[i].offset = 1;
else if (colorStop->offset > 1) stops[i].offset = 1; prevOffset = stops[i].offset;
prevOffset = stops[i].offset;
}
fillGrad->colorStops(stops, stopCount);
free(stops);
} }
fillGrad->colorStops(stops, g->stops.count);
free(stops);
return fillGrad; return fillGrad;
} }
@ -197,8 +189,7 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape*
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (node->transform) finalTransform = *node->transform; if (node->transform) finalTransform = *node->transform;
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { 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 *= {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
finalTransform *= m;
} }
if (child->transform) finalTransform = *child->transform * finalTransform; if (child->transform) finalTransform = *child->transform * finalTransform;
@ -208,9 +199,7 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape*
static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, bool clip) static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, bool clip)
{ {
if (node->type == SvgNodeType::Use) { if (node->type == SvgNodeType::Use) return _appendClipUseNode(loaderData, node, shape, vBox, svgPath);
return _appendClipUseNode(loaderData, node, shape, vBox, svgPath);
}
return _appendClipShape(loaderData, node, shape, vBox, svgPath, nullptr); return _appendClipShape(loaderData, node, shape, vBox, svgPath, nullptr);
} }
@ -228,8 +217,7 @@ static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const Svg
if (!compNode->node.clip.userSpace) { if (!compNode->node.clip.userSpace) {
float x, y, w, h; float x, y, w, h;
P(paint)->bounds(&x, &y, &w, &h, false, false); P(paint)->bounds(&x, &y, &w, &h, false, false);
Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1}; m *= {w, 0, x, 0, h, y, 0, 0, 1};
m *= mBBox;
} }
return m; return m;
} }
@ -248,7 +236,6 @@ static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const Svg
node->style->clipPath.applying = true; node->style->clipPath.applying = true;
auto comp = Shape::gen(); auto comp = Shape::gen();
auto child = compNode->child.data; auto child = compNode->child.data;
auto valid = false; //Composite only when valid shapes exist auto valid = false; //Composite only when valid shapes exist
@ -280,15 +267,11 @@ static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const Svg
if (!compNode->node.mask.userSpace) { if (!compNode->node.mask.userSpace) {
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::Mask); Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::Mask);
comp->transform(finalTransform); comp->transform(finalTransform);
} else { } else if (node->transform) {
if (node->transform) comp->transform(*node->transform); comp->transform(*node->transform);
}
if (compNode->node.mask.type == SvgMaskType::Luminance) {
paint->mask(std::move(comp), MaskMethod::Luma);
} else {
paint->mask(std::move(comp), MaskMethod::Alpha);
} }
if (compNode->node.mask.type == SvgMaskType::Luminance) paint->mask(std::move(comp), MaskMethod::Luma);
else paint->mask(std::move(comp), MaskMethod::Alpha);
} }
node->style->mask.applying = false; node->style->mask.applying = false;
@ -330,13 +313,9 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg,
vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity); 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); vg->fill((tvg::FillRule)style->fill.fillRule);
//Rendering order
vg->order(!style->paintOrder); vg->order(!style->paintOrder);
vg->opacity(style->opacity);
//Apply node opacity
if (style->opacity < 255) vg->opacity(style->opacity);
if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return; if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
@ -445,21 +424,16 @@ static unique_ptr<Shape> _shapeBuildHelper(SvgLoaderData& loaderData, SvgNode* n
static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform) static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform)
{ {
if (!_recognizeShape(node, shape)) return false;
//The 'transform' matrix has higher priority than the node->transform, since it already contains it //The 'transform' matrix has higher priority than the node->transform, since it already contains it
auto m = transform ? transform : (node->transform ? node->transform : nullptr); auto m = transform ? transform : (node->transform ? node->transform : nullptr);
uint32_t currentPtsCnt = 0; uint32_t currentPtsCnt = 0;
if (m) { if (m) {
const Point *tmp = nullptr; currentPtsCnt = shape->pathCoords(nullptr);
currentPtsCnt = shape->pathCoords(&tmp);
}
if (!_recognizeShape(node, shape)) return false;
if (m) {
const Point *pts = nullptr; const Point *pts = nullptr;
auto ptsCnt = shape->pathCoords(&pts); auto ptsCnt = shape->pathCoords(&pts);
auto p = const_cast<Point*>(pts) + currentPtsCnt; auto p = const_cast<Point*>(pts) + currentPtsCnt;
while (currentPtsCnt++ < ptsCnt) { while (currentPtsCnt++ < ptsCnt) {
*p *= *m; *p *= *m;
@ -478,10 +452,12 @@ enum class imageMimeTypeEncoding
utf8 = 0x2 utf8 = 0x2
}; };
constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b)); return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
} }
constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
return (static_cast<int>(a) & static_cast<int>(b)); return (static_cast<int>(a) & static_cast<int>(b));
} }
@ -508,38 +484,37 @@ static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mim
//mediatype := [ type "/" subtype ] *( ";" parameter ) //mediatype := [ type "/" subtype ] *( ";" parameter )
//parameter := attribute "=" value //parameter := attribute "=" value
for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) { for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) { if (strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) continue;
*href += imageMimeTypes[i].sz - 1; *href += imageMimeTypes[i].sz - 1;
*mimetype = imageMimeTypes[i].name; *mimetype = imageMimeTypes[i].name;
while (**href && **href != ',') { while (**href && **href != ',') {
while (**href && **href != ';') ++(*href); while (**href && **href != ';') ++(*href);
if (!**href) return false; if (!**href) return false;
++(*href); ++(*href);
if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) { if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) { if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
*href += sizeof("base64,") - 1; *href += sizeof("base64,") - 1;
*encoding = imageMimeTypeEncoding::base64; *encoding = imageMimeTypeEncoding::base64;
return true; //valid 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 (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) { if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
++(*href); *href += sizeof("utf8,") - 1;
*encoding = imageMimeTypeEncoding::utf8; *encoding = imageMimeTypeEncoding::utf8;
return true; //allow no encoding defined if utf8 expected return true; //valid utf8
}
} }
return false;
} }
//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; return false;
} }
@ -600,11 +575,13 @@ static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode*
TaskScheduler::async(true); TaskScheduler::async(true);
float w, h; float w, h;
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; Matrix m;
if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) { if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) {
auto sx = node->node.image.w / w; auto sx = node->node.image.w / w;
auto sy = node->node.image.h / h; auto sy = node->node.image.h / h;
m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1}; m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
} else {
m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
} }
if (node->transform) m = *node->transform * m; if (node->transform) m = *node->transform * m;
picture->transform(m); picture->transform(m);
@ -622,8 +599,7 @@ static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMee
auto tvx = box.x * sx; auto tvx = box.x * sx;
auto tvy = box.y * sy; auto tvy = box.y * sy;
if (align == AspectRatioAlign::None) if (align == AspectRatioAlign::None) return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
//Scale //Scale
if (meetOrSlice == AspectRatioMeetOrSlice::Meet) { if (meetOrSlice == AspectRatioMeetOrSlice::Meet) {
@ -703,7 +679,6 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
if (node->node.use.symbol) { if (node->node.use.symbol) {
auto symbol = node->node.use.symbol->node.symbol; auto symbol = node->node.use.symbol->node.symbol;
auto width = (symbol.hasWidth ? symbol.w : vBox.w); auto width = (symbol.hasWidth ? symbol.w : vBox.w);
if (node->node.use.isWidthSet) width = node->node.use.w; if (node->node.use.isWidthSet) width = node->node.use.w;
auto height = (symbol.hasHeight ? symbol.h : vBox.h);; auto height = (symbol.hasHeight ? symbol.h : vBox.h);;
@ -785,8 +760,10 @@ static unique_ptr<Text> _textBuildHelper(SvgLoaderData& loaderData, const SvgNod
if (!textNode->text) return nullptr; if (!textNode->text) return nullptr;
auto text = Text::gen(); auto text = Text::gen();
Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; Matrix textTransform;
if (node->transform) textTransform = *node->transform; if (node->transform) textTransform = *node->transform;
else textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
translateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize); translateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize);
text->transform(textTransform); text->transform(textTransform);
@ -812,43 +789,42 @@ static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgN
return nullptr; return nullptr;
} }
if (_isGroupType(node->type) || mask) { if (!_isGroupType(node->type) && !mask) return nullptr;
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 scene = Scene::gen();
auto child = node->child.data; // For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper()
for (uint32_t i = 0; i < node->child.count; ++i, ++child) { if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform);
if (_isGroupType((*child)->type)) {
if ((*child)->type == SvgNodeType::Use) if (!node->style->display || node->style->opacity == 0) return scene;
scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1));
else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use)) auto child = node->child.data;
scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1)); for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
if ((*child)->id) scene->id = djb2Encode((*child)->id); if (_isGroupType((*child)->type)) {
} else if ((*child)->type == SvgNodeType::Image) { if ((*child)->type == SvgNodeType::Use)
if (auto image = _imageBuildHelper(loaderData, *child, vBox, svgPath)) { scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1));
if ((*child)->id) image->id = djb2Encode((*child)->id); else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use))
scene->push(std::move(image)); scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1));
} if ((*child)->id) scene->id = djb2Encode((*child)->id);
} else if ((*child)->type == SvgNodeType::Text) { } else if ((*child)->type == SvgNodeType::Image) {
if (auto text = _textBuildHelper(loaderData, *child, vBox, svgPath)) { if (auto image = _imageBuildHelper(loaderData, *child, vBox, svgPath)) {
if ((*child)->id) text->id = djb2Encode((*child)->id); if ((*child)->id) image->id = djb2Encode((*child)->id);
scene->push(std::move(text)); scene->push(std::move(image));
} }
} else if ((*child)->type != SvgNodeType::Mask) { } else if ((*child)->type == SvgNodeType::Text) {
if (auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath)) { if (auto text = _textBuildHelper(loaderData, *child, vBox, svgPath)) {
if ((*child)->id) shape->id = djb2Encode((*child)->id); if ((*child)->id) text->id = djb2Encode((*child)->id);
scene->push(std::move(shape)); scene->push(std::move(text));
} }
} } else if ((*child)->type != SvgNodeType::Mask) {
if (auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath)) {
if ((*child)->id) shape->id = djb2Encode((*child)->id);
scene->push(std::move(shape));
} }
_applyComposition(loaderData, scene.get(), node, vBox, svgPath);
scene->opacity(node->style->opacity);
} }
return scene;
} }
return nullptr; _applyComposition(loaderData, scene.get(), node, vBox, svgPath);
scene->opacity(node->style->opacity);
return scene;
} }