diff options
-rw-r--r-- | Libraries/LibGfx/Painter.cpp | 4 | ||||
-rw-r--r-- | Libraries/LibWeb/CMakeLists.txt | 3 | ||||
-rw-r--r-- | Libraries/LibWeb/DOM/ElementFactory.cpp | 6 | ||||
-rw-r--r-- | Libraries/LibWeb/DOM/HTMLPathElement.cpp | 499 | ||||
-rw-r--r-- | Libraries/LibWeb/DOM/HTMLPathElement.h | 123 | ||||
-rw-r--r-- | Libraries/LibWeb/DOM/HTMLSvgElement.cpp | 151 | ||||
-rw-r--r-- | Libraries/LibWeb/DOM/HTMLSvgElement.h | 66 | ||||
-rw-r--r-- | Libraries/LibWeb/DOM/SvgContext.h | 45 | ||||
-rw-r--r-- | Libraries/LibWeb/DOM/TagNames.h | 1 | ||||
-rw-r--r-- | Libraries/LibWeb/Layout/LayoutSvg.cpp | 67 | ||||
-rw-r--r-- | Libraries/LibWeb/Layout/LayoutSvg.h | 50 |
11 files changed, 1013 insertions, 2 deletions
diff --git a/Libraries/LibGfx/Painter.cpp b/Libraries/LibGfx/Painter.cpp index af82aa876f..f9679f1144 100644 --- a/Libraries/LibGfx/Painter.cpp +++ b/Libraries/LibGfx/Painter.cpp @@ -1412,7 +1412,7 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule) }); #ifdef FILL_PATH_DEBUG if ((int)scanline % 10 == 0) { - draw_text(Rect(active_list.last().x - 20, scanline, 20, 10), String::format("%d", (int)scanline)); + draw_text(IntRect(active_list.last().x - 20, scanline, 20, 10), String::format("%d", (int)scanline)); } #endif @@ -1499,7 +1499,7 @@ void Painter::fill_path(Path& path, Color color, WindingRule winding_rule) #ifdef FILL_PATH_DEBUG size_t i { 0 }; for (auto& segment : segments) - draw_line(Point(segment.from.x(), segment.from.y()), Point(segment.to.x(), segment.to.y()), Color::from_hsv(++i / segments.size() * 255, 255, 255), 1); + draw_line(IntPoint(segment.from.x(), segment.from.y()), IntPoint(segment.to.x(), segment.to.y()), Color::from_hsv(++i / segments.size() * 255, 255, 255), 1); #endif } diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index f8ba964892..c3ef63162e 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -46,8 +46,10 @@ set(SOURCES DOM/HTMLInputElement.cpp DOM/HTMLObjectElement.cpp DOM/HTMLLinkElement.cpp + DOM/HTMLPathElement.cpp DOM/HTMLScriptElement.cpp DOM/HTMLStyleElement.cpp + DOM/HTMLSvgElement.cpp DOM/HTMLTableElement.cpp DOM/HTMLTableCellElement.cpp DOM/HTMLTableRowElement.cpp @@ -79,6 +81,7 @@ set(SOURCES Layout/LayoutNode.cpp Layout/LayoutPosition.cpp Layout/LayoutReplaced.cpp + Layout/LayoutSvg.cpp Layout/LayoutTable.cpp Layout/LayoutTableCell.cpp Layout/LayoutTableRow.cpp diff --git a/Libraries/LibWeb/DOM/ElementFactory.cpp b/Libraries/LibWeb/DOM/ElementFactory.cpp index 213a228d2c..cb980db4b4 100644 --- a/Libraries/LibWeb/DOM/ElementFactory.cpp +++ b/Libraries/LibWeb/DOM/ElementFactory.cpp @@ -41,8 +41,10 @@ #include <LibWeb/DOM/HTMLInputElement.h> #include <LibWeb/DOM/HTMLLinkElement.h> #include <LibWeb/DOM/HTMLObjectElement.h> +#include <LibWeb/DOM/HTMLPathElement.h> #include <LibWeb/DOM/HTMLScriptElement.h> #include <LibWeb/DOM/HTMLStyleElement.h> +#include <LibWeb/DOM/HTMLSvgElement.h> #include <LibWeb/DOM/HTMLTableCellElement.h> #include <LibWeb/DOM/HTMLTableElement.h> #include <LibWeb/DOM/HTMLTableRowElement.h> @@ -97,6 +99,10 @@ NonnullRefPtr<Element> create_element(Document& document, const FlyString& tag_n return adopt(*new HTMLCanvasElement(document, lowercase_tag_name)); if (lowercase_tag_name == HTML::TagNames::object) return adopt(*new HTMLObjectElement(document, lowercase_tag_name)); + if (lowercase_tag_name == HTML::TagNames::svg) + return adopt(*new HTMLSvgElement(document, lowercase_tag_name)); + if (lowercase_tag_name == HTML::TagNames::path) + return adopt(*new HTMLPathElement(document, lowercase_tag_name)); return adopt(*new Element(document, lowercase_tag_name)); } diff --git a/Libraries/LibWeb/DOM/HTMLPathElement.cpp b/Libraries/LibWeb/DOM/HTMLPathElement.cpp new file mode 100644 index 0000000000..edc114ed7a --- /dev/null +++ b/Libraries/LibWeb/DOM/HTMLPathElement.cpp @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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. + */ + +#include <AK/StringBuilder.h> +#include <LibGfx/Path.h> +#include <LibWeb/DOM/Document.h> +#include <LibWeb/DOM/Event.h> +#include <LibWeb/DOM/HTMLPathElement.h> +#include <ctype.h> + +//#define PATH_DEBUG + +namespace Web { + +PathDataParser::PathDataParser(const String& source) + : m_source(source) +{ +} + +Vector<PathInstruction> PathDataParser::parse() +{ + parse_whitespace(); + while (!done()) + parse_drawto(); + if (!m_instructions.is_empty() && m_instructions[0].type != PathInstructionType::Move) + ASSERT_NOT_REACHED(); + return m_instructions; +} + +void PathDataParser::parse_drawto() { + if (match('M') || match('m')) { + parse_moveto(); + } else if (match('Z') || match('z')) { + parse_closepath(); + } else if (match('L') || match('l')) { + parse_lineto(); + } else if (match('H') || match('h')) { + parse_horizontal_lineto(); + } else if (match('V') || match('v')) { + parse_vertical_lineto(); + } else if (match('C') || match('c')) { + parse_curveto(); + } else if (match('S') || match('s')) { + parse_smooth_curveto(); + } else if (match('Q') || match('q')) { + parse_quadratic_bezier_curveto(); + } else if (match('T') || match('t')) { + parse_smooth_quadratic_bezier_curveto(); + } else if (match('A') || match('a')) { + parse_elliptical_arc(); + } +} + +void PathDataParser::parse_moveto() +{ + bool absolute = consume() == 'M'; + parse_whitespace(); + for (auto& pair : parse_coordinate_pair_sequence()) + m_instructions.append({ PathInstructionType::Move, absolute, pair }); +} + +void PathDataParser::parse_closepath() +{ + bool absolute = consume() == 'Z'; + m_instructions.append({ PathInstructionType::ClosePath, absolute, {} }); +} + +void PathDataParser::parse_lineto() +{ + bool absolute = consume() == 'L'; + parse_whitespace(); + for (auto& pair : parse_coordinate_pair_sequence()) + m_instructions.append({ PathInstructionType::Line, absolute, pair }); +} + +void PathDataParser::parse_horizontal_lineto() +{ + bool absolute = consume() == 'H'; + parse_whitespace(); + m_instructions.append({ PathInstructionType::HorizontalLine, absolute, parse_coordinate_sequence() }); +} + +void PathDataParser::parse_vertical_lineto() +{ + bool absolute = consume() == 'V'; + parse_whitespace(); + m_instructions.append({ PathInstructionType::VerticalLine, absolute, parse_coordinate_sequence() }); +} + +void PathDataParser::parse_curveto() +{ + bool absolute = consume() == 'C'; + parse_whitespace(); + + while (true) { + m_instructions.append({ PathInstructionType::Curve, absolute, parse_coordinate_pair_triplet() }); + parse_whitespace(); + if (!match_number()) + break; + } +} + +void PathDataParser::parse_smooth_curveto() +{ + bool absolute = consume() == 'S'; + parse_whitespace(); + + while (true) { + m_instructions.append({ PathInstructionType::SmoothCurve, absolute, parse_coordinate_pair_double() }); + parse_whitespace(); + if (!match_number()) + break; + } +} + +void PathDataParser::parse_quadratic_bezier_curveto() +{ + bool absolute = consume() == 'Q'; + parse_whitespace(); + + while (true) { + m_instructions.append({ PathInstructionType::QuadraticBezierCurve, absolute, parse_coordinate_pair_double() }); + parse_whitespace(); + if (!match_number()) + break; + } +} + +void PathDataParser::parse_smooth_quadratic_bezier_curveto() +{ + bool absolute = consume() == 'T'; + parse_whitespace(); + + while (true) { + m_instructions.append({ PathInstructionType::SmoothQuadraticBezierCurve, absolute, parse_coordinate_pair_double() }); + parse_whitespace(); + if (!match_number()) + break; + } +} + +void PathDataParser::parse_elliptical_arc() +{ + bool absolute = consume() == 'A'; + parse_whitespace(); + + while (true) { + m_instructions.append({ PathInstructionType::EllipticalArc, absolute, parse_elliptical_arg_argument() }); + parse_whitespace(); + if (!match_number()) + break; + } +} + +float PathDataParser::parse_coordinate() +{ + return parse_number(); +} + +Vector<float> PathDataParser::parse_coordinate_pair() +{ + Vector<float> coordinates; + coordinates.append(parse_coordinate()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + coordinates.append(parse_coordinate()); + return coordinates; +} + +Vector<float> PathDataParser::parse_coordinate_sequence() +{ + Vector<float> sequence; + while (true) { + sequence.append(parse_coordinate()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + if (!match_comma_whitespace() && !match_number()) + break; + } + return sequence; +} + +Vector<Vector<float>> PathDataParser::parse_coordinate_pair_sequence() +{ + Vector<Vector<float>> sequence; + while (true) { + sequence.append(parse_coordinate_pair()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + if (!match_comma_whitespace() && !match_number()) + break; + } + return sequence; +} + +Vector<float> PathDataParser::parse_coordinate_pair_double() +{ + Vector<float> coordinates; + coordinates.append(parse_coordinate_pair()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + coordinates.append(parse_coordinate_pair()); + return coordinates; +} + +Vector<float> PathDataParser::parse_coordinate_pair_triplet() +{ + Vector<float> coordinates; + coordinates.append(parse_coordinate_pair()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + coordinates.append(parse_coordinate_pair()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + coordinates.append(parse_coordinate_pair()); + return coordinates; +} + +Vector<float> PathDataParser::parse_elliptical_arg_argument() +{ + Vector<float> numbers; + numbers.append(parse_number()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + numbers.append(parse_number()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + numbers.append(parse_number()); + parse_comma_whitespace(); + numbers.append(parse_flag()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + numbers.append(parse_flag()); + if (match_comma_whitespace()) + parse_comma_whitespace(); + numbers.append(parse_coordinate_pair()); + + return numbers; +} + +void PathDataParser::parse_whitespace(bool must_match_once) +{ + bool matched = false; + while (!done() && match_whitespace()) { + consume(); + matched = true; + } + + ASSERT(!must_match_once || matched); +} + +void PathDataParser::parse_comma_whitespace() +{ + if (match(',')) { + consume(); + parse_whitespace(); + } else { + parse_whitespace(1); + if (match(',')) + consume(); + parse_whitespace(); + } +} + +float PathDataParser::parse_fractional_constant() +{ + StringBuilder builder; + bool floating_point = false; + + while (!done() && isdigit(ch())) + builder.append(consume()); + + if (match('.')) { + floating_point = true; + builder.append('.'); + consume(); + while (!done() && isdigit(ch())) + builder.append(consume()); + } else { + ASSERT(builder.length() > 0); + } + + if (floating_point) + return strtof(builder.to_string().characters(), nullptr); + return builder.to_string().to_int().value(); +} + +float PathDataParser::parse_number() +{ + bool negative = false; + if (match('-')) { + consume(); + negative = true; + } else if (match('+')) { + consume(); + } + auto number = parse_fractional_constant(); + if (match('e') || match('E')) + TODO(); + return negative ? number * -1 : number; +} + +float PathDataParser::parse_flag() +{ + auto number = parse_number(); + ASSERT(number == 0 || number == 1); + return number; +} + +bool PathDataParser::match_whitespace() const +{ + if (done()) + return false; + char c = ch(); + return c == 0x9 || c == 0x20 || c == 0xa || c == 0xc || c == 0xd; +} + +bool PathDataParser::match_comma_whitespace() const +{ + return match_whitespace() || match(','); +} + +bool PathDataParser::match_number() const +{ + return !done() && (isdigit(ch()) || ch() == '-' || ch() == '+'); +} + +HTMLPathElement::HTMLPathElement(Document& document, const FlyString& tag_name) + : HTMLElement(document, tag_name) +{ +} + +#ifdef PATH_DEBUG +static void print_instruction(const PathInstruction& instruction) +{ + auto& data = instruction.data; + + switch (instruction.type) { + case PathInstructionType::Move: + dbg() << "Move (absolute: " << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); i += 2) + dbg() << " x=" << data[i] << ", y=" << data[i + 1]; + break; + case PathInstructionType::ClosePath: + dbg() << "ClosePath (absolute=" << instruction.absolute << ")"; + break; + case PathInstructionType::Line: + dbg() << "Line (absolute=" << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); i += 2) + dbg() << " x=" << data[i] << ", y=" << data[i + 1]; + break; + case PathInstructionType::HorizontalLine: + dbg() << "HorizontalLine (absolute=" << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); ++i) + dbg() << " x=" << data[i]; + break; + case PathInstructionType::VerticalLine: + dbg() << "VerticalLine (absolute=" << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); ++i) + dbg() << " y=" << data[i]; + break; + case PathInstructionType::Curve: + dbg() << "Curve (absolute=" << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); i += 6) + dbg() << " (x1=" << data[i] << ", y1=" << data[i + 1] << "), (x2=" << data[i + 2] << ", y2=" << data[i + 3] << "), (x=" << data[i + 4] << ", y=" << data[i + 5] << ")"; + break; + case PathInstructionType::SmoothCurve: + dbg() << "SmoothCurve (absolute: " << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); i += 4) + dbg() << " (x2=" << data[i] << ", y2=" << data[i + 1] << "), (x=" << data[i + 2] << ", y=" << data[i + 3] << ")"; + break; + case PathInstructionType::QuadraticBezierCurve: + dbg() << "QuadraticBezierCurve (absolute: " << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); i += 4) + dbg() << " (x1=" << data[i] << ", y1=" << data[i + 1] << "), (x=" << data[i + 2] << ", y=" << data[i + 3] << ")"; + break; + case PathInstructionType::SmoothQuadraticBezierCurve: + dbg() << "SmoothQuadraticBezierCurve (absolute: " << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); i += 2) + dbg() << " x=" << data[i] << ", y=" << data[i + 1]; + break; + case PathInstructionType::EllipticalArc: + dbg() << "EllipticalArc (absolute: " << instruction.absolute << ")"; + for (size_t i = 0; i < data.size(); i += 7) + dbg() << " (rx=" << data[i] << ", ry=" << data[i + 1] << ") x-axis-rotation=" << data[i + 2] << ", large-arc-flag=" << data[i + 3] << ", sweep-flag=" << data[i + 4] << ", (x=" << data[i + 5] << ", y=" << data[i + 6] << ")"; + break; + case PathInstructionType::Invalid: + dbg() << "Invalid (absolute: " << instruction.absolute << ")"; + break; + } +} +#endif + +void HTMLPathElement::parse_attribute(const FlyString& name, const String& value) +{ + HTMLElement::parse_attribute(name, value); + if (name == "d") + m_instructions = PathDataParser(value).parse(); +} + +void HTMLPathElement::paint(const SvgPaintingContext& context, Gfx::Painter& painter) +{ + Gfx::Path path; + + for (auto& instruction : m_instructions) { + auto& absolute = instruction.absolute; + auto& data = instruction.data; + +#ifdef PATH_DEBUG + print_instruction(instruction); +#endif + + switch (instruction.type) { + case PathInstructionType::Move: + if (absolute) { + path.move_to({ data[0], data[1] }); + } else { + ASSERT(!path.segments().is_empty()); + path.move_to(Gfx::FloatPoint { data[0], data[1] } + path.segments().last().point); + } + break; + case PathInstructionType::ClosePath: + path.close(); + break; + case PathInstructionType::Line: + if (absolute) { + path.line_to({ data[0], data[1] }); + } else { + ASSERT(!path.segments().is_empty()); + path.line_to(Gfx::FloatPoint { data[0], data[1] } + path.segments().last().point); + } + break; + case PathInstructionType::HorizontalLine: { + ASSERT(!path.segments().is_empty()); + auto last_point = path.segments().last().point; + if (absolute) { + path.line_to(Gfx::FloatPoint { data[0], last_point.y() }); + } else { + path.line_to(Gfx::FloatPoint { data[0] + last_point.x(), last_point.y() }); + } + break; + } + case PathInstructionType::VerticalLine: { + ASSERT(!path.segments().is_empty()); + auto last_point = path.segments().last().point; + if (absolute) { + path.line_to(Gfx::FloatPoint{ last_point.x(), data[0] }); + } else { + path.line_to(Gfx::FloatPoint{ last_point.x(), data[0] + last_point.y() }); + } + break; + } + case PathInstructionType::QuadraticBezierCurve: + if (absolute) { + path.quadratic_bezier_curve_to({ data[0], data[1] }, { data[2], data[3] }); + } else { + ASSERT(!path.segments().is_empty()); + auto last_point = path.segments().last().point; + path.quadratic_bezier_curve_to({ data[0] + last_point.x(), data[1] + last_point.y() }, { data[2] + last_point.x(), data[3] + last_point.y() }); + } + break; + case PathInstructionType::Curve: + case PathInstructionType::SmoothCurve: + case PathInstructionType::SmoothQuadraticBezierCurve: + case PathInstructionType::EllipticalArc: + TODO(); + case PathInstructionType::Invalid: + ASSERT_NOT_REACHED(); + } + } + + painter.fill_path(path, context.fill_color, Gfx::Painter::WindingRule::EvenOdd); + painter.stroke_path(path, context.stroke_color, context.stroke_width); +} + +} diff --git a/Libraries/LibWeb/DOM/HTMLPathElement.h b/Libraries/LibWeb/DOM/HTMLPathElement.h new file mode 100644 index 0000000000..8a29f866e8 --- /dev/null +++ b/Libraries/LibWeb/DOM/HTMLPathElement.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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. + */ + +#pragma once + +#include <LibGfx/Bitmap.h> +#include <LibWeb/DOM/HTMLElement.h> +#include <LibWeb/DOM/SvgContext.h> + +namespace Web { + +enum class PathInstructionType { + Move, + ClosePath, + Line, + HorizontalLine, + VerticalLine, + Curve, + SmoothCurve, + QuadraticBezierCurve, + SmoothQuadraticBezierCurve, + EllipticalArc, + Invalid, +}; + +struct PathInstruction { + PathInstructionType type; + bool absolute; + Vector<float> data; +}; + +class PathDataParser final { +public: + PathDataParser(const String& source); + ~PathDataParser() = default; + + Vector<PathInstruction> parse(); + +private: + void parse_drawto(); + + void parse_moveto(); + void parse_closepath(); + void parse_lineto(); + void parse_horizontal_lineto(); + void parse_vertical_lineto(); + void parse_curveto(); + void parse_smooth_curveto(); + void parse_quadratic_bezier_curveto(); + void parse_smooth_quadratic_bezier_curveto(); + void parse_elliptical_arc(); + + float parse_coordinate(); + Vector<float> parse_coordinate_pair(); + Vector<float> parse_coordinate_sequence(); + Vector<Vector<float>> parse_coordinate_pair_sequence(); + Vector<float> parse_coordinate_pair_double(); + Vector<float> parse_coordinate_pair_triplet(); + Vector<float> parse_elliptical_arg_argument(); + void parse_whitespace(bool must_match_once = false); + void parse_comma_whitespace(); + float parse_fractional_constant(); + float parse_number(); + float parse_flag(); + + bool match_whitespace() const; + bool match_comma_whitespace() const; + bool match_number() const; + bool match(char c) const { return !done() && ch() == c; } + + bool done() const { return m_cursor >= m_source.length(); } + char ch() const { return m_source[m_cursor]; } + char consume() { return m_source[m_cursor++]; } + + String m_source; + size_t m_cursor { 0 }; + Vector<PathInstruction> m_instructions; +}; + +class HTMLPathElement final + : public HTMLElement + , public SvgGraphicElement { +public: + HTMLPathElement(Document&, const FlyString& tag_name); + virtual ~HTMLPathElement() override = default; + + virtual void parse_attribute(const FlyString& name, const String& value) override; + virtual void paint(const SvgPaintingContext&, Gfx::Painter& painter) override; + +private: + Vector<PathInstruction> m_instructions; +}; + +template<> +inline bool is<HTMLPathElement>(const Node& node) +{ + return is<Element>(node) && to<Element>(node).tag_name() == HTML::TagNames::path; +} + +} diff --git a/Libraries/LibWeb/DOM/HTMLSvgElement.cpp b/Libraries/LibWeb/DOM/HTMLSvgElement.cpp new file mode 100644 index 0000000000..53bba76f37 --- /dev/null +++ b/Libraries/LibWeb/DOM/HTMLSvgElement.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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. + */ + +#include <LibGfx/Path.h> +#include <LibWeb/CSS/StyleResolver.h> +#include <LibWeb/DOM/Document.h> +#include <LibWeb/DOM/Event.h> +#include <LibWeb/DOM/HTMLPathElement.h> +#include <LibWeb/DOM/HTMLSvgElement.h> +#include <LibWeb/Layout/LayoutSvg.h> +#include <ctype.h> + +namespace Web { + +static constexpr auto max_svg_area = 16384 * 16384; + +HTMLSvgElement::HTMLSvgElement(Document& document, const FlyString& tag_name) + : HTMLElement(document, tag_name) +{ +} + +void HTMLSvgElement::parse_attribute(const FlyString& name, const String& value) +{ + HTMLElement::parse_attribute(name, value); + if (name == "stroke") { + m_stroke_color = Gfx::Color::from_string(value); + } else if (name == "stroke-width") { + auto result = value.to_int(); + if (result.has_value()) + m_stroke_width = result.value(); + } else if (name == "fill") { + m_fill_color = Gfx::Color::from_string(value); + } +} + +RefPtr<LayoutNode> HTMLSvgElement::create_layout_node(const StyleProperties* parent_style) +{ + auto style = document().style_resolver().resolve_style(*this, parent_style); + if (style->display() == CSS::Display::None) + return nullptr; + return adopt(*new LayoutSvg(document(), *this, move(style))); +} + +static Gfx::IntSize bitmap_size_for_canvas(const HTMLSvgElement& canvas) +{ + auto width = canvas.width(); + auto height = canvas.height(); + + Checked<size_t> area = width; + area *= height; + + if (area.has_overflow()) { + dbg() << "Refusing to create " << width << "x" << height << " svg (overflow)"; + return {}; + } + if (area.value() > max_svg_area) { + dbg() << "Refusing to create " << width << "x" << height << " svg (exceeds maximum size)"; + return {}; + } + return Gfx::IntSize(width, height); +} + +bool HTMLSvgElement::create_bitmap() +{ + auto size = bitmap_size_for_canvas(*this); + if (size.is_empty()) { + m_bitmap = nullptr; + return false; + } + + if (!m_bitmap || m_bitmap->size() != size) + m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, size); + + Gfx::Painter painter(*m_bitmap); + paint(painter); + + return m_bitmap; +} + +SvgPaintingContext HTMLSvgElement::make_context() const +{ + SvgPaintingContext context; + + if (m_stroke_width.has_value()) + context.stroke_width = m_stroke_width.value(); + if (m_stroke_color.has_value()) + context.stroke_color = m_stroke_color.value(); + if (m_fill_color.has_value()) + context.fill_color = m_fill_color.value(); + + return context; +} + +unsigned HTMLSvgElement::width() const +{ + return attribute(HTML::AttributeNames::width).to_uint().value_or(300); +} + +unsigned HTMLSvgElement::height() const +{ + return attribute(HTML::AttributeNames::height).to_uint().value_or(150); +} + +static bool is_svg_graphic_element(const HTMLElement& element) +{ + return is<HTMLPathElement>(element); +} + +static SvgGraphicElement& as_svg_graphic_element(HTMLElement& element) +{ + if (is<HTMLPathElement>(element)) + return to<HTMLPathElement>(element); + ASSERT_NOT_REACHED(); +} + +void HTMLSvgElement::paint(Gfx::Painter& painter) +{ + for_each_child([&](Node& child) { + if (is<HTMLElement>(child)) { + auto& element = to<HTMLElement>(child); + if (is_svg_graphic_element(element)) { + as_svg_graphic_element(element).paint(make_context(), painter); + } + } + }); +} + +} diff --git a/Libraries/LibWeb/DOM/HTMLSvgElement.h b/Libraries/LibWeb/DOM/HTMLSvgElement.h new file mode 100644 index 0000000000..4c39fa1458 --- /dev/null +++ b/Libraries/LibWeb/DOM/HTMLSvgElement.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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. + */ + +#pragma once + +#include <LibGfx/Bitmap.h> +#include <LibWeb/DOM/HTMLElement.h> +#include <LibWeb/DOM/SvgContext.h> + +namespace Web { + +class HTMLSvgElement final : public HTMLElement { +public: + HTMLSvgElement(Document&, const FlyString& tag_name); + virtual ~HTMLSvgElement() override = default; + + virtual void parse_attribute(const FlyString& name, const String& value) override; + virtual RefPtr<LayoutNode> create_layout_node(const StyleProperties* parent_style) override; + + const RefPtr<Gfx::Bitmap> bitmap() const { return m_bitmap; } + bool create_bitmap(); + SvgPaintingContext make_context() const; + + unsigned width() const; + unsigned height() const; + +private: + void paint(Gfx::Painter&); + + RefPtr<Gfx::Bitmap> m_bitmap; + + Optional<float> m_stroke_width; + Optional<Color> m_stroke_color; + Optional<Color> m_fill_color; +}; + +template<> +inline bool is<HTMLSvgElement>(const Node& node) +{ + return is<Element>(node) && to<Element>(node).tag_name() == HTML::TagNames::svg; +} + +} diff --git a/Libraries/LibWeb/DOM/SvgContext.h b/Libraries/LibWeb/DOM/SvgContext.h new file mode 100644 index 0000000000..c17a4d7309 --- /dev/null +++ b/Libraries/LibWeb/DOM/SvgContext.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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. + */ + +#pragma once + +#include <LibGfx/Color.h> +#include <LibGfx/Painter.h> + +namespace Web { + +struct SvgPaintingContext { + Gfx::Color fill_color { Gfx::Color::Black }; + Gfx::Color stroke_color { Gfx::Color::Black }; + float stroke_width { 1 }; +}; + +class SvgGraphicElement { +public: + virtual void paint(const SvgPaintingContext&, Gfx::Painter& painter) = 0; +}; + +} diff --git a/Libraries/LibWeb/DOM/TagNames.h b/Libraries/LibWeb/DOM/TagNames.h index 55fefe2cd1..ae5f632d1d 100644 --- a/Libraries/LibWeb/DOM/TagNames.h +++ b/Libraries/LibWeb/DOM/TagNames.h @@ -110,6 +110,7 @@ void initialize(); __ENUMERATE_HTML_TAG(option) \ __ENUMERATE_HTML_TAG(p) \ __ENUMERATE_HTML_TAG(param) \ + __ENUMERATE_HTML_TAG(path) \ __ENUMERATE_HTML_TAG(plaintext) \ __ENUMERATE_HTML_TAG(pre) \ __ENUMERATE_HTML_TAG(ruby) \ diff --git a/Libraries/LibWeb/Layout/LayoutSvg.cpp b/Libraries/LibWeb/Layout/LayoutSvg.cpp new file mode 100644 index 0000000000..c282e4c98a --- /dev/null +++ b/Libraries/LibWeb/Layout/LayoutSvg.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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. + */ + +#include <LibGUI/Painter.h> +#include <LibGfx/Font.h> +#include <LibGfx/StylePainter.h> +#include <LibWeb/Layout/LayoutSvg.h> + +namespace Web { + +LayoutSvg::LayoutSvg(Document& document, const HTMLSvgElement& element, NonnullRefPtr<StyleProperties> style) + : LayoutReplaced(document, element, move(style)) +{ +} + +void LayoutSvg::layout(LayoutMode layout_mode) +{ + set_has_intrinsic_width(true); + set_has_intrinsic_height(true); + set_intrinsic_width(node().width()); + set_intrinsic_height(node().height()); + LayoutReplaced::layout(layout_mode); +} + +void LayoutSvg::paint(PaintContext& context, PaintPhase phase) +{ + if (!is_visible()) + return; + + LayoutReplaced::paint(context, phase); + + if (phase == PaintPhase::Foreground) { + if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect()))) + return; + + if (!node().bitmap()) + node().create_bitmap(); + + ASSERT(node().bitmap()); + context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect()); + } +} + +} diff --git a/Libraries/LibWeb/Layout/LayoutSvg.h b/Libraries/LibWeb/Layout/LayoutSvg.h new file mode 100644 index 0000000000..dd5035c201 --- /dev/null +++ b/Libraries/LibWeb/Layout/LayoutSvg.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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. + */ + +#pragma once + +#include <LibWeb/DOM/HTMLSvgElement.h> +#include <LibWeb/Layout/LayoutReplaced.h> + +namespace Web { + +class HTMLSvgElement; + +class LayoutSvg : public LayoutReplaced { +public: + LayoutSvg(Document&, const HTMLSvgElement&, NonnullRefPtr<StyleProperties>); + virtual ~LayoutSvg() override = default; + virtual void layout(LayoutMode = LayoutMode::Default) override; + virtual void paint(PaintContext&, PaintPhase) override; + + HTMLSvgElement& node() { return static_cast<HTMLSvgElement&>(LayoutReplaced::node()); } + +private: + virtual const char* class_name() const override { return "LayoutSvg"; } + virtual bool is_canvas() const override { return true; } +}; + +} |