diff --git a/src/Parser.cpp b/src/Parser.cpp index 0c0b61b..b883b93 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -3,10 +3,12 @@ Parser *Parser::instance = nullptr; namespace { + // Lambda function to convert a hex color string to ColorShape auto getHexColor = [](std::string color) -> ColorShape { std::stringstream ss; int pos = color.find("#"); if (color.size() < 5 || color[pos + 4] == ' ') { + // Parse 3-digit hex color format ss << std::hex << color.substr(pos + 1, 1) << " " << color.substr(pos + 2, 1) << " " << color.substr(pos + 3, 1); int r, g, b; @@ -16,6 +18,7 @@ namespace { b = b * 16 + b; return ColorShape(r, g, b, 255); } else if (color.size() < 6 || color[pos + 5] == ' ') { + // Parse 4-digit hex color format (with alpha channel) ss << std::hex << color.substr(pos + 1, 1) << " " << color.substr(pos + 2, 1) << " " << color.substr(pos + 3, 1) << color.substr(pos + 4, 1); @@ -27,11 +30,13 @@ namespace { a = a * 16 + a; return ColorShape(r, g, b, a); } else { + // Parse 6-digit hex color format (with optional alpha channel) ss << std::hex << color.substr(pos + 1, 2) << " " << color.substr(pos + 3, 2) << " " << color.substr(pos + 5, 2); int r, g, b; ss >> r >> g >> b; if (color[pos + 7] != '\0' && color[pos + 7] != ' ') { + // Parse alpha channel if present std::stringstream ss; ss << std::hex << color.substr(pos + 7, 2); int a; @@ -42,6 +47,7 @@ namespace { } }; + // Lambda function to convert an RGB color string to ColorShape auto getRgbColor = [](std::string color) -> ColorShape { int r, g, b; float a = 1; @@ -49,6 +55,7 @@ namespace { return ColorShape(r, g, b, 255 * a); }; + // Remove extra spaces, tabs, and newlines from a string std::string removeExtraSpaces(std::string input) { input.erase(std::remove(input.begin(), input.end(), '\t'), input.end()); input.erase(std::remove(input.begin(), input.end(), '\n'), input.end()); @@ -78,6 +85,7 @@ namespace { return result; } + // Remove redundant consecutive spaces from a string void removeRedundantSpaces(std::string &path_string) { int index = 0; while (index < path_string.size()) { @@ -93,6 +101,7 @@ namespace { } } + // Insert space before each letter in a string, except 'e' void insertSpaceBeforeEachLetter(std::string &path_string) { std::string result; for (int index = 0; index < path_string.size(); index++) { @@ -118,6 +127,8 @@ namespace { path_string = result; } + // Format SVG path string by removing extra spaces and handling + // abbreviations void formatSvgPathString(std::string &path_string) { std::replace(path_string.begin(), path_string.end(), '\t', ' '); std::replace(path_string.begin(), path_string.end(), '\n', ' '); @@ -125,6 +136,7 @@ namespace { std::replace(path_string.begin(), path_string.end(), ',', ' '); removeRedundantSpaces(path_string); + // Check for and handle abbreviations in the SVG path string auto checkAbbreviation = [](const std::string &s) { int cnt = 0; for (auto c : s) @@ -155,6 +167,7 @@ namespace { } } // namespace +// Singleton design pattern: Parser class instance creation Parser *Parser::getInstance(const std::string &file_name) { if (instance == nullptr) { instance = new Parser(file_name); @@ -162,12 +175,15 @@ Parser *Parser::getInstance(const std::string &file_name) { return instance; } +// Constructor for the Parser class Parser::Parser(const std::string &file_name) { root = parseElements(file_name); } +// Get the root element of the SVG document Group *Parser::getRoot() { return dynamic_cast< Group * >(root); } +// Convert XML attribute to string representation Attributes xmlToString(rapidxml::xml_attribute<> *attribute) { Attributes attributes; while (attribute) { @@ -178,6 +194,7 @@ Attributes xmlToString(rapidxml::xml_attribute<> *attribute) { return attributes; } +// Parse SVG elements from the XML document SVGElement *Parser::parseElements(std::string file_name) { rapidxml::xml_document<> doc; std::ifstream file(file_name); @@ -201,6 +218,7 @@ SVGElement *Parser::parseElements(std::string file_name) { SVGElement *root = new Group(); SVGElement *current = root; + // Parse SVG elements while (node) { if (std::string(node->name()) == "defs") { // Parse gradients @@ -217,6 +235,8 @@ SVGElement *Parser::parseElements(std::string file_name) { if (std::string(attribute->name()) == group_attribute.first) { if (group_attribute.first == "opacity") { + // Adjust opacity if already present in the group + // and node std::string opacity = std::to_string( std::stof(attribute->value()) * std::stof(group_attribute.second)); @@ -229,6 +249,7 @@ SVGElement *Parser::parseElements(std::string file_name) { } if (!found && group_attribute.first != "transform") { + // Add missing attributes from the group to the node char *name = doc.allocate_string(group_attribute.first.c_str()); char *value = @@ -302,6 +323,7 @@ SVGElement *Parser::parseElements(std::string file_name) { return root; } +// Parse and retrieve the value of the specified attribute from the XML node std::string Parser::getAttribute(rapidxml::xml_node<> *node, std::string name) { if (name == "text") return removeExtraSpaces(node->value()); std::string result; @@ -323,10 +345,13 @@ std::string Parser::getAttribute(rapidxml::xml_node<> *node, std::string name) { return result; } +// Parse and retrieve the float value of the specified attribute from the XML +// node float Parser::getFloatAttribute(rapidxml::xml_node<> *node, std::string name) { float result; if (node->first_attribute(name.c_str()) == NULL) { if (std::string(node->name()).find("Gradient") != std::string::npos) { + // Handle gradient-specific attribute default values if (name == "x1" || name == "y1" || name == "fr") result = 0; else if (name == "cx" || name == "cy") @@ -344,6 +369,7 @@ float Parser::getFloatAttribute(rapidxml::xml_node<> *node, std::string name) { result = name == "x2" ? this->viewbox.second.x : this->viewbox.second.y; } else { + // Handle default float attribute values for other elements if (name == "stroke-width" || name == "stroke-opacity" || name == "fill-opacity" || name == "opacity" || name == "stop-opacity") @@ -353,6 +379,7 @@ float Parser::getFloatAttribute(rapidxml::xml_node<> *node, std::string name) { } } else { if (name == "width" || name == "height") { + // Handle width and height attributes with percentage or point units std::string value = node->first_attribute(name.c_str())->value(); if (value.find("%") != std::string::npos) { result = std::stof(value.substr(0, value.find("%"))) * @@ -368,6 +395,7 @@ float Parser::getFloatAttribute(rapidxml::xml_node<> *node, std::string name) { return result; } +// Parse and convert color attribute from the XML node ColorShape Parser::parseColor(rapidxml::xml_node<> *node, std::string name, std::string &id) { std::string color = getAttribute(node, name); @@ -380,6 +408,7 @@ ColorShape Parser::parseColor(rapidxml::xml_node<> *node, std::string name, else { ColorShape result; if (color.find("url") != std::string::npos) { + // Handle gradient color reference if (color.find("'") != std::string::npos) { id = color.substr(color.find("'") + 1); id.erase(id.find("'")); @@ -390,10 +419,13 @@ ColorShape Parser::parseColor(rapidxml::xml_node<> *node, std::string name, } result = ColorShape::Transparent; } else if (color.find("#") != std::string::npos) { + // Handle hex color representation result = getHexColor(color); } else if (color.find("rgb") != std::string::npos) { + // Handle RGB color representation result = getRgbColor(color); } else { + // Handle predefined color names auto color_code = color_map.find(color); if (color_code == color_map.end()) { std::cout << "Color " << color << " not found" << std::endl; @@ -410,6 +442,7 @@ ColorShape Parser::parseColor(rapidxml::xml_node<> *node, std::string name, } } +// Parse and return the Gradient object associated with the specified ID Gradient *Parser::parseGradient(std::string id) { if (gradients.find(id) == gradients.end()) { std::cout << "Gradient " << id << " not found" << std::endl; @@ -418,6 +451,7 @@ Gradient *Parser::parseGradient(std::string id) { return gradients.at(id); } +// Parse and return the gradient stops from the XML node std::vector< Stop > Parser::getGradientStops(rapidxml::xml_node<> *node) { std::vector< Stop > stops; rapidxml::xml_node<> *stop_node = node->first_node(); @@ -434,6 +468,7 @@ std::vector< Stop > Parser::getGradientStops(rapidxml::xml_node<> *node) { return stops; } +// Parse and handle gradients defined in the XML node void Parser::GetGradients(rapidxml::xml_node<> *node) { rapidxml::xml_node<> *gradient_node = node->first_node(); while (gradient_node) { @@ -484,6 +519,7 @@ void Parser::GetGradients(rapidxml::xml_node<> *node) { } } +// Parse SVG elements from the XML document std::vector< Vector2Df > Parser::parsePoints(rapidxml::xml_node<> *node) { std::vector< Vector2Df > points; std::string points_string = getAttribute(node, "points"); @@ -500,15 +536,18 @@ std::vector< Vector2Df > Parser::parsePoints(rapidxml::xml_node<> *node) { return points; } +// Parse and convert path data into a vector of PathPoint std::vector< PathPoint > Parser::parsePathPoints(rapidxml::xml_node<> *node) { std::vector< PathPoint > points; std::string path_string = getAttribute(node, "d"); + // Pre-processing the raw path string formatSvgPathString(path_string); + // Tokenizing the path string using stringstream std::stringstream ss(path_string); std::string element; - PathPoint pPoint{{0, 0}, 'M'}; + PathPoint pPoint{{0, 0}, 'M'}; // Default starting point and command while (ss >> element) { if (std::isalpha(element[0])) { pPoint.tc = element[0]; @@ -554,6 +593,7 @@ std::vector< PathPoint > Parser::parsePathPoints(rapidxml::xml_node<> *node) { std::vector< PathPoint > handle_points; + // Processing and transforming raw path points Vector2Df first_point{0, 0}, cur_point{0, 0}; int n = points.size(); for (int i = 0; i < n; i++) { @@ -649,6 +689,7 @@ std::vector< PathPoint > Parser::parsePathPoints(rapidxml::xml_node<> *node) { return handle_points; } +// Retrieve the order of transformations applied to the SVG element std::vector< std::string > Parser::getTransformOrder( rapidxml::xml_node<> *node) { std::string transform_tag; @@ -681,6 +722,7 @@ std::vector< std::string > Parser::getTransformOrder( return order; } +// Parse and convert SVG elements from the XML document SVGElement *Parser::parseShape(rapidxml::xml_node<> *node) { SVGElement *shape = NULL; std::string type = node->name(); @@ -688,6 +730,7 @@ SVGElement *Parser::parseShape(rapidxml::xml_node<> *node) { ColorShape stroke_color = parseColor(node, "stroke", id); ColorShape fill_color = parseColor(node, "fill", id); float stroke_width = getFloatAttribute(node, "stroke-width"); + // Determine the type of SVG element and create the corresponding object if (type == "line") { shape = parseLine(node, stroke_color, stroke_width); } else if (type == "rect") { @@ -706,6 +749,7 @@ SVGElement *Parser::parseShape(rapidxml::xml_node<> *node) { shape = parseText(node, fill_color, stroke_color, stroke_width); } + // Apply transformations and gradient if applicable if (shape != NULL) { if (type == "text") { float dx = getFloatAttribute(node, "dx"); @@ -725,6 +769,7 @@ SVGElement *Parser::parseShape(rapidxml::xml_node<> *node) { return shape; } +// Parse a line element Line *Parser::parseLine(rapidxml::xml_node<> *node, const ColorShape &stroke_color, float stroke_width) { Line *shape = new Line( @@ -734,6 +779,7 @@ Line *Parser::parseLine(rapidxml::xml_node<> *node, return shape; } +// Parse a rectangle element Rect *Parser::parseRect(rapidxml::xml_node<> *node, const ColorShape &fill_color, const ColorShape &stroke_color, float stroke_width) { @@ -748,6 +794,7 @@ Rect *Parser::parseRect(rapidxml::xml_node<> *node, return shape; } +// Parse a circle element Circle *Parser::parseCircle(rapidxml::xml_node<> *node, const ColorShape &fill_color, const ColorShape &stroke_color, @@ -760,6 +807,7 @@ Circle *Parser::parseCircle(rapidxml::xml_node<> *node, return shape; } +// Parse an ellipse element Ell *Parser::parseEllipse(rapidxml::xml_node<> *node, const ColorShape &fill_color, const ColorShape &stroke_color, float stroke_width) { @@ -772,6 +820,7 @@ Ell *Parser::parseEllipse(rapidxml::xml_node<> *node, return shape; } +// Parse a polygon element Plygon *Parser::parsePolygon(rapidxml::xml_node<> *node, const ColorShape &fill_color, const ColorShape &stroke_color, @@ -788,6 +837,7 @@ Plygon *Parser::parsePolygon(rapidxml::xml_node<> *node, return shape; } +// Parse a polyline element Plyline *Parser::parsePolyline(rapidxml::xml_node<> *node, const ColorShape &fill_color, const ColorShape &stroke_color, @@ -804,6 +854,7 @@ Plyline *Parser::parsePolyline(rapidxml::xml_node<> *node, return shape; } +// Parse a text element Text *Parser::parseText(rapidxml::xml_node<> *node, const ColorShape &fill_color, const ColorShape &stroke_color, float stroke_width) { @@ -828,6 +879,7 @@ Text *Parser::parseText(rapidxml::xml_node<> *node, return shape; } +// Parse a path element Path *Parser::parsePath(rapidxml::xml_node<> *node, const ColorShape &fill_color, const ColorShape &stroke_color, float stroke_width) { @@ -843,6 +895,7 @@ Path *Parser::parsePath(rapidxml::xml_node<> *node, return shape; } +// Destructor Parser::~Parser() { delete root; for (auto gradient : gradients) { @@ -850,8 +903,11 @@ Parser::~Parser() { } } +// Print data of parsed SVG elements void Parser::printShapesData() { root->printData(); } +// Get the viewBox of the SVG document std::pair< Vector2Df, Vector2Df > Parser::getViewBox() const { return viewbox; } +// Get the viewport of the SVG document Vector2Df Parser::getViewPort() const { return viewport; } \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp index fe5a8b0..8fa5ca2 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -15,6 +15,7 @@ Renderer* Renderer::getInstance() { return instance; } +// Function to extract translation values from a transform string std::pair< float, float > getTranslate(std::string transform_value) { float trans_x = 0, trans_y = 0; if (transform_value.find(",") != std::string::npos) { @@ -26,18 +27,21 @@ std::pair< float, float > getTranslate(std::string transform_value) { return std::pair< float, float >(trans_x, trans_y); } +// Function to extract rotation value from a transform string float getRotate(std::string transform_value) { float degree = 0; sscanf(transform_value.c_str(), "rotate(%f)", °ree); return degree; } +// Function to extract scale value from a transform string float getScale(std::string transform_value) { float scale = 0; sscanf(transform_value.c_str(), "scale(%f)", &scale); return scale; } +// Function to extract X and Y scale values from a transform string std::pair< float, float > getScaleXY(std::string transform_value) { float scale_x = 0, scale_y = 0; if (transform_value.find(",") != std::string::npos) @@ -47,6 +51,7 @@ std::pair< float, float > getScaleXY(std::string transform_value) { return std::pair< float, float >(scale_x, scale_y); } +// Apply transformations based on the specified order void Renderer::applyTransform(std::vector< std::string > transform_order, Gdiplus::Graphics& graphics) const { for (auto type : transform_order) { @@ -70,11 +75,17 @@ void Renderer::applyTransform(std::vector< std::string > transform_order, } } +// Draw shapes within a group, considering transformations void Renderer::draw(Gdiplus::Graphics& graphics, Group* group) const { for (auto shape : group->getElements()) { + // Store the original transformation matrix Gdiplus::Matrix original; graphics.GetTransform(&original); + + // Apply the transformations for the current shape applyTransform(shape->getTransforms(), graphics); + + // Draw the specific shape based on its class if (shape->getClass() == "Group") { Group* group = dynamic_cast< Group* >(shape); draw(graphics, group); @@ -107,15 +118,19 @@ void Renderer::draw(Gdiplus::Graphics& graphics, Group* group) const { } } +// Draw a line on the given graphics context void Renderer::drawLine(Gdiplus::Graphics& graphics, Line* line) const { + // Extract color and thickness information from the Line object ColorShape color = line->getOutlineColor(); Gdiplus::Pen linePen(Gdiplus::Color(color.a, color.r, color.g, color.b), line->getOutlineThickness()); + // Extract start and end points from the Line object Gdiplus::PointF startPoint(line->getPosition().x, line->getPosition().y); Gdiplus::PointF endPoint(line->getDirection().x, line->getDirection().y); graphics.DrawLine(&linePen, startPoint, endPoint); } +// Draw a rectangle on the given graphics context void Renderer::drawRectangle(Gdiplus::Graphics& graphics, Rect* rectangle) const { float x = rectangle->getPosition().x; @@ -124,23 +139,26 @@ void Renderer::drawRectangle(Gdiplus::Graphics& graphics, float height = rectangle->getHeight(); ColorShape outline_color = rectangle->getOutlineColor(); + // Create a pen for the rectangle outline Gdiplus::Pen rect_outline(Gdiplus::Color(outline_color.a, outline_color.r, outline_color.g, outline_color.b), rectangle->getOutlineThickness()); Gdiplus::RectF bound(x, y, width, height); Gdiplus::Brush* rect_fill = getBrush(rectangle, bound); + // Check if the rectangle has rounded corners if (rectangle->getRadius().x != 0 || rectangle->getRadius().y != 0) { float dx = rectangle->getRadius().x * 2; float dy = rectangle->getRadius().y * 2; + // Create a GraphicsPath for drawing rounded rectangles Gdiplus::GraphicsPath path; path.AddArc(x, y, dx, dy, 180, 90); path.AddArc(x + width - dx, y, dx, dy, 270, 90); path.AddArc(x + width - dx, y + height - dy, dx, dy, 0, 90); path.AddArc(x, y + height - dy, dx, dy, 90, 90); path.CloseFigure(); - + // Fill and draw the rounded rectangle if (Gdiplus::PathGradientBrush* brush = dynamic_cast< Gdiplus::PathGradientBrush* >(rect_fill)) { ColorShape color = @@ -153,6 +171,7 @@ void Renderer::drawRectangle(Gdiplus::Graphics& graphics, graphics.FillPath(rect_fill, &path); graphics.DrawPath(&rect_outline, &path); } else { + // Fill and draw the regular rectangle if (Gdiplus::PathGradientBrush* brush = dynamic_cast< Gdiplus::PathGradientBrush* >(rect_fill)) { ColorShape color = @@ -169,18 +188,22 @@ void Renderer::drawRectangle(Gdiplus::Graphics& graphics, delete rect_fill; } +// Draw a circle on the given graphics context void Renderer::drawCircle(Gdiplus::Graphics& graphics, Circle* circle) const { ColorShape outline_color = circle->getOutlineColor(); Gdiplus::Pen circle_outline( Gdiplus::Color(outline_color.a, outline_color.r, outline_color.g, outline_color.b), circle->getOutlineThickness()); + + // Create a bounding rectangle for the circle Vector2Df min_bound = circle->getMinBound(); Vector2Df max_bound = circle->getMaxBound(); Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x, max_bound.y - min_bound.y); Gdiplus::Brush* circle_fill = getBrush(circle, bound); + // Check if the circle has a gradient fill if (Gdiplus::PathGradientBrush* brush = dynamic_cast< Gdiplus::PathGradientBrush* >(circle_fill)) { ColorShape color = circle->getGradient()->getStops().back().getColor(); @@ -204,6 +227,7 @@ void Renderer::drawCircle(Gdiplus::Graphics& graphics, Circle* circle) const { delete circle_fill; } +// Draw an ellipse on the given graphics context void Renderer::drawEllipse(Gdiplus::Graphics& graphics, Ell* ellipse) const { ColorShape outline_color = ellipse->getOutlineColor(); @@ -212,6 +236,7 @@ void Renderer::drawEllipse(Gdiplus::Graphics& graphics, Ell* ellipse) const { outline_color.b), ellipse->getOutlineThickness()); + // Create a bounding rectangle for the ellipse Vector2Df min_bound = ellipse->getMinBound(); Vector2Df max_bound = ellipse->getMaxBound(); Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x, @@ -241,6 +266,7 @@ void Renderer::drawEllipse(Gdiplus::Graphics& graphics, Ell* ellipse) const { delete ellipse_fill; } +// Draw a polygon on the given graphics context void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const { ColorShape outline_color = polygon->getOutlineColor(); Gdiplus::Pen polygon_outline( @@ -248,6 +274,7 @@ void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const { outline_color.b), polygon->getOutlineThickness()); + // Extract vertices and create an array of Gdiplus::PointF Gdiplus::PointF* points = new Gdiplus::PointF[polygon->getPoints().size()]; int idx = 0; const std::vector< Vector2Df >& vertices = polygon->getPoints(); @@ -255,6 +282,7 @@ void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const { points[idx++] = Gdiplus::PointF(vertex.x, vertex.y); } + // Determine the fill mode based on the polygon's fill rule Gdiplus::FillMode fill_mode; if (polygon->getFillRule() == "evenodd") { fill_mode = Gdiplus::FillModeAlternate; @@ -262,12 +290,15 @@ void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const { fill_mode = Gdiplus::FillModeWinding; } + // Create a bounding rectangle for the polygon Vector2Df min_bound = polygon->getMinBound(); Vector2Df max_bound = polygon->getMaxBound(); Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x, max_bound.y - min_bound.y); + // Get the fill brush for the polygon Gdiplus::Brush* polygon_fill = getBrush(polygon, bound); + // If the fill brush is a gradient, fill the polygon with a corner color if (Gdiplus::PathGradientBrush* brush = dynamic_cast< Gdiplus::PathGradientBrush* >(polygon_fill)) { ColorShape color = polygon->getGradient()->getStops().back().getColor(); @@ -283,6 +314,7 @@ void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const { delete polygon_fill; } +// Draw text on the given graphics context void Renderer::drawText(Gdiplus::Graphics& graphics, Text* text) const { ColorShape outline_color = text->getOutlineColor(); graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); @@ -291,14 +323,18 @@ void Renderer::drawText(Gdiplus::Graphics& graphics, Text* text) const { outline_color.g, outline_color.b), text->getOutlineThickness()); + // Set the font family for the text Gdiplus::FontFamily font_family(L"Times New Roman"); + // Set the position for the text Gdiplus::PointF position(text->getPosition().x, text->getPosition().y); Gdiplus::GraphicsPath path; + // Convert the content to wide string for GDI+ std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > converter; std::wstring wide_content = converter.from_bytes(text->getContent()); + // Set text alignment based on anchor position Gdiplus::StringFormat string_format; if (text->getAnchor() == "middle") { string_format.SetAlignment(Gdiplus::StringAlignmentCenter); @@ -310,6 +346,7 @@ void Renderer::drawText(Gdiplus::Graphics& graphics, Text* text) const { string_format.SetAlignment(Gdiplus::StringAlignmentNear); } + // Set font style based on text style Gdiplus::FontStyle font_style = Gdiplus::FontStyleRegular; if (text->getFontStyle() == "italic" || text->getFontStyle() == "oblique") { font_style = Gdiplus::FontStyleItalic; @@ -322,6 +359,7 @@ void Renderer::drawText(Gdiplus::Graphics& graphics, Text* text) const { path.GetBounds(&bound); Gdiplus::Brush* text_fill = getBrush(text, bound); + // If the fill brush is a gradient, fill the text with a corner color if (Gdiplus::PathGradientBrush* brush = dynamic_cast< Gdiplus::PathGradientBrush* >(text_fill)) { ColorShape color = text->getGradient()->getStops().back().getColor(); @@ -343,6 +381,7 @@ void Renderer::drawText(Gdiplus::Graphics& graphics, Text* text) const { delete text_fill; } +// Draw a polyline on the given graphics context void Renderer::drawPolyline(Gdiplus::Graphics& graphics, Plyline* polyline) const { ColorShape outline_color = polyline->getOutlineColor(); @@ -351,6 +390,7 @@ void Renderer::drawPolyline(Gdiplus::Graphics& graphics, outline_color.b), polyline->getOutlineThickness()); + // Determine the fill mode based on the polyline's fill rule Gdiplus::FillMode fill_mode; if (polyline->getFillRule() == "evenodd") { fill_mode = Gdiplus::FillModeAlternate; @@ -371,12 +411,14 @@ void Renderer::drawPolyline(Gdiplus::Graphics& graphics, points[i].y); } + // Create a bounding rectangle for the polyline Vector2Df min_bound = polyline->getMinBound(); Vector2Df max_bound = polyline->getMaxBound(); Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x, max_bound.y - min_bound.y); Gdiplus::Brush* polyline_fill = getBrush(polyline, bound); + // If the fill brush is a gradient, fill the polyline with a corner color if (Gdiplus::PathGradientBrush* brush = dynamic_cast< Gdiplus::PathGradientBrush* >(polyline_fill)) { ColorShape color = @@ -392,6 +434,7 @@ void Renderer::drawPolyline(Gdiplus::Graphics& graphics, delete polyline_fill; } +// Draw a path on the given graphics context void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const { ColorShape outline_color = path->getOutlineColor(); Gdiplus::Pen path_outline(Gdiplus::Color(outline_color.a, outline_color.r, @@ -482,11 +525,17 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const { i += 1; } } else if (points[i].tc == 't') { + // Calculate reflection control point Vector2Df auto_control_point; if (i > 0 && (points[i - 1].tc == 'q' || points[i - 1].tc == 't')) { + // If the previous point is a quadratic bezier or a smooth + // quadratic bezier, + // calculate the reflection control point using the reflection + // formula auto_control_point.x = cur_point.x * 2 - points[i - 2].point.x; auto_control_point.y = cur_point.y * 2 - points[i - 2].point.y; } else { + // Otherwise, use the current point as the control point auto_control_point = cur_point; } Vector2Df end_point = points[i].point; @@ -495,11 +544,13 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const { t_points[1] = Gdiplus::PointF{auto_control_point.x, auto_control_point.y}; t_points[2] = Gdiplus::PointF{end_point.x, end_point.y}; + // Add the cubic bezier curve to the path gdi_path.AddCurve(t_points, 3); cur_point = points[i].point; } else if (points[i].tc == 'a') { float rx = points[i].radius.x; float ry = points[i].radius.y; + // If either radius is zero, treat it as a line segment if (rx == 0 || ry == 0) { gdi_path.AddLine(cur_point.x, cur_point.y, points[i].point.x, points[i].point.y); @@ -517,7 +568,7 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const { bool large_arc_flag = points[i].large_arc_flag; bool sweep_flag = points[i].sweep_flag; Vector2Df end_point{points[i].point.x, points[i].point.y}; - + // Calculate angles and points for the elliptical arc float angle = x_axis_rotation * acos(-1) / 180.0; float cosAngle = cos(angle); float sinAngle = sin(angle); @@ -527,7 +578,7 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const { float Y = (cur_point.y - end_point.y) / 2.0; point1.x = (cosAngle * cosAngle + sinAngle * sinAngle) * X; point1.y = (cosAngle * cosAngle + sinAngle * sinAngle) * Y; - + // Correction of out-of-range radii float radii_check = (point1.x * point1.x) / (rx * rx) + (point1.y * point1.y) / (ry * ry); if (radii_check > 1.0) { @@ -575,7 +626,7 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const { std::fmod((start_angle * 180.0) / acos(-1), 360); float delta_angle_degree = std::fmod((delta_angle * 180.0) / acos(-1), 360); - + // Add the elliptical arc to the path gdi_path.AddArc(center.x - rx, center.y - ry, 2.0 * rx, 2.0 * ry, start_angle_degree, delta_angle_degree); @@ -627,6 +678,8 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const { delete path_fill; } +// Get the Gdiplus::Brush for rendering an SVG element (shape) with a gradient +// or solid color Gdiplus::Brush* Renderer::getBrush(SVGElement* shape, Gdiplus::RectF bound) const { Gradient* gradient = shape->getGradient(); @@ -734,18 +787,22 @@ Gdiplus::Brush* Renderer::getBrush(SVGElement* shape, return nullptr; } +// Apply transformation matrix operations on a linear gradient brush void Renderer::applyTransformsOnBrush( std::vector< std::string > transform_order, Gdiplus::LinearGradientBrush*& brush) const { for (auto type : transform_order) { if (type.find("translate") != std::string::npos) { + // Apply translation transformation float trans_x = getTranslate(type).first, trans_y = getTranslate(type).second; brush->TranslateTransform(trans_x, trans_y); } else if (type.find("rotate") != std::string::npos) { + // Apply rotation transformation float degree = getRotate(type); brush->RotateTranform(degree); } else if (type.find("scale") != std::string::npos) { + // Apply scaling transformation if (type.find(",") != std::string::npos) { float scale_x = getScaleXY(type).first, scale_y = getScaleXY(type).second; @@ -755,6 +812,7 @@ void Renderer::applyTransformsOnBrush( brush->ScaleTransform(scale, scale); } } else if (type.find("matrix") != std::string::npos) { + // Apply matrix transformation float a = 0, b = 0, c = 0, d = 0, e = 0, f = 0; if (type.find(",") != std::string::npos) { type.erase(std::remove(type.begin(), type.end(), ','), @@ -768,6 +826,7 @@ void Renderer::applyTransformsOnBrush( } } +// Apply transformation matrix operations on a path gradient brush void Renderer::applyTransformsOnBrush( std::vector< std::string > transform_order, Gdiplus::PathGradientBrush*& brush) const {