diff --git a/3rdparty/plutovg/plutovg-rle.c b/3rdparty/plutovg/plutovg-rle.c index 0472e460f..be51ca227 100644 --- a/3rdparty/plutovg/plutovg-rle.c +++ b/3rdparty/plutovg/plutovg-rle.c @@ -13,7 +13,7 @@ static SW_FT_Outline* sw_ft_outline_create(int points, int contours) SW_FT_Outline* ft = malloc(sizeof(SW_FT_Outline)); ft->points = malloc((size_t)(points + contours) * sizeof(SW_FT_Vector)); ft->tags = malloc((size_t)(points + contours) * sizeof(char)); - ft->contours = malloc((size_t)contours * sizeof(short)); + ft->contours = malloc((size_t)contours * sizeof(int)); ft->contours_flag = malloc((size_t)contours * sizeof(char)); ft->n_points = ft->n_contours = 0; ft->flags = 0x0; diff --git a/3rdparty/software/sw_ft_raster.h b/3rdparty/software/sw_ft_raster.h index f4499dce9..85a69bc32 100644 --- a/3rdparty/software/sw_ft_raster.h +++ b/3rdparty/software/sw_ft_raster.h @@ -114,12 +114,12 @@ /* */ typedef struct SW_FT_Outline_ { - short n_contours; /* number of contours in glyph */ - short n_points; /* number of points in the glyph */ + int n_contours; /* number of contours in glyph */ + int n_points; /* number of points in the glyph */ SW_FT_Vector* points; /* the outline's points */ char* tags; /* the points flags */ - short* contours; /* the contour end points */ + int* contours; /* the contour end points */ char* contours_flag; /* the contour open flags */ int flags; /* outline masks */ diff --git a/3rdparty/software/sw_ft_stroker.c b/3rdparty/software/sw_ft_stroker.c index 259fe279f..89ec12296 100644 --- a/3rdparty/software/sw_ft_stroker.c +++ b/3rdparty/software/sw_ft_stroker.c @@ -650,8 +650,8 @@ static void ft_stroke_border_export(SW_FT_StrokeBorder border, { SW_FT_UInt count = border->num_points; SW_FT_Byte* tags = border->tags; - SW_FT_Short* write = outline->contours + outline->n_contours; - SW_FT_Short idx = (SW_FT_Short)outline->n_points; + SW_FT_Int* write = outline->contours + outline->n_contours; + SW_FT_Int idx = (SW_FT_Int)outline->n_points; for (; count > 0; count--, tags++, idx++) { if (*tags & SW_FT_STROKE_TAG_END) { @@ -661,7 +661,7 @@ static void ft_stroke_border_export(SW_FT_StrokeBorder border, } } - outline->n_points = (short)(outline->n_points + border->num_points); + outline->n_points = (int)(outline->n_points + border->num_points); assert(SW_FT_Outline_Check(outline) == 0); } diff --git a/CMakeLists.txt b/CMakeLists.txt index b9bf46a88..52d1f6320 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.3) project(lunasvg VERSION 2.3.2 LANGUAGES CXX C) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_C_STANDARD 11) option(BUILD_SHARED_LIBS "Builds as shared library" OFF) diff --git a/include/lunasvg.h b/include/lunasvg.h index c83c27c91..ae44b75d1 100644 --- a/include/lunasvg.h +++ b/include/lunasvg.h @@ -39,6 +39,7 @@ namespace lunasvg { class Rect; +class Matrix; class LUNASVG_API Box { @@ -47,6 +48,9 @@ public: Box(double x, double y, double w, double h); Box(const Rect& rect); + Box& transform(const Matrix& matrix); + Box transformed(const Matrix& matrix) const; + public: double x{0}; double y{0}; @@ -78,7 +82,6 @@ public: Matrix inverted() const; Matrix operator*(const Matrix& matrix) const; - Box map(const Box& box) const; static Matrix rotated(double angle); static Matrix rotated(double angle, double cx, double cy); @@ -159,63 +162,9 @@ public: static std::unique_ptr loadFromData(const char* data); /** - * @brief Pre-Rotates the document matrix clockwise around the current origin - * @param angle - rotation angle, in degrees - * @return this + * @brief Sets the current transformation matrix of the document + * @param matrix - current transformation matrix */ - Document* rotate(double angle); - - /** - * @brief Pre-Rotates the document matrix clockwise around the given point - * @param angle - rotation angle, in degrees - * @param cx - horizontal translation - * @param cy - vertical translation - * @return this - */ - Document* rotate(double angle, double cx, double cy); - - /** - * @brief Pre-Scales the document matrix by sx horizontally and sy vertically - * @param sx - horizontal scale factor - * @param sy - vertical scale factor - * @return this - */ - Document* scale(double sx, double sy); - - /** - * @brief Pre-Shears the document matrix by shx horizontally and shy vertically - * @param shx - horizontal skew factor, in degree - * @param shy - vertical skew factor, in degree - * @return this - */ - Document* shear(double shx, double shy); - - /** - * @brief Pre-Translates the document matrix by tx horizontally and ty vertically - * @param tx - horizontal translation - * @param ty - vertical translation - * @return this - */ - Document* translate(double tx, double ty); - - /** - * @brief Pre-Multiplies the document matrix by Matrix(a, b, c, d, e, f) - * @param a - horizontal scale factor - * @param b - horizontal skew factor - * @param c - vertical skew factor - * @param d - vertical scale factor - * @param e - horizontal translation - * @param f - vertical translation - * @return this - */ - Document* transform(double a, double b, double c, double d, double e, double f); - - /** - * @brief Resets the document matrix to identity - * @return this - */ - Document* identity(); - void setMatrix(const Matrix& matrix); /** diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 1866748d2..c00b891d5 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -20,6 +20,8 @@ PRIVATE "${CMAKE_CURRENT_LIST_DIR}/svgelement.cpp" "${CMAKE_CURRENT_LIST_DIR}/symbolelement.cpp" "${CMAKE_CURRENT_LIST_DIR}/useelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/cssparser.cpp" + "${CMAKE_CURRENT_LIST_DIR}/cssstylesheet.cpp" ) target_include_directories(lunasvg diff --git a/source/canvas.cpp b/source/canvas.cpp index 6311ffc17..d97729199 100644 --- a/source/canvas.cpp +++ b/source/canvas.cpp @@ -9,7 +9,7 @@ static plutovg_fill_rule_t to_plutovg_fill_rule(WindRule winding); static plutovg_operator_t to_plutovg_operator(BlendMode mode); static plutovg_line_cap_t to_plutovg_line_cap(LineCap cap); static plutovg_line_join_t to_plutovg_line_join(LineJoin join); -static plutovg_spread_method_t to_plutovg_spread_methood(SpreadMethod spread); +static plutovg_spread_method_t to_plutovg_spread_method(SpreadMethod spread); static void to_plutovg_stops(plutovg_gradient_t* gradient, const GradientStops& stops); static void to_plutovg_path(plutovg_t* pluto, const Path& path); @@ -67,7 +67,7 @@ void Canvas::setLinearGradient(double x1, double y1, double x2, double y2, const auto gradient = plutovg_gradient_create_linear(x1, y1, x2, y2); auto matrix = to_plutovg_matrix(transform); to_plutovg_stops(gradient, stops); - plutovg_gradient_set_spread(gradient, to_plutovg_spread_methood(spread)); + plutovg_gradient_set_spread(gradient, to_plutovg_spread_method(spread)); plutovg_gradient_set_matrix(gradient, &matrix); plutovg_set_source_gradient(pluto, gradient); plutovg_gradient_destroy(gradient); @@ -78,7 +78,7 @@ void Canvas::setRadialGradient(double cx, double cy, double r, double fx, double auto gradient = plutovg_gradient_create_radial(cx, cy, r, fx, fy, 0); auto matrix = to_plutovg_matrix(transform); to_plutovg_stops(gradient, stops); - plutovg_gradient_set_spread(gradient, to_plutovg_spread_methood(spread)); + plutovg_gradient_set_spread(gradient, to_plutovg_spread_method(spread)); plutovg_gradient_set_matrix(gradient, &matrix); plutovg_set_source_gradient(pluto, gradient); plutovg_gradient_destroy(gradient); @@ -227,7 +227,7 @@ plutovg_line_join_t to_plutovg_line_join(LineJoin join) return join == LineJoin::Miter ? plutovg_line_join_miter : join == LineJoin::Round ? plutovg_line_join_round : plutovg_line_join_bevel; } -static plutovg_spread_method_t to_plutovg_spread_methood(SpreadMethod spread) +static plutovg_spread_method_t to_plutovg_spread_method(SpreadMethod spread) { return spread == SpreadMethod::Pad ? plutovg_spread_method_pad : spread == SpreadMethod::Reflect ? plutovg_spread_method_reflect : plutovg_spread_method_repeat; } diff --git a/source/clippathelement.cpp b/source/clippathelement.cpp index 9472b5ce5..25ea70097 100644 --- a/source/clippathelement.cpp +++ b/source/clippathelement.cpp @@ -5,13 +5,13 @@ namespace lunasvg { ClipPathElement::ClipPathElement() - : GraphicsElement(ElementId::ClipPath) + : GraphicsElement(ElementID::ClipPath) { } Units ClipPathElement::clipPathUnits() const { - auto& value = get(PropertyId::ClipPathUnits); + auto& value = get(PropertyID::ClipPathUnits); return Parser::parseUnits(value, Units::UserSpaceOnUse); } diff --git a/source/cssparser.cpp b/source/cssparser.cpp new file mode 100644 index 000000000..4ad35907d --- /dev/null +++ b/source/cssparser.cpp @@ -0,0 +1,1816 @@ +#include "cssparser.h" + +#include + +namespace lunasvg { + +constexpr bool isNameStart(char cc) { return isalpha(cc) || cc == '_'; } +constexpr bool isNameChar(char cc) { return isNameStart(cc) || isdigit(cc) || cc == '-'; } +constexpr bool isNewLine(char cc) { return (cc == '\n' || cc == '\r' || cc == '\f'); } +constexpr bool isNonPrintable(char cc) { return (cc >= 0 && cc <= 0x8) || cc == 0xb || (cc >= 0xf && cc <= 0x1f) || cc == 0x7f; } + +CSSTokenStream CSSTokenizer::tokenize() +{ + while(true) { + auto token = nextToken(); + if(token.type() == CSSToken::Type::Comment) + continue; + if(token.type() == CSSToken::Type::EndOfFile) + break; + m_tokenList.push_back(token); + } + + auto begin = m_tokenList.data(); + auto end = begin + m_tokenList.size(); + return CSSTokenStream(begin, end); +} + +bool CSSTokenizer::isEscapeSequence(char first, char second) +{ + return first == '\\' && !isNewLine(second); +} + +bool CSSTokenizer::isIdentSequence(char first, char second, char third) +{ + if(isNameStart(first) || isEscapeSequence(first, second)) + return true; + if(first == '-') + return isNameStart(second) || second == '-' || isEscapeSequence(second, third); + return false; +} + +bool CSSTokenizer::isNumberSequence(char first, char second, char third) +{ + if(isdigit(first)) + return true; + if(first == '-' || first == '+') + return isdigit(second) || (second == '.' && isdigit(third)); + if(first == '.') + return isdigit(second); + return false; +} + +bool CSSTokenizer::isEscapeSequence() const +{ + if(m_input.empty()) + return false; + return isEscapeSequence(*m_input, m_input.peek(1)); +} + +bool CSSTokenizer::isIdentSequence() const +{ + if(m_input.empty()) + return false; + auto second = m_input.peek(1); + if(second == 0) + return isIdentSequence(*m_input, 0, 0); + return isIdentSequence(*m_input, second, m_input.peek(2)); +} + +bool CSSTokenizer::isNumberSequence() const +{ + if(m_input.empty()) + return false; + auto second = m_input.peek(1); + if(second == 0) + return isNumberSequence(*m_input, 0, 0); + return isNumberSequence(*m_input, second, m_input.peek(2)); +} + +bool CSSTokenizer::isExponentSequence() const +{ + if(m_input.peek() != 'E' && m_input.peek() != 'e') + return false; + if(m_input.peek(1) == '+' || m_input.peek(1) == '-') + return isdigit(m_input.peek(2)); + return isdigit(m_input.peek(1)); +} + +std::string_view CSSTokenizer::substring(size_t offset, size_t count) +{ + return m_input.string(offset, count); +} + +std::string_view CSSTokenizer::addstring(std::string&& value) +{ + m_stringList.push_back(std::move(value)); + return m_stringList.back(); +} + +std::string_view CSSTokenizer::consumeName() +{ + size_t count = 0; + while(true) { + auto cc = m_input.peek(count); + if(cc == 0 || cc == '\\') + break; + if(!isNameChar(cc)) { + auto offset = m_input.offset(); + m_input.advance(count); + return substring(offset, count); + } + + count += 1; + } + + std::string output; + while(true) { + auto cc = m_input.peek(); + if(isNameChar(cc)) { + output += cc; + m_input.advance(); + } else if(isEscapeSequence()) { + appendCodepoint(output, consumeEscape()); + } else { + break; + } + } + + return addstring(std::move(output)); +} + +uint32_t CSSTokenizer::consumeEscape() +{ + assert(isEscapeSequence()); + auto cc = m_input.advance(); + if(isxdigit(cc)) { + int count = 0; + uint32_t cp = 0; + do { + cp = cp * 16 + xdigit(cc); + cc = m_input.advance(); + count += 1; + } while(count < 6 && isxdigit(cc)); + + if(isspace(cc)) { + if(cc == '\r' && m_input.peek(1) == '\n') + m_input.advance(); + m_input.advance(); + } + + if(cp == 0 || cp >= 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) + return 0xFFFD; + return cp; + } + + if(cc == 0) + return 0xFFFD; + m_input.advance(); + return cc; +} + +CSSToken CSSTokenizer::consumeStringToken() +{ + auto endingCodePoint = m_input.peek(); + assert(endingCodePoint == '\"' || endingCodePoint == '\''); + m_input.advance(); + size_t count = 0; + while(true) { + auto cc = m_input.peek(count); + if(cc == 0 || cc == '\\') + break; + if(cc == endingCodePoint) { + auto offset = m_input.offset(); + m_input.advance(count); + m_input.advance(); + return CSSToken(CSSToken::Type::String, substring(offset, count)); + } + + if(isNewLine(cc)) { + m_input.advance(count); + return CSSToken(CSSToken::Type::BadString); + } + + count += 1; + } + + std::string output; + while(true) { + auto cc = m_input.peek(); + if(cc == 0) + break; + if(cc == endingCodePoint) { + m_input.advance(); + break; + } + + if(isNewLine(cc)) + return CSSToken(CSSToken::Type::BadString); + + if(cc == '\\') { + auto next = m_input.peek(1); + if(next == 0) { + m_input.advance(); + } else if(isNewLine(next)) { + if(next == '\r' && m_input.peek(2) == '\n') + m_input.advance(); + m_input.advance(2); + } else { + appendCodepoint(output, consumeEscape()); + } + } else { + output += cc; + m_input.advance(); + } + } + + if(output.empty()) + return CSSToken(CSSToken::Type::String); + return CSSToken(CSSToken::Type::String, addstring(std::move(output))); +} + +CSSToken CSSTokenizer::consumeNumericToken() +{ + assert(isNumberSequence()); + auto numberType = CSSToken::NumberType::Integer; + auto numberSign = CSSToken::NumberSign::None; + double fraction = 0; + double integer = 0; + int exponent = 0; + int expsign = 1; + + if(m_input.peek() == '-') { + numberSign = CSSToken::NumberSign::Minus; + m_input.advance(); + } else if(m_input.peek() == '+') { + numberSign = CSSToken::NumberSign::Plus; + m_input.advance(); + } + + if(isdigit(m_input.peek())) { + auto cc = m_input.peek(); + do { + integer = 10.0 * integer + (cc - '0'); + cc = m_input.advance(); + } while(isdigit(cc)); + } + + if(m_input.peek() == '.' && isdigit(m_input.peek(1))) { + numberType = CSSToken::NumberType::Number; + auto cc = m_input.advance(); + int count = 0; + do { + fraction = 10.0 * fraction + (cc - '0'); + count += 1; + cc = m_input.advance(); + } while(isdigit(cc)); + fraction *= std::pow(10.0, -count); + } + + if(isExponentSequence()) { + numberType = CSSToken::NumberType::Number; + m_input.advance(); + if(m_input.peek() == '-') { + expsign = -1; + m_input.advance(); + } else if(m_input.peek() == '+') { + m_input.advance(); + } + + auto cc = m_input.peek(); + do { + exponent = 10 * exponent + (cc - '0'); + cc = m_input.advance(); + } while(isdigit(cc)); + } + + double number = (integer + fraction) * std::pow(10.0, exponent * expsign); + if(numberSign == CSSToken::NumberSign::Minus) + number = -number; + + if(m_input.peek() == '%') { + m_input.advance(); + return CSSToken(CSSToken::Type::Percentage, numberType, numberSign, number); + } + + if(isIdentSequence()) + return CSSToken(CSSToken::Type::Dimension, numberType, numberSign, number, consumeName()); + return CSSToken(CSSToken::Type::Number, numberType, numberSign, number); +} + +CSSToken CSSTokenizer::consumeIdentLikeToken() +{ + auto name = consumeName(); + if(equals(name, "url", false) && m_input.peek() == '(') { + auto cc = m_input.advance(); + while(isspace(cc) && isspace(m_input.peek(1))) { + cc = m_input.advance(); + } + + if(isspace(cc)) + cc = m_input.peek(1); + + if(cc == '"' || cc == '\'') + return CSSToken(CSSToken::Type::Function, name); + return consumeUrlToken(); + } + + if(m_input.peek() == '(') { + m_input.advance(); + return CSSToken(CSSToken::Type::Function, name); + } + + return CSSToken(CSSToken::Type::Ident, name); +} + +CSSToken CSSTokenizer::consumeUrlToken() +{ + auto cc = m_input.peek(); + while(isspace(cc)) { + cc = m_input.advance(); + } + + size_t count = 0; + while(true) { + auto cc = m_input.peek(count); + if(cc == 0 || cc == '\\' || isspace(cc)) + break; + if(cc == ')') { + auto offset = m_input.offset(); + m_input.advance(count); + m_input.advance(); + return CSSToken(CSSToken::Type::Url, substring(offset, count)); + } + + if(cc == '"' || cc == '\'' || cc == '(' || isNonPrintable(cc)) { + m_input.advance(count); + return consumeBadUrlRemnants(); + } + + count += 1; + } + + std::string output; + while(true) { + auto cc = m_input.peek(); + if(cc == 0) + break; + if(cc == ')') { + m_input.advance(); + break; + } + + if(cc == '\\') { + if(isEscapeSequence()) { + appendCodepoint(output, consumeEscape()); + continue; + } + + return consumeBadUrlRemnants(); + } + + if(isspace(cc)) { + do { + cc = m_input.advance(); + } while(isspace(cc)); + + if(cc == 0) + break; + if(cc == ')') { + m_input.advance(); + break; + } + + return consumeBadUrlRemnants(); + } + + if(cc == '"' || cc == '\'' || cc == '(' || isNonPrintable(cc)) + return consumeBadUrlRemnants(); + + output += cc; + m_input.advance(); + } + + return CSSToken(CSSToken::Type::Url, addstring(std::move(output))); +} + +CSSToken CSSTokenizer::consumeBadUrlRemnants() +{ + while(true) { + auto cc = m_input.peek(); + if(cc == 0) + break; + if(cc == ')') { + m_input.advance(); + break; + } + + if(isEscapeSequence()) { + consumeEscape(); + } else { + m_input.advance(); + } + } + + return CSSToken(CSSToken::Type::BadUrl); +} + +CSSToken CSSTokenizer::consumeWhitespaceToken() +{ + auto cc = m_input.peek(); + assert(isspace(cc)); + do { + cc = m_input.advance(); + } while(isspace(cc)); + + return CSSToken(CSSToken::Type::Whitespace); +} + +CSSToken CSSTokenizer::consumeCommentToken() +{ + while(true) { + auto cc = m_input.peek(); + if(cc == 0) + break; + if(cc == '*' && m_input.peek(1) == '/') { + m_input.advance(2); + break; + } + + m_input.advance(); + } + + return CSSToken(CSSToken::Type::Comment); +} + +CSSToken CSSTokenizer::consumeSolidusToken() +{ + auto cc = m_input.advance(); + if(cc == '*') { + m_input.advance(); + return consumeCommentToken(); + } + + return CSSToken(CSSToken::Type::Delim, '/'); +} + +CSSToken CSSTokenizer::consumeHashToken() +{ + auto cc = m_input.advance(); + if(isNameChar(cc) || isEscapeSequence()) { + if(isIdentSequence()) + return CSSToken(CSSToken::Type::Hash, CSSToken::HashType::Identifier, consumeName()); + return CSSToken(CSSToken::Type::Hash, CSSToken::HashType::Unrestricted, consumeName()); + } + + return CSSToken(CSSToken::Type::Delim, '#'); +} + +CSSToken CSSTokenizer::consumePlusSignToken() +{ + if(isNumberSequence()) + return consumeNumericToken(); + + m_input.advance(); + return CSSToken(CSSToken::Type::Delim, '+'); +} + +CSSToken CSSTokenizer::consumeHyphenMinusToken() +{ + if(isNumberSequence()) + return consumeNumericToken(); + + if(m_input.peek(1) == '-' && m_input.peek(2) == '>') { + m_input.advance(3); + return CSSToken(CSSToken::Type::CDC); + } + + if(isIdentSequence()) + return consumeIdentLikeToken(); + + m_input.advance(); + return CSSToken(CSSToken::Type::Delim, '-'); +} + +CSSToken CSSTokenizer::consumeFullStopToken() +{ + if(isNumberSequence()) + return consumeNumericToken(); + + m_input.advance(); + return CSSToken(CSSToken::Type::Delim, '.'); +} + +CSSToken CSSTokenizer::consumeLessThanSignToken() +{ + auto cc = m_input.advance(); + if(cc == '!' && m_input.peek(1) == '-' && m_input.peek(2) == '-') { + m_input.advance(3); + return CSSToken(CSSToken::Type::CDO); + } + + return CSSToken(CSSToken::Type::Delim, '<'); +} + +CSSToken CSSTokenizer::consumeCommercialAtToken() +{ + m_input.advance(); + if(isIdentSequence()) + return CSSToken(CSSToken::Type::AtKeyword, consumeName()); + + return CSSToken(CSSToken::Type::Delim, '@'); +} + +CSSToken CSSTokenizer::consumeReverseSolidusToken() +{ + if(isEscapeSequence()) + return consumeIdentLikeToken(); + + m_input.advance(); + return CSSToken(CSSToken::Type::Delim, '\\'); +} + +CSSToken CSSTokenizer::nextToken() +{ + auto cc = m_input.peek(); + if(cc == 0) + return CSSToken(CSSToken::Type::EndOfFile); + + if(isspace(cc)) + return consumeWhitespaceToken(); + + if(isdigit(cc)) + return consumeNumericToken(); + + if(isNameStart(cc)) + return consumeIdentLikeToken(); + + switch(cc) { + case '/': + return consumeSolidusToken(); + case '#': + return consumeHashToken(); + case '+': + return consumePlusSignToken(); + case '-': + return consumeHyphenMinusToken(); + case '.': + return consumeFullStopToken(); + case '<': + return consumeLessThanSignToken(); + case '@': + return consumeCommercialAtToken(); + case '\\': + return consumeReverseSolidusToken(); + case '\"': + return consumeStringToken(); + case '\'': + return consumeStringToken(); + } + + m_input.advance(); + switch(cc) { + case '(': + return CSSToken(CSSToken::Type::LeftParenthesis); + case ')': + return CSSToken(CSSToken::Type::RightParenthesis); + case '[': + return CSSToken(CSSToken::Type::LeftSquareBracket); + case ']': + return CSSToken(CSSToken::Type::RightSquareBracket); + case '{': + return CSSToken(CSSToken::Type::LeftCurlyBracket); + case '}': + return CSSToken(CSSToken::Type::RightCurlyBracket); + case ',': + return CSSToken(CSSToken::Type::Comma); + case ':': + return CSSToken(CSSToken::Type::Colon); + case ';': + return CSSToken(CSSToken::Type::Semicolon); + } + + return CSSToken(CSSToken::Type::Delim, cc); +} + +void CSSParser::parseSheet(CSSRuleList& rules, const std::string_view& content) +{ + CSSTokenizer tokenizer(content); + auto input = tokenizer.tokenize(); + while(!input.empty()) { + input.consumeWhitespace(); + if(input->type() == CSSToken::Type::CDC + || input->type() == CSSToken::Type::CDO) { + input.consume(); + continue; + } + + consumeRule(input, rules); + } +} + +void CSSParser::parseStyle(CSSPropertyList& properties, const std::string_view& content) +{ + CSSTokenizer tokenizer(content); + auto input = tokenizer.tokenize(); + consumeDeclaractionList(input, properties); +} + +bool CSSParser::consumeRule(CSSTokenStream& input, CSSRuleList& rules) +{ + if(input->type() == CSSToken::Type::AtKeyword) + return consumeAtRule(input, rules); + return consumeStyleRule(input, rules); +} + +bool CSSParser::consumeStyleRule(CSSTokenStream& input, CSSRuleList& rules) +{ + auto preludeBegin = input.begin(); + while(!input.empty() && input->type() != CSSToken::Type::LeftCurlyBracket) { + input.consumeComponent(); + } + + CSSTokenStream prelude(preludeBegin, input.begin()); + if(input.empty()) + return false; + + auto block = input.consumeBlock(); + CSSSelectorList selectors; + if(!consumeSelectorList(prelude, selectors)) + return false; + + CSSPropertyList properties; + consumeDeclaractionList(block, properties); + rules.push_back(CSSRule::create(std::move(selectors), std::move(properties))); + return true; +} + +bool CSSParser::consumeAtRule(CSSTokenStream& input, CSSRuleList& rules) +{ + auto name = input->data(); + input.consume(); + auto preludeBegin = input.begin(); + while(input->type() != CSSToken::Type::EndOfFile + && input->type() != CSSToken::Type::LeftCurlyBracket + && input->type() != CSSToken::Type::Semicolon) { + input.consumeComponent(); + } + + CSSTokenStream prelude(preludeBegin, input.begin()); + if(input->type() == CSSToken::Type::EndOfFile + || input->type() == CSSToken::Type::Semicolon) { + if(input->type() == CSSToken::Type::Semicolon) + input.consume(); + if(equals(name, "import", false)) + return consumeImportRule(prelude, rules); + return false; + } + + return false; +} + +bool CSSParser::consumeImportRule(CSSTokenStream& input, CSSRuleList& rules) +{ + return false; +} + +bool CSSParser::consumeSelectorList(CSSTokenStream& input, CSSSelectorList& selectors) +{ + CSSSelector selector; + input.consumeWhitespace(); + if(!consumeSelector(input, selector)) + return false; + + selectors.push_back(std::move(selector)); + while(input->type() == CSSToken::Type::Comma) { + input.consumeIncludingWhitespace(); + if(!consumeSelector(input, selector)) + return false; + selectors.push_back(std::move(selector)); + } + + return input.empty(); +} + +bool CSSParser::consumeSelector(CSSTokenStream& input, CSSSelector& selector) +{ + auto combinator = CSSSimpleSelector::Combinator::None; + do { + CSSSimpleSelector sel; + sel.combinator = combinator; + if(!consumeSimpleSelector(input, sel)) + return combinator == CSSSimpleSelector::Combinator::Descendant; + selector.push_back(std::move(sel)); + } while(consumeCombinator(input, combinator)); + return true; +} + +bool CSSParser::consumeSimpleSelector(CSSTokenStream& input, CSSSimpleSelector& selector) +{ + auto consume = [](auto& input, auto& selector) { + if(input->type() == CSSToken::Type::Hash) + return consumeIdSelector(input, selector); + if(input->type() == CSSToken::Type::Delim && input->delim() == '.') + return consumeClassSelector(input, selector); + if(input->type() == CSSToken::Type::LeftSquareBracket) + return consumeAttributeSelector(input, selector); + if(input->type() == CSSToken::Type::Colon) + return consumePseudoSelector(input, selector); + return false; + }; + + if(!consumeTagSelector(input, selector) + && !consume(input, selector)) { + return false; + } + + while(consume(input, selector)); + return true; +} + +bool CSSParser::consumeTagSelector(CSSTokenStream& input, CSSSimpleSelector& selector) +{ + if(input->type() == CSSToken::Type::Ident) { + selector.id = elementid(input->data()); + input.consume(); + return true; + } + + if(input->type() == CSSToken::Type::Delim && input->delim() == '*') { + selector.id = ElementID::Star; + input.consume(); + return true; + } + + return false; +} + +bool CSSParser::consumeIdSelector(CSSTokenStream& input, CSSSimpleSelector& selector) +{ + if(input->hashType() == CSSToken::HashType::Identifier) { + CSSSimpleSelector::Attribute attribute; + attribute.type = CSSSimpleSelector::Attribute::Type::Equals; + attribute.id = PropertyID::Id; + attribute.value = input->data(); + selector.attributes.push_back(attribute); + input.consume(); + return true; + } + + return false; +} + +bool CSSParser::consumeClassSelector(CSSTokenStream& input, CSSSimpleSelector& selector) +{ + input.consume(); + if(input->type() == CSSToken::Type::Ident) { + CSSSimpleSelector::Attribute attribute; + attribute.type = CSSSimpleSelector::Attribute::Type::Includes; + attribute.id = PropertyID::Class; + attribute.value = input->data(); + selector.attributes.push_back(attribute); + input.consume(); + return true; + } + + return false; +} + +bool CSSParser::consumeAttributeSelector(CSSTokenStream& input, CSSSimpleSelector& selector) +{ + auto block = input.consumeBlock(); + if(block->type() != CSSToken::Type::Ident) + return false; + + CSSSimpleSelector::Attribute attribute; + attribute.id = propertyid(block->data()); + block.consumeIncludingWhitespace(); + if(block.empty()) { + attribute.type = CSSSimpleSelector::Attribute::Type::None; + selector.attributes.push_back(attribute); + return true; + } + + if(block->type() != CSSToken::Type::Delim) + return false; + + switch(block->delim()) { + case '=': + attribute.type = CSSSimpleSelector::Attribute::Type::Equals; + break; + case '*': + attribute.type = CSSSimpleSelector::Attribute::Type::Contains; + break; + case '~': + attribute.type = CSSSimpleSelector::Attribute::Type::Includes; + break; + case '^': + attribute.type = CSSSimpleSelector::Attribute::Type::StartsWith; + break; + case '$': + attribute.type = CSSSimpleSelector::Attribute::Type::EndsWith; + break; + case '|': + attribute.type = CSSSimpleSelector::Attribute::Type::DashEquals; + break; + default: + return false; + } + + if(attribute.type != CSSSimpleSelector::Attribute::Type::Equals) { + block.consume(); + if(block->type() != CSSToken::Type::Delim && block->delim() != '=') + return false; + } + + block.consumeIncludingWhitespace(); + if(block->type() != CSSToken::Type::Ident && block->type() != CSSToken::Type::String) + return false; + + attribute.value = block->data(); + block.consumeIncludingWhitespace(); + if(block.empty()) { + selector.attributes.push_back(attribute); + return true; + } + + return false; +} + +bool CSSParser::consumePseudoSelector(CSSTokenStream& input, CSSSimpleSelector& selector) +{ + input.consume(); + if(input->type() == CSSToken::Type::Ident) { + auto name = input->data(); + input.consume(); + static const struct { + std::string_view name; + CSSSimpleSelector::Pseudo::Type value; + } table[] = { + {"root", CSSSimpleSelector::Pseudo::Type::Root}, + {"empty", CSSSimpleSelector::Pseudo::Type::Empty}, + {"first-child", CSSSimpleSelector::Pseudo::Type::FirstChild}, + {"last-child", CSSSimpleSelector::Pseudo::Type::LastChild}, + {"only-child", CSSSimpleSelector::Pseudo::Type::OnlyChild}, + {"first-of-type", CSSSimpleSelector::Pseudo::Type::FirstOfType}, + {"last-of-type", CSSSimpleSelector::Pseudo::Type::LastOfType}, + {"only-of-type", CSSSimpleSelector::Pseudo::Type::OnlyOfType} + }; + + auto it = std::find_if(table, std::end(table), [name](auto& item) { return equals(name, item.name, false); }); + if(it == std::end(table)) + return false; + CSSSimpleSelector::Pseudo pseudo; + pseudo.type = it->value; + selector.pseudos.push_back(pseudo); + return true; + } + + if(input->type() == CSSToken::Type::Function) { + auto name = input->data(); + auto block = input.consumeBlock(); + block.consumeIncludingWhitespace(); + static const struct { + std::string_view name; + CSSSimpleSelector::Pseudo::Type value; + } table[] = { + {"is", CSSSimpleSelector::Pseudo::Type::Is}, + {"not", CSSSimpleSelector::Pseudo::Type::Not}, + {"nth-child", CSSSimpleSelector::Pseudo::Type::NthChild}, + {"nth-last-child", CSSSimpleSelector::Pseudo::Type::NthLastChild}, + {"nth-of-type", CSSSimpleSelector::Pseudo::Type::NthOfType}, + {"nth-last-of-type", CSSSimpleSelector::Pseudo::Type::NthLastOfType} + }; + + auto it = std::find_if(table, std::end(table), [name](auto& item) { return equals(name, item.name, false); }); + if(it == std::end(table)) + return false; + CSSSimpleSelector::Pseudo pseudo; + pseudo.type = it->value; + switch(pseudo.type) { + case CSSSimpleSelector::Pseudo::Type::Is: + case CSSSimpleSelector::Pseudo::Type::Not: { + if(!consumeSelectorList(block, pseudo.selectors)) + return false; + break; + } + + case CSSSimpleSelector::Pseudo::Type::NthChild: + case CSSSimpleSelector::Pseudo::Type::NthLastChild: + case CSSSimpleSelector::Pseudo::Type::NthOfType: + case CSSSimpleSelector::Pseudo::Type::NthLastOfType: { + if(!consumeMatchPattern(block, pseudo.pattern)) + return false; + break; + } + + default: + assert(false); + } + + selector.pseudos.push_back(pseudo); + block.consumeWhitespace(); + return block.empty(); + } + + return false; +} + +bool CSSParser::consumeCombinator(CSSTokenStream& input, CSSSimpleSelector::Combinator& combinator) +{ + combinator = CSSSimpleSelector::Combinator::None; + while(input->type() == CSSToken::Type::Whitespace) { + combinator = CSSSimpleSelector::Combinator::Descendant; + input.consume(); + } + + if(input->type() == CSSToken::Type::Delim) { + if(input->delim() == '+') { + combinator = CSSSimpleSelector::Combinator::DirectAdjacent; + input.consumeIncludingWhitespace(); + return true; + } + + if(input->delim() == '~') { + combinator = CSSSimpleSelector::Combinator::InDirectAdjacent; + input.consumeIncludingWhitespace(); + return true; + } + + if(input->delim() == '>') { + combinator = CSSSimpleSelector::Combinator::Child; + input.consumeIncludingWhitespace(); + return true; + } + } + + return combinator == CSSSimpleSelector::Combinator::Descendant; +} + +bool CSSParser::consumeMatchPattern(CSSTokenStream& input, CSSSimpleSelector::Pseudo::MatchPattern& pattern) +{ + if(input->type() == CSSToken::Type::Number) { + if(input->numberType() != CSSToken::NumberType::Integer) + return false; + pattern = std::make_pair(0, input->integer()); + input.consume(); + return true; + } + + if(input->type() == CSSToken::Type::Ident) { + if(equals(input->data(), "odd", false)) { + pattern = std::make_pair(2, 1); + input.consume(); + return true; + } + + if(equals(input->data(), "even", false)) { + pattern = std::make_pair(2, 0); + input.consume(); + return true; + } + } + + std::stringstream ss; + if(input->type() == CSSToken::Type::Delim) { + if(input->delim() != '+') + return false; + input.consume(); + if(input->type() != CSSToken::Type::Ident) + return false; + pattern.first = 1; + ss << input->data(); + input.consume(); + } else if(input->type() == CSSToken::Type::Ident) { + auto ident = input->data(); + input.consume(); + if(ident.front() == '-') { + pattern.first = -1; + ss << ident.substr(1); + } else { + pattern.first = 1; + ss << ident; + } + } else if(input->type() == CSSToken::Type::Dimension) { + if(input->numberType() != CSSToken::NumberType::Integer) + return false; + pattern.first = input->integer(); + ss << input->data(); + input.consume(); + } + + constexpr auto eof = std::stringstream::traits_type::eof(); + if(ss.peek() == eof || !equals(ss.get(), 'n', false)) + return false; + + auto sign = CSSToken::NumberSign::None; + if(ss.peek() != eof) { + if(ss.get() != '-') + return false; + sign = CSSToken::NumberSign::Minus; + if(ss.peek() != eof) { + ss >> pattern.second; + if(ss.fail()) + return false; + pattern.second = -pattern.second; + return true; + } + } + + input.consumeWhitespace(); + if(sign == CSSToken::NumberSign::None && input->type() == CSSToken::Type::Delim) { + auto delim = input->delim(); + if(delim == '+') + sign = CSSToken::NumberSign::Plus; + else if(delim == '-') + sign = CSSToken::NumberSign::Minus; + else + return false; + input.consumeIncludingWhitespace(); + } + + if(sign == CSSToken::NumberSign::None && input->type() != CSSToken::Type::Number) { + pattern.second = 0; + return true; + } + + if(input->type() != CSSToken::Type::Number || input->numberType() != CSSToken::NumberType::Integer) + return false; + + if(sign == CSSToken::NumberSign::None && input->numberSign() == CSSToken::NumberSign::None) + return false; + + if(sign != CSSToken::NumberSign::None && input->numberSign() != CSSToken::NumberSign::None) + return false; + + pattern.second = input->integer(); + if(sign == CSSToken::NumberSign::Minus) + pattern.second = -pattern.second; + input.consume(); + return true; +} + +void CSSParser::consumeDeclaractionList(CSSTokenStream& input, CSSPropertyList& properties) +{ + input.consumeWhitespace(); + consumeDeclaraction(input, properties); + while(input->type() == CSSToken::Type::Semicolon) { + input.consumeIncludingWhitespace(); + consumeDeclaraction(input, properties); + } +} + +bool CSSParser::consumeDeclaraction(CSSTokenStream& input, CSSPropertyList& properties) +{ + auto begin = input.begin(); + while(!input.empty() && input->type() != CSSToken::Type::Semicolon) { + input.consumeComponent(); + } + + CSSTokenStream newInput(begin, input.begin()); + if(newInput->type() != CSSToken::Type::Ident) + return false; + + auto id = csspropertyid(newInput->data()); + if(id == CSSPropertyID::Unknown) + return false; + + newInput.consumeIncludingWhitespace(); + if(newInput->type() != CSSToken::Type::Colon) + return false; + + newInput.consumeIncludingWhitespace(); + auto valueBegin = newInput.begin(); + auto valueEnd = newInput.end(); + + auto it = valueEnd - 1; + while(it->type() == CSSToken::Type::Whitespace) { + it -= 1; + } + + bool important = false; + if(it->type() == CSSToken::Type::Ident && equals(it->data(), "important", false)) { + do { + it -= 1; + } while(it->type() == CSSToken::Type::Whitespace); + if(it->type() == CSSToken::Type::Delim && it->delim() == '!') { + important = true; + valueEnd = it; + } + } + + CSSTokenStream value(valueBegin, valueEnd); + return consumeDeclaractionValue(value, properties, id, important); +} + +bool CSSParser::consumeDeclaractionValue(CSSTokenStream& input, CSSPropertyList& properties, CSSPropertyID id, bool important) +{ + if(input->type() == CSSToken::Type::Ident) { + if(equals(input->data(), "inherit", false)) { + input.consumeIncludingWhitespace(); + if(!input.empty()) + return false; + properties.emplace_back(id, important, CSSInheritValue::create()); + return true; + } + + if(equals(input->data(), "initial", false)) { + input.consumeIncludingWhitespace(); + if(!input.empty()) + return false; + properties.emplace_back(id, important, CSSInitialValue::create()); + return true; + } + } + + auto value = consumeValue(input, id); + input.consumeWhitespace(); + if(value == nullptr || !input.empty()) + return false; + properties.emplace_back(id, important, value); + return true; +} + +struct idententry_t { + std::string_view name; + CSSValueID value; +}; + +template +inline CSSValueID matchIdent(const CSSTokenStream& input, const idententry_t(&table)[N]) +{ + if(input->type() != CSSToken::Type::Ident) + return CSSValueID::Unknown; + + auto name = input->data(); + for(auto& entry : table) { + if(equals(name, entry.name, false)) + return entry.value; + } + + return CSSValueID::Unknown; +} + +template +inline RefPtr consumeIdent(CSSTokenStream& input, const idententry_t(&table)[N]) +{ + auto id = matchIdent(input, table); + if(id == CSSValueID::Unknown) + return nullptr; + input.consumeIncludingWhitespace(); + return CSSIdentValue::create(id); +} + +RefPtr CSSParser::consumeNone(CSSTokenStream& input) +{ + if(input->type() == CSSToken::Type::Ident && equals(input->data(), "none", false)) { + input.consumeIncludingWhitespace(); + return CSSIdentValue::create(CSSValueID::None); + } + + return nullptr; +} + +RefPtr CSSParser::consumeNormal(CSSTokenStream& input) +{ + if(input->type() == CSSToken::Type::Ident && equals(input->data(), "normal", false)) { + input.consumeIncludingWhitespace(); + return CSSIdentValue::create(CSSValueID::Normal); + } + + return nullptr; +} + +RefPtr CSSParser::consumePercent(CSSTokenStream& input, bool negative) +{ + if(input->type() != CSSToken::Type::Percentage || (input->number() < 0 && !negative)) + return nullptr; + + auto value = input->number(); + input.consumeIncludingWhitespace(); + return CSSPercentValue::create(value); +} + +RefPtr CSSParser::consumeNumber(CSSTokenStream& input, bool negative) +{ + if(input->type() != CSSToken::Type::Number || (input->number() < 0 && !negative)) + return nullptr; + + auto value = input->number(); + input.consumeIncludingWhitespace(); + return CSSNumberValue::create(value); +} + +RefPtr CSSParser::consumeLength(CSSTokenStream& input, bool negative, bool unitless) +{ + if(input->type() != CSSToken::Type::Dimension && input->type() != CSSToken::Type::Number) + return nullptr; + + auto value = input->number(); + if(value < 0.0 && !negative || (input->type() == CSSToken::Type::Number && !unitless)) + return nullptr; + if(input->type() == CSSToken::Type::Number) { + input.consumeIncludingWhitespace(); + return CSSLengthValue::create(value, CSSLengthValue::Unit::None); + } + + static const struct { + std::string_view name; + CSSLengthValue::Unit value; + } table[] = { + {"em", CSSLengthValue::Unit::Ems}, + {"ex", CSSLengthValue::Unit::Exs}, + {"px", CSSLengthValue::Unit::Pixels}, + {"cm", CSSLengthValue::Unit::Centimeters}, + {"mm", CSSLengthValue::Unit::Millimeters}, + {"in", CSSLengthValue::Unit::Inches}, + {"pt", CSSLengthValue::Unit::Points}, + {"pc", CSSLengthValue::Unit::Picas}, + {"vw", CSSLengthValue::Unit::ViewportWidth}, + {"vh", CSSLengthValue::Unit::ViewportHeight}, + {"vmin", CSSLengthValue::Unit::ViewportMin}, + {"vmax", CSSLengthValue::Unit::ViewportMax}, + {"rem", CSSLengthValue::Unit::Rems}, + {"ch", CSSLengthValue::Unit::Chs} + }; + + auto name = input->data(); + auto it = std::find_if(table, std::end(table), [name](auto& item) { return equals(name, item.name, false); }); + if(it == std::end(table)) + return nullptr; + input.consumeIncludingWhitespace(); + return CSSLengthValue::create(value, it->value); +} + +RefPtr CSSParser::consumeLengthOrNormal(CSSTokenStream& input, bool negative, bool unitless) +{ + if(auto value = consumeNormal(input)) + return value; + return consumeLength(input, negative, unitless); +} + +RefPtr CSSParser::consumeLengthOrPercent(CSSTokenStream& input, bool negative, bool unitless) +{ + auto value = consumeLength(input, negative, unitless); + if(value == nullptr) + return consumePercent(input, negative); + return value; +} + +RefPtr CSSParser::consumeNumberOrPercent(CSSTokenStream& input, bool negative) +{ + auto value = consumeNumber(input, negative); + if(value == nullptr) + return consumePercent(input, negative); + return value; +} + +RefPtr CSSParser::consumeUrl(CSSTokenStream& input) +{ + std::string value; + switch(input->type()) { + case CSSToken::Type::Url: + case CSSToken::Type::String: + value = input->data(); + input.consumeIncludingWhitespace(); + break; + case CSSToken::Type::Function: { + if(!equals(input->data(), "url", false)) + return nullptr; + CSSTokenStreamGuard guard(input); + auto block = input.consumeBlock(); + block.consumeWhitespace(); + value = block->data(); + block.consumeIncludingWhitespace(); + if(!block.empty()) + return nullptr; + input.consumeWhitespace(); + guard.release(); + break; + } + + default: + return nullptr; + } + + return CSSUrlValue::create(std::move(value)); +} + +RefPtr CSSParser::consumeUrlOrNone(CSSTokenStream& input) +{ + if(auto value = consumeNone(input)) + return value; + return consumeUrl(input); +} + +RefPtr CSSParser::consumeColor(CSSTokenStream& input) +{ + if(input->type() == CSSToken::Type::Hash) { + int count = 0; + uint32_t value = 0; + for(auto cc : input->data()) { + if(count >= 6 || !isxdigit(cc)) + return nullptr; + value = value * 16 + xdigit(cc); + count += 1; + } + + if(count != 6 && count != 3) + return nullptr; + if(count == 3) { + value = ((value&0xf00) << 8) | ((value&0x0f0) << 4) | (value&0x00f); + value |= value << 4; + } + + input.consumeIncludingWhitespace(); + return CSSColorValue::create(value | 0xFF000000); + } + + if(input->type() == CSSToken::Type::Function) { + auto name = input->data(); + if(equals(name, "rgb", false) || equals(name, "rgba", false)) + return consumeRgb(input); + return nullptr; + } + + if(input->type() == CSSToken::Type::Ident) { + auto name = input->data(); + if(equals(name, "currentcolor", false)) { + input.consumeIncludingWhitespace(); + return CSSIdentValue::create(CSSValueID::CurrentColor); + } + + if(equals(name, "transparent", false)) { + input.consumeIncludingWhitespace(); + return CSSColorValue::create(0x00000000); + } + + static const struct { + std::string_view name; + uint32_t value; + } table[] = { + {"aliceblue", 0xF0F8FF}, + {"antiquewhite", 0xFAEBD7}, + {"aqua", 0x00FFFF}, + {"aquamarine", 0x7FFFD4}, + {"azure", 0xF0FFFF}, + {"beige", 0xF5F5DC}, + {"bisque", 0xFFE4C4}, + {"black", 0x000000}, + {"blanchedalmond", 0xFFEBCD}, + {"blue", 0x0000FF}, + {"blueviolet", 0x8A2BE2}, + {"brown", 0xA52A2A}, + {"burlywood", 0xDEB887}, + {"cadetblue", 0x5F9EA0}, + {"chartreuse", 0x7FFF00}, + {"chocolate", 0xD2691E}, + {"coral", 0xFF7F50}, + {"cornflowerblue", 0x6495ED}, + {"cornsilk", 0xFFF8DC}, + {"crimson", 0xDC143C}, + {"cyan", 0x00FFFF}, + {"darkblue", 0x00008B}, + {"darkcyan", 0x008B8B}, + {"darkgoldenrod", 0xB8860B}, + {"darkgray", 0xA9A9A9}, + {"darkgreen", 0x006400}, + {"darkgrey", 0xA9A9A9}, + {"darkkhaki", 0xBDB76B}, + {"darkmagenta", 0x8B008B}, + {"darkolivegreen", 0x556B2F}, + {"darkorange", 0xFF8C00}, + {"darkorchid", 0x9932CC}, + {"darkred", 0x8B0000}, + {"darksalmon", 0xE9967A}, + {"darkseagreen", 0x8FBC8F}, + {"darkslateblue", 0x483D8B}, + {"darkslategray", 0x2F4F4F}, + {"darkslategrey", 0x2F4F4F}, + {"darkturquoise", 0x00CED1}, + {"darkviolet", 0x9400D3}, + {"deeppink", 0xFF1493}, + {"deepskyblue", 0x00BFFF}, + {"dimgray", 0x696969}, + {"dimgrey", 0x696969}, + {"dodgerblue", 0x1E90FF}, + {"firebrick", 0xB22222}, + {"floralwhite", 0xFFFAF0}, + {"forestgreen", 0x228B22}, + {"fuchsia", 0xFF00FF}, + {"gainsboro", 0xDCDCDC}, + {"ghostwhite", 0xF8F8FF}, + {"gold", 0xFFD700}, + {"goldenrod", 0xDAA520}, + {"gray", 0x808080}, + {"green", 0x008000}, + {"greenyellow", 0xADFF2F}, + {"grey", 0x808080}, + {"honeydew", 0xF0FFF0}, + {"hotpink", 0xFF69B4}, + {"indianred", 0xCD5C5C}, + {"indigo", 0x4B0082}, + {"ivory", 0xFFFFF0}, + {"khaki", 0xF0E68C}, + {"lavender", 0xE6E6FA}, + {"lavenderblush", 0xFFF0F5}, + {"lawngreen", 0x7CFC00}, + {"lemonchiffon", 0xFFFACD}, + {"lightblue", 0xADD8E6}, + {"lightcoral", 0xF08080}, + {"lightcyan", 0xE0FFFF}, + {"lightgoldenrodyellow", 0xFAFAD2}, + {"lightgray", 0xD3D3D3}, + {"lightgreen", 0x90EE90}, + {"lightgrey", 0xD3D3D3}, + {"lightpink", 0xFFB6C1}, + {"lightsalmon", 0xFFA07A}, + {"lightseagreen", 0x20B2AA}, + {"lightskyblue", 0x87CEFA}, + {"lightslategray", 0x778899}, + {"lightslategrey", 0x778899}, + {"lightsteelblue", 0xB0C4DE}, + {"lightyellow", 0xFFFFE0}, + {"lime", 0x00FF00}, + {"limegreen", 0x32CD32}, + {"linen", 0xFAF0E6}, + {"magenta", 0xFF00FF}, + {"maroon", 0x800000}, + {"mediumaquamarine", 0x66CDAA}, + {"mediumblue", 0x0000CD}, + {"mediumorchid", 0xBA55D3}, + {"mediumpurple", 0x9370DB}, + {"mediumseagreen", 0x3CB371}, + {"mediumslateblue", 0x7B68EE}, + {"mediumspringgreen", 0x00FA9A}, + {"mediumturquoise", 0x48D1CC}, + {"mediumvioletred", 0xC71585}, + {"midnightblue", 0x191970}, + {"mintcream", 0xF5FFFA}, + {"mistyrose", 0xFFE4E1}, + {"moccasin", 0xFFE4B5}, + {"navajowhite", 0xFFDEAD}, + {"navy", 0x000080}, + {"oldlace", 0xFDF5E6}, + {"olive", 0x808000}, + {"olivedrab", 0x6B8E23}, + {"orange", 0xFFA500}, + {"orangered", 0xFF4500}, + {"orchid", 0xDA70D6}, + {"palegoldenrod", 0xEEE8AA}, + {"palegreen", 0x98FB98}, + {"paleturquoise", 0xAFEEEE}, + {"palevioletred", 0xDB7093}, + {"papayawhip", 0xFFEFD5}, + {"peachpuff", 0xFFDAB9}, + {"peru", 0xCD853F}, + {"pink", 0xFFC0CB}, + {"plum", 0xDDA0DD}, + {"powderblue", 0xB0E0E6}, + {"purple", 0x800080}, + {"rebeccapurple", 0x663399}, + {"red", 0xFF0000}, + {"rosybrown", 0xBC8F8F}, + {"royalblue", 0x4169E1}, + {"saddlebrown", 0x8B4513}, + {"salmon", 0xFA8072}, + {"sandybrown", 0xF4A460}, + {"seagreen", 0x2E8B57}, + {"seashell", 0xFFF5EE}, + {"sienna", 0xA0522D}, + {"silver", 0xC0C0C0}, + {"skyblue", 0x87CEEB}, + {"slateblue", 0x6A5ACD}, + {"slategray", 0x708090}, + {"slategrey", 0x708090}, + {"snow", 0xFFFAFA}, + {"springgreen", 0x00FF7F}, + {"steelblue", 0x4682B4}, + {"tan", 0xD2B48C}, + {"teal", 0x008080}, + {"thistle", 0xD8BFD8}, + {"tomato", 0xFF6347}, + {"turquoise", 0x40E0D0}, + {"violet", 0xEE82EE}, + {"wheat", 0xF5DEB3}, + {"white", 0xFFFFFF}, + {"whitesmoke", 0xF5F5F5}, + {"yellow", 0xFFFF00}, + {"yellowgreen", 0x9ACD32} + }; + + auto it = std::lower_bound(table, std::end(table), name, [](auto& item, auto& name) { return item.name < name; }); + if(it == std::end(table) || it->name != name) + return nullptr; + input.consumeIncludingWhitespace(); + return CSSColorValue::create(it->value | 0xFF000000); + } + + return nullptr; +} + +inline bool consumeRgbComponent(CSSTokenStream& input, int& component) +{ + if(input->type() != CSSToken::Type::Number + && input->type() != CSSToken::Type::Percentage) { + return false; + } + + auto value = input->number(); + if(input->type() == CSSToken::Type::Percentage) + value *= 2.55; + value = std::clamp(value, 0.0, 255.0); + component = static_cast(std::round(value)); + input.consumeIncludingWhitespace(); + return true; +} + +RefPtr CSSParser::consumeRgb(CSSTokenStream& input) +{ + assert(input->type() == CSSToken::Type::Function); + CSSTokenStreamGuard guard(input); + auto block = input.consumeBlock(); + block.consumeWhitespace(); + + int red = 0; + if(!consumeRgbComponent(block, red)) + return nullptr; + + if(block->type() != CSSToken::Type::Comma) + return nullptr; + + int blue = 0; + block.consumeIncludingWhitespace(); + if(!consumeRgbComponent(block, blue)) + return nullptr; + + if(block->type() != CSSToken::Type::Comma) + return nullptr; + + int green = 0; + block.consumeIncludingWhitespace(); + if(!consumeRgbComponent(block, green)) + return nullptr; + + int alpha = 255; + if(block->type() == CSSToken::Type::Comma) { + block.consumeIncludingWhitespace(); + if(block->type() != CSSToken::Type::Number + && block->type() != CSSToken::Type::Percentage) { + return nullptr; + } + + auto value = block->number(); + if(block->type() == CSSToken::Type::Percentage) + value /= 100.0; + value = std::clamp(value, 0.0, 1.0); + alpha = static_cast(std::round(value * 255.0)); + block.consumeIncludingWhitespace(); + } + + if(!block.empty()) + return nullptr; + input.consumeWhitespace(); + guard.release(); + return CSSColorValue::create(red, green, blue, alpha); +} + +RefPtr CSSParser::consumeFillOrStroke(CSSTokenStream& input) +{ + if(auto value = consumeNone(input)) + return value; + + auto first = consumeUrl(input); + if(first == nullptr) + return consumeColor(input); + + auto second = consumeNone(input); + if(second == nullptr) + second = consumeColor(input); + if(second == nullptr) + return first; + return CSSPairValue::create(first, second); +} + +RefPtr CSSParser::consumeDashList(CSSTokenStream& input) +{ + if(auto value = consumeNone(input)) + return value; + + CSSValueList values; + auto value = consumeLengthOrPercent(input, false, true); + if(value == nullptr) + return nullptr; + + values.push_back(value); + while(input->type() == CSSToken::Type::Comma) { + input.consumeIncludingWhitespace(); + auto value = consumeLengthOrPercent(input, false, true); + if(value == nullptr) + return nullptr; + values.push_back(value); + } + + if(!input.empty()) + return nullptr; + return CSSListValue::create(std::move(values)); +} + +RefPtr CSSParser::consumeFontWeight(CSSTokenStream& input) +{ + static const idententry_t table[] = { + {"normal", CSSValueID::Normal}, + {"bold", CSSValueID::Bold}, + {"bolder", CSSValueID::Bolder}, + {"lighter", CSSValueID::Lighter} + }; + + if(auto value = consumeIdent(input, table)) + return value; + + if(input->type() != CSSToken::Type::Number || input->numberType() != CSSToken::NumberType::Integer) + return nullptr; + auto value = input->integer(); + if(value < 1 || value > 1000) + return nullptr; + input.consumeIncludingWhitespace(); + return CSSIntegerValue::create(value); +} + +RefPtr CSSParser::consumeFontSize(CSSTokenStream& input, bool unitless) +{ + static const idententry_t table[] = { + {"xx-small", CSSValueID::XxSmall}, + {"x-small", CSSValueID::XSmall}, + {"small", CSSValueID::Small}, + {"medium", CSSValueID::Medium}, + {"large", CSSValueID::Large}, + {"x-large", CSSValueID::XLarge}, + {"xx-large", CSSValueID::XxLarge}, + {"xxx-large", CSSValueID::XxxLarge}, + {"smaller", CSSValueID::Smaller}, + {"larger", CSSValueID::Larger} + }; + + if(auto value = consumeIdent(input, table)) + return value; + return consumeLengthOrPercent(input, false, unitless); +} + +RefPtr CSSParser::consumeFontFamilyValue(CSSTokenStream& input) +{ + if(input->type() == CSSToken::Type::String) { + std::string value(input->data()); + input.consumeIncludingWhitespace(); + return CSSStringValue::create(std::move(value)); + } + + std::string value; + while(input->type() == CSSToken::Type::Ident) { + if(!value.empty()) + value += ' '; + value += input->data(); + input.consumeIncludingWhitespace(); + } + + if(value.empty()) + return nullptr; + return CSSStringValue::create(std::move(value)); +} + +RefPtr CSSParser::consumeFontFamily(CSSTokenStream& input) +{ + CSSValueList values; + while(!input.empty()) { + auto value = consumeFontFamilyValue(input); + if(value == nullptr) + return nullptr; + values.push_back(value); + } + + return CSSListValue::create(std::move(values)); +} + +RefPtr CSSParser::consumeValue(CSSTokenStream& input, CSSPropertyID id) +{ + switch(id) { + case CSSPropertyID::Stroke_Miterlimit: + return consumeNumber(input, false); + case CSSPropertyID::Stroke_Dashoffset: + return consumeLengthOrPercent(input, true, true); + case CSSPropertyID::Stroke_Width: + return consumeLengthOrPercent(input, false, true); + case CSSPropertyID::Letter_Spacing: + case CSSPropertyID::Word_Spacing: + return consumeLengthOrNormal(input, true, true); + case CSSPropertyID::Opacity: + case CSSPropertyID::Fill_Opacity: + case CSSPropertyID::Stroke_Opacity: + case CSSPropertyID::Stop_Opacity: + case CSSPropertyID::Solid_Opacity: + return consumeNumberOrPercent(input, false); + case CSSPropertyID::Stroke_Dasharray: + return consumeDashList(input); + case CSSPropertyID::Clip_Path: + case CSSPropertyID::Marker_End: + case CSSPropertyID::Marker_Mid: + case CSSPropertyID::Marker_Start: + case CSSPropertyID::Mask: + return consumeUrlOrNone(input); + case CSSPropertyID::Color: + case CSSPropertyID::Stop_Color: + case CSSPropertyID::Solid_Color: + return consumeColor(input); + case CSSPropertyID::Fill: + case CSSPropertyID::Stroke: + return consumeFillOrStroke(input); + case CSSPropertyID::Font_Weight: + return consumeFontWeight(input); + case CSSPropertyID::Font_Size: + return consumeFontSize(input, true); + case CSSPropertyID::Font_Family: + return consumeFontFamily(input); + case CSSPropertyID::Font_Style: { + static const idententry_t table[] = { + {"normal", CSSValueID::Normal}, + {"italic", CSSValueID::Italic}, + {"oblique", CSSValueID::Oblique} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Font_Variant: { + static const idententry_t table[] = { + {"normal", CSSValueID::Normal}, + {"small-caps", CSSValueID::SmallCaps} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Fill_Rule: + case CSSPropertyID::Clip_Rule: { + static const idententry_t table[] = { + {"nonzero", CSSValueID::Nonzero}, + {"evenodd", CSSValueID::Evenodd} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Overflow: { + static const idententry_t table[] = { + {"auto", CSSValueID::Auto}, + {"visible", CSSValueID::Visible}, + {"hidden", CSSValueID::Hidden} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Stroke_Linecap: { + static const idententry_t table[] = { + {"butt", CSSValueID::Butt}, + {"round", CSSValueID::Round}, + {"square", CSSValueID::Square} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Stroke_Linejoin: { + static const idententry_t table[] = { + {"miter", CSSValueID::Miter}, + {"round", CSSValueID::Round}, + {"bevel", CSSValueID::Bevel} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Text_Anchor: { + static const idententry_t table[] = { + {"start", CSSValueID::Start}, + {"middle", CSSValueID::Middle}, + {"end", CSSValueID::End} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Visibility: { + static const idententry_t table[] = { + {"visible", CSSValueID::Visible}, + {"hidden", CSSValueID::Hidden}, + {"collapse", CSSValueID::Collapse} + }; + + return consumeIdent(input, table); + } + + case CSSPropertyID::Display: { + static const idententry_t table[] = { + {"none", CSSValueID::None}, + {"inline", CSSValueID::Inline} + }; + + return consumeIdent(input, table); + } + + default: + return nullptr; + } +} + +} // namespace lunasvg diff --git a/source/cssparser.h b/source/cssparser.h new file mode 100644 index 000000000..df3a72290 --- /dev/null +++ b/source/cssparser.h @@ -0,0 +1,305 @@ +#ifndef CSSPARSER_H +#define CSSPARSER_H + +#include "parserutils.h" +#include "cssstylesheet.h" + +#include + +namespace lunasvg { + +class CSSToken { +public: + enum class Type : uint8_t { + Unknown, + Ident, + Function, + AtKeyword, + Hash, + String, + BadString, + Url, + BadUrl, + Delim, + Number, + Percentage, + Dimension, + Whitespace, + Comment, + CDO, + CDC, + Colon, + Semicolon, + Comma, + LeftParenthesis, + RightParenthesis, + LeftSquareBracket, + RightSquareBracket, + LeftCurlyBracket, + RightCurlyBracket, + EndOfFile + }; + + enum class HashType : uint8_t { + Identifier, + Unrestricted + }; + + enum class NumberType : uint8_t { + Integer, + Number + }; + + enum class NumberSign : uint8_t { + None, + Plus, + Minus + }; + + CSSToken() = default; + explicit CSSToken(Type type) : m_type(type) {} + CSSToken(Type type, uint32_t delim) : m_type(type), m_delim(delim) {} + CSSToken(Type type, std::string_view data) : m_type(type), m_data(data) {} + CSSToken(Type type, HashType hashType, std::string_view data) : m_type(type), m_hashType(hashType), m_data(data) {} + + CSSToken(Type type, NumberType numberType, NumberSign numberSign, double number) + : m_type(type), m_numberType(numberType), m_numberSign(numberSign), m_number(number) + {} + + CSSToken(Type type, NumberType numberType, NumberSign numberSign, double number, std::string_view unit) + : m_type(type), m_numberType(numberType), m_numberSign(numberSign), m_number(number), m_data(unit) + {} + + Type type() const { return m_type; } + HashType hashType() const { return m_hashType; } + NumberType numberType() const { return m_numberType; } + NumberSign numberSign() const { return m_numberSign; } + uint32_t delim() const { return m_delim; } + double number() const { return m_number; } + int integer() const { return static_cast(m_number); } + const std::string_view& data() const { return m_data; } + + static Type closeType(Type type) { + switch(type) { + case Type::Function: + case Type::LeftParenthesis: + return Type::RightParenthesis; + case Type::LeftSquareBracket: + return Type::RightSquareBracket; + case Type::LeftCurlyBracket: + return Type::RightCurlyBracket; + default: + assert(false); + } + } + +private: + Type m_type{Type::Unknown}; + HashType m_hashType{HashType::Identifier}; + NumberType m_numberType{NumberType::Integer}; + NumberSign m_numberSign{NumberSign::None}; + uint32_t m_delim{0}; + double m_number{0}; + std::string_view m_data; +}; + +using CSSTokenList = std::vector; + +class CSSTokenStream { +public: + CSSTokenStream(const CSSToken* begin, const CSSToken* end) + : m_begin(begin), m_end(end) + {} + + const CSSToken& peek() const { + static const CSSToken EndOfFileToken(CSSToken::Type::EndOfFile); + if(m_begin == m_end) + return EndOfFileToken; + return *m_begin; + } + + void consume() { + assert(m_begin < m_end); + m_begin += 1; + } + + void consumeWhitespace() { + while(m_begin < m_end && m_begin->type() == CSSToken::Type::Whitespace) { + m_begin += 1; + } + } + + void consumeIncludingWhitespace() { + assert(m_begin < m_end); + do { + m_begin += 1; + } while(m_begin < m_end && m_begin->type() == CSSToken::Type::Whitespace); + } + + void consumeComponent() { + assert(m_begin < m_end); + switch(m_begin->type()) { + case CSSToken::Type::Function: + case CSSToken::Type::LeftParenthesis: + case CSSToken::Type::LeftSquareBracket: + case CSSToken::Type::LeftCurlyBracket: { + auto closeType = CSSToken::closeType(m_begin->type()); + m_begin += 1; + while(m_begin < m_end && m_begin->type() != closeType) + consumeComponent(); + if(m_begin < m_end) + m_begin += 1; + break; + } + + default: + m_begin += 1; + break; + } + } + + CSSTokenStream consumeBlock() { + assert(m_begin < m_end); + auto closeType = CSSToken::closeType(m_begin->type()); + m_begin += 1; + auto blockBegin = m_begin; + while(m_begin < m_end && m_begin->type() != closeType) + consumeComponent(); + auto blockEnd = m_begin; + if(m_begin < m_end) + m_begin += 1; + return CSSTokenStream(blockBegin, blockEnd); + } + + bool empty() const { return m_begin == m_end; } + + const CSSToken& operator*() const { return peek(); } + const CSSToken* operator->() const { return &peek(); } + + const CSSToken* begin() const { return m_begin; } + const CSSToken* end() const { return m_end; } + +private: + const CSSToken* m_begin; + const CSSToken* m_end; +}; + +class CSSTokenStreamGuard { +public: + CSSTokenStreamGuard(CSSTokenStream& input) + : m_input(input), m_state(input) + {} + + void release() { m_state = m_input; } + + ~CSSTokenStreamGuard() { m_input = m_state; } + +private: + CSSTokenStream& m_input; + CSSTokenStream m_state; +}; + +class CSSTokenizer { +public: + CSSTokenizer(const std::string_view& input) + : m_input(input) + {} + + CSSTokenStream tokenize(); + +private: + static bool isEscapeSequence(char first, char second); + static bool isIdentSequence(char first, char second, char third); + static bool isNumberSequence(char first, char second, char third); + + bool isEscapeSequence() const; + bool isIdentSequence() const; + bool isNumberSequence() const; + bool isExponentSequence() const; + + std::string_view substring(size_t offset, size_t count); + std::string_view addstring(std::string&& value); + + std::string_view consumeName(); + uint32_t consumeEscape(); + + CSSToken consumeStringToken(); + CSSToken consumeNumericToken(); + CSSToken consumeIdentLikeToken(); + CSSToken consumeUrlToken(); + CSSToken consumeBadUrlRemnants(); + CSSToken consumeWhitespaceToken(); + CSSToken consumeCommentToken(); + CSSToken consumeSolidusToken(); + CSSToken consumeHashToken(); + CSSToken consumePlusSignToken(); + CSSToken consumeHyphenMinusToken(); + CSSToken consumeFullStopToken(); + CSSToken consumeLessThanSignToken(); + CSSToken consumeCommercialAtToken(); + CSSToken consumeReverseSolidusToken(); + + CSSToken nextToken(); + +private: + using StringList = std::vector; + ParserString m_input; + CSSTokenList m_tokenList; + StringList m_stringList; +}; + +class CSSParser { +public: + static void parseSheet(CSSRuleList& rules, const std::string_view& content); + static void parseStyle(CSSPropertyList& properties, const std::string_view& content); + +private: + static bool consumeRule(CSSTokenStream& input, CSSRuleList& rules); + static bool consumeStyleRule(CSSTokenStream& input, CSSRuleList& rules); + static bool consumeAtRule(CSSTokenStream& input, CSSRuleList& rules); + static bool consumeImportRule(CSSTokenStream& input, CSSRuleList& rules); + + static bool consumeSelectorList(CSSTokenStream& input, CSSSelectorList& selectors); + static bool consumeSelector(CSSTokenStream& input, CSSSelector& selector); + static bool consumeSimpleSelector(CSSTokenStream& input, CSSSimpleSelector& selector); + static bool consumeTagSelector(CSSTokenStream& input, CSSSimpleSelector& selector); + static bool consumeIdSelector(CSSTokenStream& input, CSSSimpleSelector& selector); + static bool consumeClassSelector(CSSTokenStream& input, CSSSimpleSelector& selector); + static bool consumeAttributeSelector(CSSTokenStream& input, CSSSimpleSelector& selector); + static bool consumePseudoSelector(CSSTokenStream& input, CSSSimpleSelector& selector); + + static bool consumeCombinator(CSSTokenStream& input, CSSSimpleSelector::Combinator& combinator); + static bool consumeMatchPattern(CSSTokenStream& input, CSSSimpleSelector::Pseudo::MatchPattern& pattern); + + static void consumeDeclaractionList(CSSTokenStream& input, CSSPropertyList& properties); + static bool consumeDeclaraction(CSSTokenStream& input, CSSPropertyList& properties); + static bool consumeDeclaractionValue(CSSTokenStream& input, CSSPropertyList& properties, CSSPropertyID id, bool important); + + static RefPtr consumeNone(CSSTokenStream& input); + static RefPtr consumeNormal(CSSTokenStream& input); + + static RefPtr consumePercent(CSSTokenStream& input, bool negative); + static RefPtr consumeNumber(CSSTokenStream& input, bool negative); + static RefPtr consumeLength(CSSTokenStream& input, bool negative, bool unitless); + static RefPtr consumeLengthOrNormal(CSSTokenStream& input, bool negative, bool unitless); + static RefPtr consumeLengthOrPercent(CSSTokenStream& input, bool negative, bool unitless); + static RefPtr consumeNumberOrPercent(CSSTokenStream& input, bool negative); + + static RefPtr consumeUrl(CSSTokenStream& input); + static RefPtr consumeUrlOrNone(CSSTokenStream& input); + static RefPtr consumeColor(CSSTokenStream& input); + static RefPtr consumeRgb(CSSTokenStream& input); + static RefPtr consumeFillOrStroke(CSSTokenStream& input); + static RefPtr consumeDashList(CSSTokenStream& input); + + static RefPtr consumeFontWeight(CSSTokenStream& input); + static RefPtr consumeFontSize(CSSTokenStream& input, bool unitless); + static RefPtr consumeFontFamilyValue(CSSTokenStream& input); + static RefPtr consumeFontFamily(CSSTokenStream& input); + + static RefPtr consumeValue(CSSTokenStream& input, CSSPropertyID id); +}; + +} // namespace lunasvg + +#endif // CSSPARSER_H diff --git a/source/cssstylesheet.cpp b/source/cssstylesheet.cpp new file mode 100644 index 000000000..9ae7d4a86 --- /dev/null +++ b/source/cssstylesheet.cpp @@ -0,0 +1,378 @@ +#include "cssstylesheet.h" +#include "cssparser.h" + +#include + +namespace lunasvg { + +RefPtr CSSInitialValue::create() +{ + static auto value = adoptPtr(new CSSInitialValue); + return value; +} + +RefPtr CSSInheritValue::create() +{ + static auto value = adoptPtr(new CSSInheritValue); + return value; +} + +RefPtr CSSIdentValue::create(CSSValueID value) +{ + static std::map> table; + auto it = table.find(value); + if(it == table.end()) { + auto item = adoptPtr(new CSSIdentValue(value)); + table.emplace(value, item); + return item; + } + + return it->second; +} + +RefPtr CSSIntegerValue::create(int value) +{ + return adoptPtr(new CSSIntegerValue(value)); +} + +RefPtr CSSNumberValue::create(double value) +{ + return adoptPtr(new CSSNumberValue(value)); +} + +RefPtr CSSPercentValue::create(double value) +{ + return adoptPtr(new CSSPercentValue(value)); +} + +RefPtr CSSLengthValue::create(double value, Unit unit) +{ + return adoptPtr(new CSSLengthValue(value, unit)); +} + +RefPtr CSSStringValue::create(std::string value) +{ + return adoptPtr(new CSSStringValue(std::move(value))); +} + +RefPtr CSSUrlValue::create(std::string value) +{ + return adoptPtr(new CSSUrlValue(std::move(value))); +} + +RefPtr CSSColorValue::create(uint32_t value) +{ + return adoptPtr(new CSSColorValue(value)); +} + +RefPtr CSSColorValue::create(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return adoptPtr(new CSSColorValue(a << 24 | r << 16 | g << 8 | b)); +} + +RefPtr CSSPairValue::create(RefPtr first, RefPtr second) +{ + return adoptPtr(new CSSPairValue(std::move(first), std::move(second))); +} + +RefPtr CSSListValue::create(CSSValueList values) +{ + return adoptPtr(new CSSListValue(std::move(values))); +} + +CSSPropertyID csspropertyid(const std::string_view& name) +{ + static const struct { + std::string_view name; + CSSPropertyID value; + } table[] = { + {"clip-path", CSSPropertyID::Clip_Path}, + {"clip-rule", CSSPropertyID::Clip_Rule}, + {"color", CSSPropertyID::Color}, + {"display", CSSPropertyID::Display}, + {"fill", CSSPropertyID::Fill}, + {"fill-opacity", CSSPropertyID::Fill_Opacity}, + {"fill-rule", CSSPropertyID::Fill_Rule}, + {"font-family", CSSPropertyID::Font_Family}, + {"font-size", CSSPropertyID::Font_Size}, + {"font-style", CSSPropertyID::Font_Style}, + {"font-variant", CSSPropertyID::Font_Variant}, + {"font-weight", CSSPropertyID::Font_Weight}, + {"letter-spacing", CSSPropertyID::Letter_Spacing}, + {"marker-end", CSSPropertyID::Marker_End}, + {"marker-mid", CSSPropertyID::Marker_Mid}, + {"marker-start", CSSPropertyID::Marker_Start}, + {"mask", CSSPropertyID::Mask}, + {"opacity", CSSPropertyID::Opacity}, + {"overflow", CSSPropertyID::Overflow}, + {"solid-color", CSSPropertyID::Solid_Color}, + {"solid-opacity", CSSPropertyID::Solid_Opacity}, + {"stop-color", CSSPropertyID::Stop_Color}, + {"stop-opacity", CSSPropertyID::Stop_Opacity}, + {"stroke", CSSPropertyID::Stroke}, + {"stroke-dasharray", CSSPropertyID::Stroke_Dasharray}, + {"stroke-dashoffset", CSSPropertyID::Stroke_Dashoffset}, + {"stroke-linecap", CSSPropertyID::Stroke_Linecap}, + {"stroke-linejoin", CSSPropertyID::Stroke_Linejoin}, + {"stroke-miterlimit", CSSPropertyID::Stroke_Miterlimit}, + {"stroke-opacity", CSSPropertyID::Stroke_Opacity}, + {"stroke-width", CSSPropertyID::Stroke_Width}, + {"text-anchor", CSSPropertyID::Text_Anchor}, + {"text-decoration", CSSPropertyID::Text_Decoration}, + {"visibility", CSSPropertyID::Visibility}, + {"word-spacing", CSSPropertyID::Word_Spacing}, + }; + + auto it = std::lower_bound(table, std::end(table), name, [](auto& item, auto& name) { return item.name < name; }); + if(it != std::end(table) && it->name == name) + return it->value; + return CSSPropertyID::Unknown; +} + +bool CSSSimpleSelector::Pseudo::matchnth(size_t count) const +{ + auto [a, b] = pattern; + if(a == 0) + return count == b; + if(a > 0) { + if(count < b) + return false; + return (count - b) % a == 0; + } + + if(count > b) + return false; + return (b - count) % -a == 0; +} + +std::unique_ptr CSSRule::create(CSSSelectorList selectors, CSSPropertyList properties) +{ + return std::unique_ptr(new CSSRule(std::move(selectors), std::move(properties))); +} + +bool CSSRuleData::match(const Element* element) const +{ + if(m_selector.empty()) + return false; + + if(m_selector.size() == 1) + return matchSimpleSelector(m_selector.front(), element); + + auto it = m_selector.rbegin(); + auto end = m_selector.rend(); + if(!matchSimpleSelector(*it, element)) + return false; + ++it; + + while(it != end) { + switch(it->combinator) { + case CSSSimpleSelector::Combinator::Child: + case CSSSimpleSelector::Combinator::Descendant: + element = element->parent; + break; + case CSSSimpleSelector::Combinator::DirectAdjacent: + case CSSSimpleSelector::Combinator::InDirectAdjacent: + element = element->previousElement(); + break; + default: + assert(false); + } + + if(element == nullptr) + return false; + + auto match = matchSimpleSelector(*it, element); + if(!match && (it->combinator != CSSSimpleSelector::Combinator::Descendant && it->combinator != CSSSimpleSelector::Combinator::InDirectAdjacent)) + return false; + + if(match || (it->combinator != CSSSimpleSelector::Combinator::Descendant && it->combinator != CSSSimpleSelector::Combinator::InDirectAdjacent)) + ++it; + } + + return true; +} + +bool CSSRuleData::matchSimpleSelector(const CSSSimpleSelector& selector, const Element* element) +{ + if(selector.id != ElementID::Star && selector.id != element->id) + return false; + + for(auto& attribute : selector.attributes) + if(!matchAttributeSelector(attribute, element)) + return false; + + for(auto& pseudo : selector.pseudos) + if(!matchPseudoClassSelector(pseudo, element)) + return false; + + return true; +} + +bool CSSRuleData::matchAttributeSelector(const CSSSimpleSelector::Attribute& attribute, const Element* element) +{ + auto& value = element->get(attribute.id); + switch(attribute.type) { + case CSSSimpleSelector::Attribute::Type::None: + return !value.empty(); + case CSSSimpleSelector::Attribute::Type::Equals: + return equals(value, attribute.value, false); + case CSSSimpleSelector::Attribute::Type::Contains: + return contains(value, attribute.value, false); + case CSSSimpleSelector::Attribute::Type::Includes: + return includes(value, attribute.value, false); + case CSSSimpleSelector::Attribute::Type::StartsWith: + return startswith(value, attribute.value, false); + case CSSSimpleSelector::Attribute::Type::EndsWith: + return endswith(value, attribute.value, false); + case CSSSimpleSelector::Attribute::Type::DashEquals: + return dashequals(value, attribute.value, false); + default: + return false; + } +} + +bool CSSRuleData::matchPseudoClassSelector(const CSSSimpleSelector::Pseudo& pseudo, const Element* element) +{ + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::Empty) + return element->children.empty(); + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::Root) + return element->parent == nullptr; + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::Is) { + for(auto& selector : pseudo.selectors) { + for(auto& sel : selector) { + if(!matchSimpleSelector(sel, element)) { + return false; + } + } + } + + return true; + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::Not) { + for(auto& selector : pseudo.selectors) { + for(auto& sel : selector) { + if(matchSimpleSelector(sel, element)) { + return false; + } + } + } + + return true; + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::FirstChild) + return !element->previousElement(); + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::LastChild) + return !element->nextElement(); + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::OnlyChild) + return !(element->previousElement() || element->nextElement()); + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::FirstOfType) { + auto child = element->nextElement(); + while(child) { + if(child->id == element->id) + return false; + child = element->nextElement(); + } + + return true; + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::LastOfType) { + auto child = element->nextElement(); + while(child) { + if(child->id == element->id) + return false; + child = element->nextElement(); + } + + return true; + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::OnlyOfType) { + auto child = element->nextElement(); + while(child) { + if(child->id == element->id) + return false; + child = element->nextElement(); + } + + child = element->nextElement(); + while(child) { + if(child->id == element->id) + return false; + child = element->nextElement(); + } + + return true; + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::NthChild) { + int count = 1; + auto child = element->previousElement(); + while(child) { + count += 1; + child = element->previousElement(); + } + + return pseudo.matchnth(count); + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::NthLastChild) { + int count = 1; + auto child = element->nextElement(); + while(child) { + count += 1; + child = element->nextElement(); + } + + return pseudo.matchnth(count); + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::NthOfType) { + int count = 1; + auto child = element->previousElement(); + while(child && child->id == element->id) { + count += 1; + child = element->previousElement(); + } + + return pseudo.matchnth(count); + } + + if(pseudo.type == CSSSimpleSelector::Pseudo::Type::NthLastOfType) { + int count = 1; + auto child = element->nextElement(); + while(child && child->id == element->id) { + count += 1; + child = element->nextElement(); + } + + return pseudo.matchnth(count); + } + + return false; +} + +void CSSStyleSheet::parse(const std::string_view& content) +{ + auto position = m_ruleList.size(); + CSSParser::parseSheet(m_ruleList, content); + for(; position < m_ruleList.size(); ++position) { + auto& rule = m_ruleList[position]; + auto& properties = rule->properties(); + for(auto& selector : rule->selectors()) { + uint32_t specificity = 0; + for(auto& sel : selector) { + specificity += (sel.id == ElementID::Star) ? 0x0 : 0x1; + for(auto& attribute : sel.attributes) { + specificity += (attribute.id == PropertyID::Id) ? 0x10000 : 0x100; + } + } + + m_ruleSet.emplace(selector, properties, specificity, position); + } + } +} + +} // namespace lunasvg diff --git a/source/cssstylesheet.h b/source/cssstylesheet.h new file mode 100644 index 000000000..06f5ef258 --- /dev/null +++ b/source/cssstylesheet.h @@ -0,0 +1,495 @@ +#ifndef CSSSTYLESHEET_H +#define CSSSTYLESHEET_H + +#include "element.h" +#include "pointer.h" + +#include +#include + +namespace lunasvg { + +class CSSValue : public RefCounted { +public: + virtual ~CSSValue() = default; + virtual bool isInitialValue() const { return false; } + virtual bool isInheritValue() const { return false; } + virtual bool isIdentValue() const { return false; } + virtual bool isIntegerValue() const { return false; } + virtual bool isNumberValue() const { return false; } + virtual bool isPercentValue() const { return false; } + virtual bool isLengthValue() const { return false; } + virtual bool isStringValue() const { return false; } + virtual bool isUrlValue() const { return false; } + virtual bool isColorValue() const { return false; } + virtual bool isPairValue() const { return false; } + virtual bool isListValue() const { return false; } + +protected: + CSSValue() = default; +}; + +using CSSValueList = std::vector>; + +class CSSInitialValue final : public CSSValue { +public: + static RefPtr create(); + + bool isInitialValue() const final { return true; } + +private: + CSSInitialValue() = default; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isInitialValue(); } +}; + +class CSSInheritValue final : public CSSValue { +public: + static RefPtr create(); + + bool isInheritValue() const final { return true; } + +private: + CSSInheritValue() = default; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isInheritValue(); } +}; + +enum class CSSValueID { + Unknown, + Auto, + Bevel, + Bold, + Bolder, + Butt, + Clip, + Collapse, + Color, + CurrentColor, + End, + Evenodd, + Hidden, + Inherit, + Initial, + Inline, + Italic, + Large, + Larger, + Lighter, + Medium, + Middle, + Miter, + None, + Nonzero, + Normal, + Oblique, + Round, + Small, + SmallCaps, + Smaller, + Square, + Start, + Stroke, + Visible, + XLarge, + XSmall, + XxLarge, + XxSmall, + XxxLarge +}; + +class CSSIdentValue final : public CSSValue { +public: + static RefPtr create(CSSValueID value); + + CSSValueID value() const { return m_value; } + bool isIdentValue() const final { return true; } + +private: + CSSIdentValue(CSSValueID value) : m_value(value) {} + CSSValueID m_value; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isIdentValue(); } +}; + +class CSSIntegerValue final : public CSSValue { +public: + static RefPtr create(int value); + + int value() const { return m_value; } + bool isIntegerValue() const final { return true; } + +private: + CSSIntegerValue(int value) : m_value(value) {} + int m_value; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isIntegerValue(); } +}; + +class CSSNumberValue final : public CSSValue { +public: + static RefPtr create(double value); + + double value() const { return m_value; } + bool isNumberValue() const final { return true; } + +private: + CSSNumberValue(double value) : m_value(value) {} + double m_value; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isNumberValue(); } +}; + +class CSSPercentValue final : public CSSValue { +public: + static RefPtr create(double value); + + double value() const { return m_value; } + bool isPercentValue() const final { return true; } + +private: + CSSPercentValue(double value) : m_value(value) {} + double m_value; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isPercentValue(); } +}; + +class CSSLengthValue final : public CSSValue { +public: + enum class Unit { + None, + Ems, + Exs, + Pixels, + Centimeters, + Millimeters, + Inches, + Points, + Picas, + ViewportWidth, + ViewportHeight, + ViewportMin, + ViewportMax, + Rems, + Chs + }; + + static RefPtr create(double value, Unit unit); + + double value() const { return m_value; } + Unit unit() const { return m_unit; } + bool isLengthValue() const final { return true; } + +private: + CSSLengthValue(double value, Unit unit) + : m_value(value), m_unit(unit) + {} + + double m_value; + Unit m_unit; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isLengthValue(); } +}; + +class CSSStringValue final : public CSSValue { +public: + static RefPtr create(std::string value); + + const std::string& value() const { return m_value; } + bool isStringValue() const final { return true; } + +private: + CSSStringValue(std::string value) : m_value(std::move(value)) {} + std::string m_value; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isStringValue(); } +}; + +class CSSUrlValue final : public CSSValue { +public: + static RefPtr create(std::string value); + + const std::string& value() const { return m_value; } + bool isUrlValue() const final { return true; } + +private: + CSSUrlValue(std::string value) : m_value(std::move(value)) {} + std::string m_value; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isUrlValue(); } +}; + +class CSSColorValue final : public CSSValue { +public: + static RefPtr create(uint32_t value); + static RefPtr create(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + uint32_t value() const { return m_value; } + bool isColorValue() const final { return true; } + +private: + CSSColorValue(uint32_t value) : m_value(value) {} + uint32_t m_value; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isColorValue(); } +}; + +class CSSPairValue final : public CSSValue { +public: + static RefPtr create(RefPtr first, RefPtr second); + + const RefPtr& first() const { return m_first; } + const RefPtr& second() const { return m_second; } + bool isPairValue() const final { return true; } + +private: + CSSPairValue(RefPtr first, RefPtr second) + : m_first(first), m_second(second) + {} + + RefPtr m_first; + RefPtr m_second; +}; + +template<> +struct is { + static bool check(const CSSValue& value) { return value.isPairValue(); } +}; + +class CSSListValue : public CSSValue { +public: + static RefPtr create(CSSValueList values); + + size_t length() const { return m_values.size(); } + const RefPtr& front() const { return m_values.front(); } + const RefPtr& back() const { return m_values.back(); } + const RefPtr& at(size_t index) const { return m_values.at(index); } + const CSSValueList& values() const { return m_values; } + bool isListValue() const final { return true; } + +protected: + CSSListValue(CSSValueList values) : m_values(std::move(values)) {} + CSSValueList m_values; +}; + +enum class CSSPropertyID { + Unknown = 0, + Clip_Path, + Clip_Rule, + Color, + Display, + Fill, + Fill_Opacity, + Fill_Rule, + Font_Family, + Font_Size, + Font_Style, + Font_Variant, + Font_Weight, + Letter_Spacing, + Marker_End, + Marker_Mid, + Marker_Start, + Mask, + Opacity, + Overflow, + Solid_Color, + Solid_Opacity, + Stop_Color, + Stop_Opacity, + Stroke, + Stroke_Dasharray, + Stroke_Dashoffset, + Stroke_Linecap, + Stroke_Linejoin, + Stroke_Miterlimit, + Stroke_Opacity, + Stroke_Width, + Text_Anchor, + Text_Decoration, + Visibility, + Word_Spacing +}; + +CSSPropertyID csspropertyid(const std::string_view& name); + +class CSSProperty { +public: + CSSProperty(CSSPropertyID id, bool important, RefPtr value) + : m_id(id), m_important(important), m_value(value) + {} + + CSSPropertyID id() const { return m_id; } + bool important() const { return m_important; } + const RefPtr& value() const { return m_value; } + +private: + CSSPropertyID m_id; + bool m_important; + RefPtr m_value; +}; + +using CSSPropertyList = std::vector; +using CSSPropertyMap = std::map; + +struct CSSSimpleSelector; + +using CSSSelector = std::vector; +using CSSSelectorList = std::vector; + +struct CSSSimpleSelector { + struct Attribute { + enum class Type { + None, + Equals, + Contains, + Includes, + StartsWith, + EndsWith, + DashEquals + }; + + Type type{Type::None}; + PropertyID id{PropertyID::Unknown}; + std::string value; + }; + + struct Pseudo { + enum class Type { + Unknown, + Empty, + Root, + Is, + Not, + FirstChild, + LastChild, + OnlyChild, + FirstOfType, + LastOfType, + OnlyOfType, + NthChild, + NthLastChild, + NthOfType, + NthLastOfType + }; + + using MatchPattern = std::pair; + + bool matchnth(size_t count) const; + + Type type{Type::Unknown}; + MatchPattern pattern; + CSSSelectorList selectors; + }; + + enum class Combinator { + None, + Descendant, + Child, + DirectAdjacent, + InDirectAdjacent + }; + + Combinator combinator{Combinator::Descendant}; + ElementID id{ElementID::Star}; + std::vector attributes; + std::vector pseudos; +}; + +class CSSRule { +public: + static std::unique_ptr create(CSSSelectorList selectors, CSSPropertyList properties); + + const CSSSelectorList& selectors() const { return m_selectors; } + const CSSPropertyList& properties() const { return m_properties; } + +private: + CSSRule(CSSSelectorList selectors, CSSPropertyList properties) + : m_selectors(std::move(selectors)), m_properties(std::move(properties)) + {} + + CSSSelectorList m_selectors; + CSSPropertyList m_properties; +}; + +using CSSRuleList = std::vector>; + +class CSSRuleData { +public: + CSSRuleData(const CSSSelector& selector, const CSSPropertyList& properties, uint32_t specificity, uint32_t position) + : m_selector(selector), m_properties(properties), m_specificity(specificity), m_position(position) + {} + + const CSSSelector& selector() const { return m_selector; } + const CSSPropertyList& properties() const { return m_properties; } + const uint32_t& specificity() const { return m_specificity; } + const uint32_t& position() const { return m_position; } + + bool match(const Element* element) const; + +private: + static bool matchSimpleSelector(const CSSSimpleSelector& selector, const Element* element); + static bool matchAttributeSelector(const CSSSimpleSelector::Attribute& attribute, const Element* element); + static bool matchPseudoClassSelector(const CSSSimpleSelector::Pseudo& pseudo, const Element* element); + +private: + const CSSSelector& m_selector; + const CSSPropertyList& m_properties; + uint32_t m_specificity; + uint32_t m_position; +}; + +inline bool operator<(const CSSRuleData& a, const CSSRuleData& b) { return std::tie(a.specificity(), a.position()) < std::tie(b.specificity(), b.position()); } +inline bool operator>(const CSSRuleData& a, const CSSRuleData& b) { return std::tie(a.specificity(), a.position()) > std::tie(b.specificity(), b.position()); } + +using CSSRuleSet = std::multiset; + +class ComputedStyle; + +class CSSStyleSheet { +public: + CSSStyleSheet() = default; + + void parse(const std::string_view& content); + bool empty() const { return m_ruleList.empty(); } + + RefPtr styleForElement(const Element* element, const RefPtr& parentStyle) const; + +private: + CSSRuleList m_ruleList; + CSSRuleSet m_ruleSet; +}; + +} // namespace lunasvg + +#endif // CSSSTYLESHEET_H diff --git a/source/defselement.cpp b/source/defselement.cpp index b39ddeac9..9402561d0 100644 --- a/source/defselement.cpp +++ b/source/defselement.cpp @@ -3,7 +3,7 @@ namespace lunasvg { DefsElement::DefsElement() - : GraphicsElement(ElementId::Defs) + : GraphicsElement(ElementID::Defs) { } diff --git a/source/element.cpp b/source/element.cpp index a3d3a319a..c4cc2999d 100644 --- a/source/element.cpp +++ b/source/element.cpp @@ -2,9 +2,146 @@ #include "parser.h" #include "svgelement.h" +#include + namespace lunasvg { -void PropertyList::set(PropertyId id, const std::string& value, int specificity) +ElementID elementid(const std::string_view& name) +{ + static const struct { + std::string_view name; + ElementID value; + } table[] = { + {"a", ElementID::A}, + {"circle", ElementID::Circle}, + {"clipPath", ElementID::ClipPath}, + {"defs", ElementID::Defs}, + {"ellipse", ElementID::Ellipse}, + {"g", ElementID::G}, + {"image", ElementID::Image}, + {"line", ElementID::Line}, + {"linearGradient", ElementID::LinearGradient}, + {"marker", ElementID::Marker}, + {"mask", ElementID::Mask}, + {"path", ElementID::Path}, + {"pattern", ElementID::Pattern}, + {"polygon", ElementID::Polygon}, + {"polyline", ElementID::Polyline}, + {"radialGradient", ElementID::RadialGradient}, + {"rect", ElementID::Rect}, + {"solidColor", ElementID::SolidColor}, + {"stop", ElementID::Stop}, + {"style", ElementID::Style}, + {"svg", ElementID::Svg}, + {"switch", ElementID::Switch}, + {"symbol", ElementID::Symbol}, + {"text", ElementID::Text}, + {"textPath", ElementID::TextPath}, + {"tref", ElementID::Tref}, + {"tspan", ElementID::Tspan}, + {"use", ElementID::Use} + }; + + auto it = std::lower_bound(table, std::end(table), name, [](auto& item, auto& name) { return item.name < name; }); + if(it != std::end(table) && it->name == name) + return it->value; + return ElementID::Unknown; +} + +PropertyID propertyid(const std::string_view& name) +{ + static const struct { + std::string_view name; + PropertyID value; + } table[] = { + {"class", PropertyID::Class}, + {"clip-path", PropertyID::Clip_Path}, + {"clip-rule", PropertyID::Clip_Rule}, + {"clipPathUnits", PropertyID::ClipPathUnits}, + {"color", PropertyID::Color}, + {"cx", PropertyID::Cx}, + {"cy", PropertyID::Cy}, + {"d", PropertyID::D}, + {"dx", PropertyID::Dx}, + {"dy", PropertyID::Dy}, + {"display", PropertyID::Display}, + {"fill", PropertyID::Fill}, + {"fill-opacity", PropertyID::Fill_Opacity}, + {"fill-rule", PropertyID::Fill_Rule}, + {"font-family", PropertyID::Font_Family}, + {"font-size", PropertyID::Font_Size}, + {"font-style", PropertyID::Font_Style}, + {"font-variant", PropertyID::Font_Variant}, + {"font-weight", PropertyID::Font_Weight}, + {"fx", PropertyID::Fx}, + {"fy", PropertyID::Fy}, + {"gradientTransform", PropertyID::GradientTransform}, + {"gradientUnits", PropertyID::GradientUnits}, + {"height", PropertyID::Height}, + {"href", PropertyID::Href}, + {"id", PropertyID::Id}, + {"letter-spacing", PropertyID::Letter_Spacing}, + {"marker-end", PropertyID::Marker_End}, + {"marker-mid", PropertyID::Marker_Mid}, + {"marker-start", PropertyID::Marker_Start}, + {"markerHeight", PropertyID::MarkerHeight}, + {"markerUnits", PropertyID::MarkerUnits}, + {"markerWidth", PropertyID::MarkerWidth}, + {"mask", PropertyID::Mask}, + {"maskContentUnits", PropertyID::MaskContentUnits}, + {"maskUnits", PropertyID::MaskUnits}, + {"offset", PropertyID::Offset}, + {"opacity", PropertyID::Opacity}, + {"orient", PropertyID::Orient}, + {"overflow", PropertyID::Overflow}, + {"patternContentUnits", PropertyID::PatternContentUnits}, + {"patternTransform", PropertyID::PatternTransform}, + {"patternUnits", PropertyID::PatternUnits}, + {"points", PropertyID::Points}, + {"preserveAspectRatio", PropertyID::PreserveAspectRatio}, + {"r", PropertyID::R}, + {"refX", PropertyID::RefX}, + {"refY", PropertyID::RefY}, + {"rotate", PropertyID::Rotate}, + {"rx", PropertyID::Rx}, + {"ry", PropertyID::Ry}, + {"solid-color", PropertyID::Solid_Color}, + {"solid-opacity", PropertyID::Solid_Opacity}, + {"spreadMethod", PropertyID::SpreadMethod}, + {"startOffset", PropertyID::StartOffset}, + {"stop-color", PropertyID::Stop_Color}, + {"stop-opacity", PropertyID::Stop_Opacity}, + {"stroke", PropertyID::Stroke}, + {"stroke-dasharray", PropertyID::Stroke_Dasharray}, + {"stroke-dashoffset", PropertyID::Stroke_Dashoffset}, + {"stroke-linecap", PropertyID::Stroke_Linecap}, + {"stroke-linejoin", PropertyID::Stroke_Linejoin}, + {"stroke-miterlimit", PropertyID::Stroke_Miterlimit}, + {"stroke-opacity", PropertyID::Stroke_Opacity}, + {"stroke-width", PropertyID::Stroke_Width}, + {"style", PropertyID::Style}, + {"text-anchor", PropertyID::Text_Anchor}, + {"text-decoration", PropertyID::Text_Decoration}, + {"transform", PropertyID::Transform}, + {"viewBox", PropertyID::ViewBox}, + {"visibility", PropertyID::Visibility}, + {"width", PropertyID::Width}, + {"word-spacing", PropertyID::Word_Spacing}, + {"x", PropertyID::X}, + {"x1", PropertyID::X1}, + {"x2", PropertyID::X2}, + {"y", PropertyID::Y}, + {"y1", PropertyID::Y1}, + {"y2", PropertyID::Y2} + }; + + auto it = std::lower_bound(table, std::end(table), name, [](auto& item, auto& name) { return item.name < name; }); + if(it != std::end(table) && it->name == name) + return it->value; + return PropertyID::Unknown; +} + +void PropertyList::set(PropertyID id, const std::string& value, int specificity) { auto property = get(id); if(property == nullptr) @@ -21,7 +158,7 @@ void PropertyList::set(PropertyId id, const std::string& value, int specificity) property->value = value; } -Property* PropertyList::get(PropertyId id) const +Property* PropertyList::get(PropertyID id) const { auto data = m_properties.data(); auto end = data + m_properties.size(); @@ -59,19 +196,19 @@ std::unique_ptr TextNode::clone() const return std::move(node); } -Element::Element(ElementId id) +Element::Element(ElementID id) : id(id) { } -void Element::set(PropertyId id, const std::string& value, int specificity) +void Element::set(PropertyID id, const std::string& value, int specificity) { properties.set(id, value, specificity); } static const std::string EmptyString; -const std::string& Element::get(PropertyId id) const +const std::string& Element::get(PropertyID id) const { auto property = properties.get(id); if(property == nullptr) @@ -82,7 +219,7 @@ const std::string& Element::get(PropertyId id) const static const std::string InheritString{"inherit"}; -const std::string& Element::find(PropertyId id) const +const std::string& Element::find(PropertyID id) const { auto element = this; do { @@ -95,12 +232,12 @@ const std::string& Element::find(PropertyId id) const return EmptyString; } -bool Element::has(PropertyId id) const +bool Element::has(PropertyID id) const { return properties.get(id); } -Element* Element::previousSibling() const +Element* Element::previousElement() const { if(parent == nullptr) return nullptr; @@ -122,7 +259,7 @@ Element* Element::previousSibling() const return nullptr; } -Element* Element::nextSibling() const +Element* Element::nextElement() const { if(parent == nullptr) return nullptr; @@ -162,15 +299,15 @@ Rect Element::currentViewport() const if(parent == nullptr) { auto element = static_cast(this); - if(element->has(PropertyId::ViewBox)) + if(element->has(PropertyID::ViewBox)) return element->viewBox(); return Rect{0, 0, 300, 150}; } - if(parent->id == ElementId::Svg) + if(parent->id == ElementID::Svg) { auto element = static_cast(parent); - if(element->has(PropertyId::ViewBox)) + if(element->has(PropertyID::ViewBox)) return element->viewBox(); LengthContext lengthContext(element); diff --git a/source/element.h b/source/element.h index f94f92774..d7e2bb834 100644 --- a/source/element.h +++ b/source/element.h @@ -8,15 +8,16 @@ namespace lunasvg { -enum class ElementId -{ +enum class ElementID { Unknown = 0, Star, + A, Circle, ClipPath, Defs, Ellipse, G, + Image, Line, LinearGradient, Marker, @@ -31,12 +32,16 @@ enum class ElementId Stop, Style, Svg, + Switch, Symbol, + Text, + TextPath, + Tref, + Tspan, Use }; -enum class PropertyId -{ +enum class PropertyID { Unknown = 0, Class, Clip_Path, @@ -46,10 +51,17 @@ enum class PropertyId Cx, Cy, D, + Dx, + Dy, Display, Fill, Fill_Opacity, Fill_Rule, + Font_Family, + Font_Size, + Font_Style, + Font_Variant, + Font_Weight, Fx, Fy, GradientTransform, @@ -57,6 +69,7 @@ enum class PropertyId Height, Href, Id, + Letter_Spacing, Marker_End, Marker_Mid, Marker_Start, @@ -78,11 +91,13 @@ enum class PropertyId R, RefX, RefY, + Rotate, Rx, Ry, Solid_Color, Solid_Opacity, SpreadMethod, + StartOffset, Stop_Color, Stop_Opacity, Stroke, @@ -94,10 +109,13 @@ enum class PropertyId Stroke_Opacity, Stroke_Width, Style, + Text_Anchor, + Text_Decoration, Transform, ViewBox, Visibility, Width, + Word_Spacing, X, X1, X2, @@ -106,9 +124,12 @@ enum class PropertyId Y2 }; +ElementID elementid(const std::string_view& name); +PropertyID propertyid(const std::string_view& name); + struct Property { - PropertyId id; + PropertyID id; std::string value; int specificity; }; @@ -118,10 +139,11 @@ class PropertyList public: PropertyList() = default; - void set(PropertyId id, const std::string& value, int specificity); - Property* get(PropertyId id) const; + void set(PropertyID id, const std::string& value, int specificity); + Property* get(PropertyID id) const; void add(const Property& property); void add(const PropertyList& properties); + void clear() { m_properties.clear(); } private: std::vector m_properties; @@ -159,20 +181,22 @@ public: std::string text; }; +using Attribute = std::pair; +using AttributeList = std::vector; using NodeList = std::list>; class Element : public Node { public: - Element(ElementId id); + Element(ElementID id); - void set(PropertyId id, const std::string& value, int specificity); - const std::string& get(PropertyId id) const; - const std::string& find(PropertyId id) const; - bool has(PropertyId id) const; + void set(PropertyID id, const std::string& value, int specificity); + const std::string& get(PropertyID id) const; + const std::string& find(PropertyID id) const; + bool has(PropertyID id) const; - Element* previousSibling() const; - Element* nextSibling() const; + Element* previousElement() const; + Element* nextElement() const; Node* addChild(std::unique_ptr child); void layoutChildren(LayoutContext* context, LayoutContainer* current) const; Rect currentViewport() const; @@ -208,7 +232,7 @@ public: } public: - ElementId id; + ElementID id; NodeList children; PropertyList properties; }; diff --git a/source/gelement.cpp b/source/gelement.cpp index b0f5c84a7..2dc805b94 100644 --- a/source/gelement.cpp +++ b/source/gelement.cpp @@ -4,7 +4,7 @@ namespace lunasvg { GElement::GElement() - : GraphicsElement(ElementId::G) + : GraphicsElement(ElementID::G) { } diff --git a/source/geometryelement.cpp b/source/geometryelement.cpp index c93b6d52b..6f848ac72 100644 --- a/source/geometryelement.cpp +++ b/source/geometryelement.cpp @@ -6,7 +6,7 @@ namespace lunasvg { -GeometryElement::GeometryElement(ElementId id) +GeometryElement::GeometryElement(ElementID id) : GraphicsElement(id) { } @@ -35,13 +35,13 @@ void GeometryElement::layout(LayoutContext* context, LayoutContainer* current) c } PathElement::PathElement() - : GeometryElement(ElementId::Path) + : GeometryElement(ElementID::Path) { } Path PathElement::d() const { - auto& value = get(PropertyId::D); + auto& value = get(PropertyID::D); return Parser::parsePath(value); } @@ -55,19 +55,19 @@ std::unique_ptr PathElement::clone() const return cloneElement(); } -PolyElement::PolyElement(ElementId id) +PolyElement::PolyElement(ElementID id) : GeometryElement(id) { } PointList PolyElement::points() const { - auto& value = get(PropertyId::Points); + auto& value = get(PropertyID::Points); return Parser::parsePointList(value); } PolygonElement::PolygonElement() - : PolyElement(ElementId::Polygon) + : PolyElement(ElementID::Polygon) { } @@ -92,7 +92,7 @@ std::unique_ptr PolygonElement::clone() const } PolylineElement::PolylineElement() - : PolyElement(ElementId::Polyline) + : PolyElement(ElementID::Polyline) { } @@ -116,25 +116,25 @@ std::unique_ptr PolylineElement::clone() const } CircleElement::CircleElement() - : GeometryElement(ElementId::Circle) + : GeometryElement(ElementID::Circle) { } Length CircleElement::cx() const { - auto& value = get(PropertyId::Cx); + auto& value = get(PropertyID::Cx); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length CircleElement::cy() const { - auto& value = get(PropertyId::Cy); + auto& value = get(PropertyID::Cy); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length CircleElement::r() const { - auto& value = get(PropertyId::R); + auto& value = get(PropertyID::R); return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); } @@ -160,31 +160,31 @@ std::unique_ptr CircleElement::clone() const } EllipseElement::EllipseElement() - : GeometryElement(ElementId::Ellipse) + : GeometryElement(ElementID::Ellipse) { } Length EllipseElement::cx() const { - auto& value = get(PropertyId::Cx); + auto& value = get(PropertyID::Cx); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length EllipseElement::cy() const { - auto& value = get(PropertyId::Cy); + auto& value = get(PropertyID::Cy); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length EllipseElement::rx() const { - auto& value = get(PropertyId::Rx); + auto& value = get(PropertyID::Rx); return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); } Length EllipseElement::ry() const { - auto& value = get(PropertyId::Ry); + auto& value = get(PropertyID::Ry); return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); } @@ -212,31 +212,31 @@ std::unique_ptr EllipseElement::clone() const } LineElement::LineElement() - : GeometryElement(ElementId::Line) + : GeometryElement(ElementID::Line) { } Length LineElement::x1() const { - auto& value = get(PropertyId::X1); + auto& value = get(PropertyID::X1); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length LineElement::y1() const { - auto& value = get(PropertyId::Y1); + auto& value = get(PropertyID::Y1); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length LineElement::x2() const { - auto& value = get(PropertyId::X2); + auto& value = get(PropertyID::X2); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length LineElement::y2() const { - auto& value = get(PropertyId::Y2); + auto& value = get(PropertyID::Y2); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } @@ -260,43 +260,43 @@ std::unique_ptr LineElement::clone() const } RectElement::RectElement() - : GeometryElement(ElementId::Rect) + : GeometryElement(ElementID::Rect) { } Length RectElement::x() const { - auto& value = get(PropertyId::X); + auto& value = get(PropertyID::X); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length RectElement::y() const { - auto& value = get(PropertyId::Y); + auto& value = get(PropertyID::Y); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length RectElement::rx() const { - auto& value = get(PropertyId::Rx); + auto& value = get(PropertyID::Rx); return Parser::parseLength(value, ForbidNegativeLengths, Length::Unknown); } Length RectElement::ry() const { - auto& value = get(PropertyId::Ry); + auto& value = get(PropertyID::Ry); return Parser::parseLength(value, ForbidNegativeLengths, Length::Unknown); } Length RectElement::width() const { - auto& value = get(PropertyId::Width); + auto& value = get(PropertyID::Width); return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); } Length RectElement::height() const { - auto& value = get(PropertyId::Height); + auto& value = get(PropertyID::Height); return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); } diff --git a/source/geometryelement.h b/source/geometryelement.h index 8d302ee39..e2f0bb462 100644 --- a/source/geometryelement.h +++ b/source/geometryelement.h @@ -10,7 +10,7 @@ class LayoutShape; class GeometryElement : public GraphicsElement { public: - GeometryElement(ElementId id); + GeometryElement(ElementID id); bool isGeometry() const { return true; } virtual void layout(LayoutContext* context, LayoutContainer* current) const; @@ -31,7 +31,7 @@ public: class PolyElement : public GeometryElement { public: - PolyElement(ElementId id); + PolyElement(ElementID id); PointList points() const; }; diff --git a/source/graphicselement.cpp b/source/graphicselement.cpp index 60c28f955..9df1a00c3 100644 --- a/source/graphicselement.cpp +++ b/source/graphicselement.cpp @@ -3,14 +3,14 @@ namespace lunasvg { -GraphicsElement::GraphicsElement(ElementId id) +GraphicsElement::GraphicsElement(ElementID id) : StyledElement(id) { } Transform GraphicsElement::transform() const { - auto& value = get(PropertyId::Transform); + auto& value = get(PropertyID::Transform); return Parser::parseTransform(value); } diff --git a/source/graphicselement.h b/source/graphicselement.h index 4da8ce8ab..1c156a04c 100644 --- a/source/graphicselement.h +++ b/source/graphicselement.h @@ -8,7 +8,7 @@ namespace lunasvg { class GraphicsElement : public StyledElement { public: - GraphicsElement(ElementId id); + GraphicsElement(ElementID id); Transform transform() const; }; diff --git a/source/layoutcontext.cpp b/source/layoutcontext.cpp index 5b92c8779..24f4d0f1e 100644 --- a/source/layoutcontext.cpp +++ b/source/layoutcontext.cpp @@ -479,14 +479,14 @@ void RenderState::endGroup(RenderState& state, const BlendInfo& info) state.canvas->blend(canvas.get(), BlendMode::Src_Over, m_mode == RenderMode::Display ? info.opacity : 1.0); } -LayoutContext::LayoutContext(const ParseDocument* document, LayoutSymbol* root) - : m_document(document), m_root(root) +LayoutContext::LayoutContext(const TreeBuilder* builder, LayoutSymbol* root) + : m_builder(builder), m_root(root) { } Element* LayoutContext::getElementById(const std::string& id) const { - return m_document->getElementById(id); + return m_builder->getElementById(id); } LayoutObject* LayoutContext::getResourcesById(const std::string& id) const @@ -517,7 +517,7 @@ LayoutMask* LayoutContext::getMasker(const std::string& id) return static_cast(ref); auto element = getElementById(id); - if(element == nullptr || element->id != ElementId::Mask) + if(element == nullptr || element->id != ElementID::Mask) return nullptr; auto masker = static_cast(element)->getMasker(this); @@ -534,7 +534,7 @@ LayoutClipPath* LayoutContext::getClipper(const std::string& id) return static_cast(ref); auto element = getElementById(id); - if(element == nullptr || element->id != ElementId::ClipPath) + if(element == nullptr || element->id != ElementID::ClipPath) return nullptr; auto clipper = static_cast(element)->getClipper(this); @@ -551,7 +551,7 @@ LayoutMarker* LayoutContext::getMarker(const std::string& id) return static_cast(ref); auto element = getElementById(id); - if(element == nullptr || element->id != ElementId::Marker) + if(element == nullptr || element->id != ElementID::Marker) return nullptr; auto marker = static_cast(element)->getMarker(this); diff --git a/source/layoutcontext.h b/source/layoutcontext.h index 13c3d3c5a..ea1aad28f 100644 --- a/source/layoutcontext.h +++ b/source/layoutcontext.h @@ -346,14 +346,13 @@ private: RenderMode m_mode; }; -class ParseDocument; +class TreeBuilder; class StyledElement; class GeometryElement; -class LayoutContext -{ +class LayoutContext { public: - LayoutContext(const ParseDocument* document, LayoutSymbol* root); + LayoutContext(const TreeBuilder* builder, LayoutSymbol* root); Element* getElementById(const std::string& id) const; LayoutObject* getResourcesById(const std::string& id) const; @@ -373,7 +372,7 @@ public: bool hasReference(const Element* element) const; private: - const ParseDocument* m_document; + const TreeBuilder* m_builder; LayoutSymbol* m_root; std::map m_resourcesCache; std::set m_references; diff --git a/source/lunasvg.cpp b/source/lunasvg.cpp index 0457067e1..a77cfefb9 100644 --- a/source/lunasvg.cpp +++ b/source/lunasvg.cpp @@ -141,15 +141,29 @@ void Bitmap::convert(int ri, int gi, int bi, int ai, bool unpremultiply) Box::Box(double x, double y, double w, double h) : x(x), y(y), w(w), h(h) -{} +{ +} Box::Box(const Rect& rect) : x(rect.x), y(rect.y), w(rect.w), h(rect.h) -{} +{ +} + +Box& Box::transform(const Matrix &matrix) +{ + *this = transformed(matrix); + return *this; +} + +Box Box::transformed(const Matrix& matrix) const +{ + return Transform(matrix).map(*this); +} Matrix::Matrix(double a, double b, double c, double d, double e, double f) : a(a), b(b), c(c), d(d), e(e), f(f) -{} +{ +} Matrix::Matrix(const Transform& transform) : a(transform.m00), b(transform.m10), c(transform.m01), d(transform.m11), e(transform.m02), f(transform.m12) @@ -232,11 +246,6 @@ Matrix Matrix::operator*(const Matrix& matrix) const return Transform(*this) * Transform(matrix); } -Box Matrix::map(const Box& box) const -{ - return Transform(*this).map(box); -} - Matrix Matrix::rotated(double angle) { return Transform::rotated(angle); @@ -283,11 +292,11 @@ std::unique_ptr Document::loadFromData(const std::string& string) std::unique_ptr Document::loadFromData(const char* data, std::size_t size) { - ParseDocument parser; - if(!parser.parse(data, size)) + TreeBuilder builder; + if(!builder.parse(data, size)) return nullptr; - auto root = parser.layout(); + auto root = builder.build(); if(!root || root->children.empty()) return nullptr; @@ -301,48 +310,6 @@ std::unique_ptr Document::loadFromData(const char* data) return loadFromData(data, std::strlen(data)); } -Document* Document::rotate(double angle) -{ - root->transform.rotate(angle); - return this; -} - -Document* Document::rotate(double angle, double cx, double cy) -{ - root->transform.rotate(angle, cx, cy); - return this; -} - -Document* Document::scale(double sx, double sy) -{ - root->transform.scale(sx, sy); - return this; -} - -Document* Document::shear(double shx, double shy) -{ - root->transform.shear(shx, shy); - return this; -} - -Document* Document::translate(double tx, double ty) -{ - root->transform.translate(tx, ty); - return this; -} - -Document* Document::transform(double a, double b, double c, double d, double e, double f) -{ - root->transform.transform(a, b, c, d, e, f); - return this; -} - -Document* Document::identity() -{ - root->transform.identity(); - return this; -} - void Document::setMatrix(const Matrix& matrix) { root->transform = Transform(matrix); diff --git a/source/markerelement.cpp b/source/markerelement.cpp index 262d7b201..5a61aff69 100644 --- a/source/markerelement.cpp +++ b/source/markerelement.cpp @@ -5,55 +5,55 @@ namespace lunasvg { MarkerElement::MarkerElement() - : StyledElement(ElementId::Marker) + : StyledElement(ElementID::Marker) { } Length MarkerElement::refX() const { - auto& value = get(PropertyId::RefX); + auto& value = get(PropertyID::RefX); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length MarkerElement::refY() const { - auto& value = get(PropertyId::RefY); + auto& value = get(PropertyID::RefY); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length MarkerElement::markerWidth() const { - auto& value = get(PropertyId::MarkerWidth); + auto& value = get(PropertyID::MarkerWidth); return Parser::parseLength(value, ForbidNegativeLengths, Length::ThreePercent); } Length MarkerElement::markerHeight() const { - auto& value = get(PropertyId::MarkerHeight); + auto& value = get(PropertyID::MarkerHeight); return Parser::parseLength(value, ForbidNegativeLengths, Length::ThreePercent); } Angle MarkerElement::orient() const { - auto& value = get(PropertyId::Orient); + auto& value = get(PropertyID::Orient); return Parser::parseAngle(value); } MarkerUnits MarkerElement::markerUnits() const { - auto& value = get(PropertyId::MarkerUnits); + auto& value = get(PropertyID::MarkerUnits); return Parser::parseMarkerUnits(value); } Rect MarkerElement::viewBox() const { - auto& value = get(PropertyId::ViewBox); + auto& value = get(PropertyID::ViewBox); return Parser::parseViewBox(value); } PreserveAspectRatio MarkerElement::preserveAspectRatio() const { - auto& value = get(PropertyId::PreserveAspectRatio); + auto& value = get(PropertyID::PreserveAspectRatio); return Parser::parsePreserveAspectRatio(value); } diff --git a/source/maskelement.cpp b/source/maskelement.cpp index da0a7fbe6..0503657e1 100644 --- a/source/maskelement.cpp +++ b/source/maskelement.cpp @@ -5,43 +5,43 @@ namespace lunasvg { MaskElement::MaskElement() - : StyledElement(ElementId::Mask) + : StyledElement(ElementID::Mask) { } Length MaskElement::x() const { - auto& value = get(PropertyId::X); + auto& value = get(PropertyID::X); return Parser::parseLength(value, AllowNegativeLengths, Length::MinusTenPercent); } Length MaskElement::y() const { - auto& value = get(PropertyId::Y); + auto& value = get(PropertyID::Y); return Parser::parseLength(value, AllowNegativeLengths, Length::MinusTenPercent); } Length MaskElement::width() const { - auto& value = get(PropertyId::Width); + auto& value = get(PropertyID::Width); return Parser::parseLength(value, ForbidNegativeLengths, Length::OneTwentyPercent); } Length MaskElement::height() const { - auto& value = get(PropertyId::Height); + auto& value = get(PropertyID::Height); return Parser::parseLength(value, ForbidNegativeLengths, Length::OneTwentyPercent); } Units MaskElement::maskUnits() const { - auto& value = get(PropertyId::MaskUnits); + auto& value = get(PropertyID::MaskUnits); return Parser::parseUnits(value, Units::ObjectBoundingBox); } Units MaskElement::maskContentUnits() const { - auto& value = get(PropertyId::MaskContentUnits); + auto& value = get(PropertyID::MaskContentUnits); return Parser::parseUnits(value, Units::UserSpaceOnUse); } diff --git a/source/paintelement.cpp b/source/paintelement.cpp index 0e0776e72..5bb85c199 100644 --- a/source/paintelement.cpp +++ b/source/paintelement.cpp @@ -7,37 +7,37 @@ namespace lunasvg { -PaintElement::PaintElement(ElementId id) +PaintElement::PaintElement(ElementID id) : StyledElement(id) { } -GradientElement::GradientElement(ElementId id) +GradientElement::GradientElement(ElementID id) : PaintElement(id) { } Transform GradientElement::gradientTransform() const { - auto& value = get(PropertyId::GradientTransform); + auto& value = get(PropertyID::GradientTransform); return Parser::parseTransform(value); } SpreadMethod GradientElement::spreadMethod() const { - auto& value = get(PropertyId::SpreadMethod); + auto& value = get(PropertyID::SpreadMethod); return Parser::parseSpreadMethod(value); } Units GradientElement::gradientUnits() const { - auto& value = get(PropertyId::GradientUnits); + auto& value = get(PropertyID::GradientUnits); return Parser::parseUnits(value, Units::ObjectBoundingBox); } std::string GradientElement::href() const { - auto& value = get(PropertyId::Href); + auto& value = get(PropertyID::Href); return Parser::parseHref(value); } @@ -50,7 +50,7 @@ GradientStops GradientElement::buildGradientStops() const if(child->isText()) continue; auto element = static_cast(child.get()); - if(element->id != ElementId::Stop) + if(element->id != ElementID::Stop) continue; auto stop = static_cast(element); auto offset = std::max(prevOffset, stop->offset()); @@ -62,31 +62,31 @@ GradientStops GradientElement::buildGradientStops() const } LinearGradientElement::LinearGradientElement() - : GradientElement(ElementId::LinearGradient) + : GradientElement(ElementID::LinearGradient) { } Length LinearGradientElement::x1() const { - auto& value = get(PropertyId::X1); + auto& value = get(PropertyID::X1); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length LinearGradientElement::y1() const { - auto& value = get(PropertyId::Y1); + auto& value = get(PropertyID::Y1); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length LinearGradientElement::x2() const { - auto& value = get(PropertyId::X2); + auto& value = get(PropertyID::X2); return Parser::parseLength(value, AllowNegativeLengths, Length::HundredPercent); } Length LinearGradientElement::y2() const { - auto& value = get(PropertyId::Y2); + auto& value = get(PropertyID::Y2); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } @@ -98,30 +98,30 @@ std::unique_ptr LinearGradientElement::getPainter(LayoutContext* c while(true) { - if(!attributes.hasGradientTransform() && current->has(PropertyId::GradientTransform)) + if(!attributes.hasGradientTransform() && current->has(PropertyID::GradientTransform)) attributes.setGradientTransform(current->gradientTransform()); - if(!attributes.hasSpreadMethod() && current->has(PropertyId::SpreadMethod)) + if(!attributes.hasSpreadMethod() && current->has(PropertyID::SpreadMethod)) attributes.setSpreadMethod(current->spreadMethod()); - if(!attributes.hasGradientUnits() && current->has(PropertyId::GradientUnits)) + if(!attributes.hasGradientUnits() && current->has(PropertyID::GradientUnits)) attributes.setGradientUnits(current->gradientUnits()); if(!attributes.hasGradientStops()) attributes.setGradientStops(current->buildGradientStops()); - if(current->id == ElementId::LinearGradient) + if(current->id == ElementID::LinearGradient) { auto element = static_cast(current); - if(!attributes.hasX1() && element->has(PropertyId::X1)) + if(!attributes.hasX1() && element->has(PropertyID::X1)) attributes.setX1(element->x1()); - if(!attributes.hasY1() && element->has(PropertyId::Y1)) + if(!attributes.hasY1() && element->has(PropertyID::Y1)) attributes.setY1(element->y1()); - if(!attributes.hasX2() && element->has(PropertyId::X2)) + if(!attributes.hasX2() && element->has(PropertyID::X2)) attributes.setX2(element->x2()); - if(!attributes.hasY2() && element->has(PropertyId::Y2)) + if(!attributes.hasY2() && element->has(PropertyID::Y2)) attributes.setY2(element->y2()); } auto ref = context->getElementById(current->href()); - if(!ref || !(ref->id == ElementId::LinearGradient || ref->id == ElementId::RadialGradient)) + if(!ref || !(ref->id == ElementID::LinearGradient || ref->id == ElementID::RadialGradient)) break; processedGradients.insert(current); @@ -164,37 +164,37 @@ std::unique_ptr LinearGradientElement::clone() const } RadialGradientElement::RadialGradientElement() - : GradientElement(ElementId::RadialGradient) + : GradientElement(ElementID::RadialGradient) { } Length RadialGradientElement::cx() const { - auto& value = get(PropertyId::Cx); + auto& value = get(PropertyID::Cx); return Parser::parseLength(value, AllowNegativeLengths, Length::FiftyPercent); } Length RadialGradientElement::cy() const { - auto& value = get(PropertyId::Cy); + auto& value = get(PropertyID::Cy); return Parser::parseLength(value, AllowNegativeLengths, Length::FiftyPercent); } Length RadialGradientElement::r() const { - auto& value = get(PropertyId::R); + auto& value = get(PropertyID::R); return Parser::parseLength(value, ForbidNegativeLengths, Length::FiftyPercent); } Length RadialGradientElement::fx() const { - auto& value = get(PropertyId::Fx); + auto& value = get(PropertyID::Fx); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length RadialGradientElement::fy() const { - auto& value = get(PropertyId::Fy); + auto& value = get(PropertyID::Fy); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } @@ -206,32 +206,32 @@ std::unique_ptr RadialGradientElement::getPainter(LayoutContext* c while(true) { - if(!attributes.hasGradientTransform() && current->has(PropertyId::GradientTransform)) + if(!attributes.hasGradientTransform() && current->has(PropertyID::GradientTransform)) attributes.setGradientTransform(current->gradientTransform()); - if(!attributes.hasSpreadMethod() && current->has(PropertyId::SpreadMethod)) + if(!attributes.hasSpreadMethod() && current->has(PropertyID::SpreadMethod)) attributes.setSpreadMethod(current->spreadMethod()); - if(!attributes.hasGradientUnits() && current->has(PropertyId::GradientUnits)) + if(!attributes.hasGradientUnits() && current->has(PropertyID::GradientUnits)) attributes.setGradientUnits(current->gradientUnits()); if(!attributes.hasGradientStops()) attributes.setGradientStops(current->buildGradientStops()); - if(current->id == ElementId::RadialGradient) + if(current->id == ElementID::RadialGradient) { auto element = static_cast(current); - if(!attributes.hasCx() && element->has(PropertyId::Cx)) + if(!attributes.hasCx() && element->has(PropertyID::Cx)) attributes.setCx(element->cx()); - if(!attributes.hasCy() && element->has(PropertyId::Cy)) + if(!attributes.hasCy() && element->has(PropertyID::Cy)) attributes.setCy(element->cy()); - if(!attributes.hasR() && element->has(PropertyId::R)) + if(!attributes.hasR() && element->has(PropertyID::R)) attributes.setR(element->r()); - if(!attributes.hasFx() && element->has(PropertyId::Fx)) + if(!attributes.hasFx() && element->has(PropertyID::Fx)) attributes.setFx(element->fx()); - if(!attributes.hasFy() && element->has(PropertyId::Fy)) + if(!attributes.hasFy() && element->has(PropertyID::Fy)) attributes.setFy(element->fy()); } auto ref = context->getElementById(current->href()); - if(!ref || !(ref->id == ElementId::LinearGradient || ref->id == ElementId::RadialGradient)) + if(!ref || !(ref->id == ElementID::LinearGradient || ref->id == ElementID::RadialGradient)) break; processedGradients.insert(current); @@ -278,67 +278,67 @@ std::unique_ptr RadialGradientElement::clone() const } PatternElement::PatternElement() - : PaintElement(ElementId::Pattern) + : PaintElement(ElementID::Pattern) { } Length PatternElement::x() const { - auto& value = get(PropertyId::X); + auto& value = get(PropertyID::X); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length PatternElement::y() const { - auto& value = get(PropertyId::Y); + auto& value = get(PropertyID::Y); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length PatternElement::width() const { - auto& value = get(PropertyId::Width); + auto& value = get(PropertyID::Width); return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); } Length PatternElement::height() const { - auto& value = get(PropertyId::Height); + auto& value = get(PropertyID::Height); return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); } Transform PatternElement::patternTransform() const { - auto& value = get(PropertyId::PatternTransform); + auto& value = get(PropertyID::PatternTransform); return Parser::parseTransform(value); } Units PatternElement::patternUnits() const { - auto& value = get(PropertyId::PatternUnits); + auto& value = get(PropertyID::PatternUnits); return Parser::parseUnits(value, Units::ObjectBoundingBox); } Units PatternElement::patternContentUnits() const { - auto& value = get(PropertyId::PatternContentUnits); + auto& value = get(PropertyID::PatternContentUnits); return Parser::parseUnits(value, Units::UserSpaceOnUse); } Rect PatternElement::viewBox() const { - auto& value = get(PropertyId::ViewBox); + auto& value = get(PropertyID::ViewBox); return Parser::parseViewBox(value); } PreserveAspectRatio PatternElement::preserveAspectRatio() const { - auto& value = get(PropertyId::PreserveAspectRatio); + auto& value = get(PropertyID::PreserveAspectRatio); return Parser::parsePreserveAspectRatio(value); } std::string PatternElement::href() const { - auto& value = get(PropertyId::Href); + auto& value = get(PropertyID::Href); return Parser::parseHref(value); } @@ -353,29 +353,29 @@ std::unique_ptr PatternElement::getPainter(LayoutContext* context) while(true) { - if(!attributes.hasX() && current->has(PropertyId::X)) + if(!attributes.hasX() && current->has(PropertyID::X)) attributes.setX(current->x()); - if(!attributes.hasY() && current->has(PropertyId::Y)) + if(!attributes.hasY() && current->has(PropertyID::Y)) attributes.setY(current->y()); - if(!attributes.hasWidth() && current->has(PropertyId::Width)) + if(!attributes.hasWidth() && current->has(PropertyID::Width)) attributes.setWidth(current->width()); - if(!attributes.hasHeight() && current->has(PropertyId::Height)) + if(!attributes.hasHeight() && current->has(PropertyID::Height)) attributes.setHeight(current->height()); - if(!attributes.hasPatternTransform() && current->has(PropertyId::PatternTransform)) + if(!attributes.hasPatternTransform() && current->has(PropertyID::PatternTransform)) attributes.setPatternTransform(current->patternTransform()); - if(!attributes.hasPatternUnits() && current->has(PropertyId::PatternUnits)) + if(!attributes.hasPatternUnits() && current->has(PropertyID::PatternUnits)) attributes.setPatternUnits(current->patternUnits()); - if(!attributes.hasPatternContentUnits() && current->has(PropertyId::PatternContentUnits)) + if(!attributes.hasPatternContentUnits() && current->has(PropertyID::PatternContentUnits)) attributes.setPatternContentUnits(current->patternContentUnits()); - if(!attributes.hasViewBox() && current->has(PropertyId::ViewBox)) + if(!attributes.hasViewBox() && current->has(PropertyID::ViewBox)) attributes.setViewBox(current->viewBox()); - if(!attributes.hasPreserveAspectRatio() && current->has(PropertyId::PreserveAspectRatio)) + if(!attributes.hasPreserveAspectRatio() && current->has(PropertyID::PreserveAspectRatio)) attributes.setPreserveAspectRatio(current->preserveAspectRatio()); if(!attributes.hasPatternContentElement() && current->children.size()) attributes.setPatternContentElement(current); auto ref = context->getElementById(current->href()); - if(!ref || ref->id != ElementId::Pattern) + if(!ref || ref->id != ElementID::Pattern) break; processedPatterns.insert(current); @@ -413,7 +413,7 @@ std::unique_ptr PatternElement::clone() const } SolidColorElement::SolidColorElement() - : PaintElement(ElementId::SolidColor) + : PaintElement(ElementID::SolidColor) { } diff --git a/source/paintelement.h b/source/paintelement.h index 70c86cd9f..b2d152a63 100644 --- a/source/paintelement.h +++ b/source/paintelement.h @@ -11,7 +11,7 @@ class LayoutObject; class PaintElement : public StyledElement { public: - PaintElement(ElementId id); + PaintElement(ElementID id); bool isPaint() const { return true; } virtual std::unique_ptr getPainter(LayoutContext* context) const = 0; @@ -20,7 +20,7 @@ public: class GradientElement : public PaintElement { public: - GradientElement(ElementId id); + GradientElement(ElementID id); Transform gradientTransform() const; SpreadMethod spreadMethod() const; diff --git a/source/parser.cpp b/source/parser.cpp index cda095a4b..6eab7cdd2 100644 --- a/source/parser.cpp +++ b/source/parser.cpp @@ -1024,122 +1024,122 @@ bool Parser::parseTransform(const char*& ptr, const char* end, TransformType& ty return true; } -static const std::map elementmap = { - {"circle", ElementId::Circle}, - {"clipPath", ElementId::ClipPath}, - {"defs", ElementId::Defs}, - {"ellipse", ElementId::Ellipse}, - {"g", ElementId::G}, - {"line", ElementId::Line}, - {"linearGradient", ElementId::LinearGradient}, - {"marker", ElementId::Marker}, - {"mask", ElementId::Mask}, - {"path", ElementId::Path}, - {"pattern", ElementId::Pattern}, - {"polygon", ElementId::Polygon}, - {"polyline", ElementId::Polyline}, - {"radialGradient", ElementId::RadialGradient}, - {"rect", ElementId::Rect}, - {"stop", ElementId::Stop}, - {"style", ElementId::Style}, - {"solidColor", ElementId::SolidColor}, - {"svg", ElementId::Svg}, - {"symbol", ElementId::Symbol}, - {"use", ElementId::Use} +static const std::map elementmap = { + {"circle", ElementID::Circle}, + {"clipPath", ElementID::ClipPath}, + {"defs", ElementID::Defs}, + {"ellipse", ElementID::Ellipse}, + {"g", ElementID::G}, + {"line", ElementID::Line}, + {"linearGradient", ElementID::LinearGradient}, + {"marker", ElementID::Marker}, + {"mask", ElementID::Mask}, + {"path", ElementID::Path}, + {"pattern", ElementID::Pattern}, + {"polygon", ElementID::Polygon}, + {"polyline", ElementID::Polyline}, + {"radialGradient", ElementID::RadialGradient}, + {"rect", ElementID::Rect}, + {"stop", ElementID::Stop}, + {"style", ElementID::Style}, + {"solidColor", ElementID::SolidColor}, + {"svg", ElementID::Svg}, + {"symbol", ElementID::Symbol}, + {"use", ElementID::Use} }; -static const std::map propertymap = { - {"class", PropertyId::Class}, - {"clipPathUnits", PropertyId::ClipPathUnits}, - {"cx", PropertyId::Cx}, - {"cy", PropertyId::Cy}, - {"d", PropertyId::D}, - {"fx", PropertyId::Fx}, - {"fy", PropertyId::Fy}, - {"gradientTransform", PropertyId::GradientTransform}, - {"gradientUnits", PropertyId::GradientUnits}, - {"height", PropertyId::Height}, - {"href", PropertyId::Href}, - {"id", PropertyId::Id}, - {"markerHeight", PropertyId::MarkerHeight}, - {"markerUnits", PropertyId::MarkerUnits}, - {"markerWidth", PropertyId::MarkerWidth}, - {"maskContentUnits", PropertyId::MaskContentUnits}, - {"maskUnits", PropertyId::MaskUnits}, - {"offset", PropertyId::Offset}, - {"orient", PropertyId::Orient}, - {"patternContentUnits", PropertyId::PatternContentUnits}, - {"patternTransform", PropertyId::PatternTransform}, - {"patternUnits", PropertyId::PatternUnits}, - {"points", PropertyId::Points}, - {"preserveAspectRatio", PropertyId::PreserveAspectRatio}, - {"r", PropertyId::R}, - {"refX", PropertyId::RefX}, - {"refY", PropertyId::RefY}, - {"rx", PropertyId::Rx}, - {"ry", PropertyId::Ry}, - {"spreadMethod", PropertyId::SpreadMethod}, - {"style", PropertyId::Style}, - {"transform", PropertyId::Transform}, - {"viewBox", PropertyId::ViewBox}, - {"width", PropertyId::Width}, - {"x", PropertyId::X}, - {"x1", PropertyId::X1}, - {"x2", PropertyId::X2}, - {"xlink:href", PropertyId::Href}, - {"y", PropertyId::Y}, - {"y1", PropertyId::Y1}, - {"y2", PropertyId::Y2} +static const std::map propertymap = { + {"class", PropertyID::Class}, + {"clipPathUnits", PropertyID::ClipPathUnits}, + {"cx", PropertyID::Cx}, + {"cy", PropertyID::Cy}, + {"d", PropertyID::D}, + {"fx", PropertyID::Fx}, + {"fy", PropertyID::Fy}, + {"gradientTransform", PropertyID::GradientTransform}, + {"gradientUnits", PropertyID::GradientUnits}, + {"height", PropertyID::Height}, + {"href", PropertyID::Href}, + {"id", PropertyID::Id}, + {"markerHeight", PropertyID::MarkerHeight}, + {"markerUnits", PropertyID::MarkerUnits}, + {"markerWidth", PropertyID::MarkerWidth}, + {"maskContentUnits", PropertyID::MaskContentUnits}, + {"maskUnits", PropertyID::MaskUnits}, + {"offset", PropertyID::Offset}, + {"orient", PropertyID::Orient}, + {"patternContentUnits", PropertyID::PatternContentUnits}, + {"patternTransform", PropertyID::PatternTransform}, + {"patternUnits", PropertyID::PatternUnits}, + {"points", PropertyID::Points}, + {"preserveAspectRatio", PropertyID::PreserveAspectRatio}, + {"r", PropertyID::R}, + {"refX", PropertyID::RefX}, + {"refY", PropertyID::RefY}, + {"rx", PropertyID::Rx}, + {"ry", PropertyID::Ry}, + {"spreadMethod", PropertyID::SpreadMethod}, + {"style", PropertyID::Style}, + {"transform", PropertyID::Transform}, + {"viewBox", PropertyID::ViewBox}, + {"width", PropertyID::Width}, + {"x", PropertyID::X}, + {"x1", PropertyID::X1}, + {"x2", PropertyID::X2}, + {"xlink:href", PropertyID::Href}, + {"y", PropertyID::Y}, + {"y1", PropertyID::Y1}, + {"y2", PropertyID::Y2} }; -static const std::map csspropertymap = { - {"clip-path", PropertyId::Clip_Path}, - {"clip-rule", PropertyId::Clip_Rule}, - {"color", PropertyId::Color}, - {"display", PropertyId::Display}, - {"fill", PropertyId::Fill}, - {"fill-opacity", PropertyId::Fill_Opacity}, - {"fill-rule", PropertyId::Fill_Rule}, - {"marker-end", PropertyId::Marker_End}, - {"marker-mid", PropertyId::Marker_Mid}, - {"marker-start", PropertyId::Marker_Start}, - {"mask", PropertyId::Mask}, - {"opacity", PropertyId::Opacity}, - {"overflow", PropertyId::Overflow}, - {"solid-color", PropertyId::Solid_Color}, - {"solid-opacity", PropertyId::Solid_Opacity}, - {"stop-color", PropertyId::Stop_Color}, - {"stop-opacity", PropertyId::Stop_Opacity}, - {"stroke", PropertyId::Stroke}, - {"stroke-dasharray", PropertyId::Stroke_Dasharray}, - {"stroke-dashoffset", PropertyId::Stroke_Dashoffset}, - {"stroke-linecap", PropertyId::Stroke_Linecap}, - {"stroke-linejoin", PropertyId::Stroke_Linejoin}, - {"stroke-miterlimit", PropertyId::Stroke_Miterlimit}, - {"stroke-opacity", PropertyId::Stroke_Opacity}, - {"stroke-width", PropertyId::Stroke_Width}, - {"visibility", PropertyId::Visibility} +static const std::map csspropertymap = { + {"clip-path", PropertyID::Clip_Path}, + {"clip-rule", PropertyID::Clip_Rule}, + {"color", PropertyID::Color}, + {"display", PropertyID::Display}, + {"fill", PropertyID::Fill}, + {"fill-opacity", PropertyID::Fill_Opacity}, + {"fill-rule", PropertyID::Fill_Rule}, + {"marker-end", PropertyID::Marker_End}, + {"marker-mid", PropertyID::Marker_Mid}, + {"marker-start", PropertyID::Marker_Start}, + {"mask", PropertyID::Mask}, + {"opacity", PropertyID::Opacity}, + {"overflow", PropertyID::Overflow}, + {"solid-color", PropertyID::Solid_Color}, + {"solid-opacity", PropertyID::Solid_Opacity}, + {"stop-color", PropertyID::Stop_Color}, + {"stop-opacity", PropertyID::Stop_Opacity}, + {"stroke", PropertyID::Stroke}, + {"stroke-dasharray", PropertyID::Stroke_Dasharray}, + {"stroke-dashoffset", PropertyID::Stroke_Dashoffset}, + {"stroke-linecap", PropertyID::Stroke_Linecap}, + {"stroke-linejoin", PropertyID::Stroke_Linejoin}, + {"stroke-miterlimit", PropertyID::Stroke_Miterlimit}, + {"stroke-opacity", PropertyID::Stroke_Opacity}, + {"stroke-width", PropertyID::Stroke_Width}, + {"visibility", PropertyID::Visibility} }; -static inline ElementId elementId(const std::string& name) +static inline ElementID elementId(const std::string& name) { auto it = elementmap.find(name); if(it == elementmap.end()) - return ElementId::Unknown; + return ElementID::Unknown; return it->second; } -static inline PropertyId cssPropertyId(const std::string& name) +static inline PropertyID cssPropertyId(const std::string& name) { auto it = csspropertymap.find(name); if(it == csspropertymap.end()) - return PropertyId::Unknown; + return PropertyID::Unknown; return it->second; } -static inline PropertyId propertyId(const std::string& name) +static inline PropertyID propertyId(const std::string& name) { auto it = propertymap.find(name); if(it == propertymap.end()) @@ -1148,331 +1148,17 @@ static inline PropertyId propertyId(const std::string& name) return it->second; } -#define IS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_' || (c) == ':') -#define IS_NAMECHAR(c) (IS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-' || (c) == '.') -static inline bool readIdentifier(const char*& ptr, const char* end, std::string& value) +bool RuleData::match(const Element* element) const { - if(ptr >= end || !IS_STARTNAMECHAR(*ptr)) + if(m_selector.empty()) return false; - auto start = ptr; - ++ptr; - while(ptr < end && IS_NAMECHAR(*ptr)) - ++ptr; + if(m_selector.size() == 1) + return matchSimpleSelector(m_selector.front(), element); - value.assign(start, ptr); - return true; -} - -#define IS_CSS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_') -#define IS_CSS_NAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-') -static inline bool readCSSIdentifier(const char*& ptr, const char* end, std::string& value) -{ - if(ptr >= end || !IS_CSS_STARTNAMECHAR(*ptr)) - return false; - - auto start = ptr; - ++ptr; - while(ptr < end && IS_CSS_NAMECHAR(*ptr)) - ++ptr; - - value.assign(start, ptr); - return true; -} - -bool CSSParser::parseMore(const std::string& value) -{ - auto ptr = value.data(); - auto end = ptr + value.size(); - - while(ptr < end) - { - Utils::skipWs(ptr, end); - if(Utils::skipDesc(ptr, end, '@')) - { - if(!parseAtRule(ptr, end)) - return false; - continue; - } - - Rule rule; - if(!parseRule(ptr, end, rule)) - return false; - m_rules.push_back(rule); - } - - return true; -} - -bool CSSParser::parseAtRule(const char*& ptr, const char* end) const -{ - int depth = 0; - while(ptr < end) - { - auto ch = *ptr; - ++ptr; - if(ch == ';' && depth == 0) - break; - if(ch == '{') ++depth; - else if(ch == '}' && depth > 0) - { - if(depth == 1) - break; - --depth; - } - } - - return true; -} - -bool CSSParser::parseRule(const char*& ptr, const char* end, Rule& rule) const -{ - if(!parseSelectors(ptr, end, rule.selectors)) - return false; - - if(!parseDeclarations(ptr, end, rule.declarations)) - return false; - - return true; -} - -bool CSSParser::parseSelectors(const char*& ptr, const char* end, SelectorList& selectors) const -{ - Selector selector; - if(!parseSelector(ptr, end, selector)) - return false; - selectors.push_back(selector); - - while(Utils::skipDesc(ptr, end, ',')) - { - Utils::skipWs(ptr, end); - Selector selector; - if(!parseSelector(ptr, end, selector)) - return false; - selectors.push_back(selector); - } - - return true; -} - -bool CSSParser::parseDeclarations(const char*& ptr, const char* end, PropertyList& declarations) const -{ - if(!Utils::skipDesc(ptr, end, '{')) - return false; - - std::string name; - std::string value; - Utils::skipWs(ptr, end); - do { - if(!readCSSIdentifier(ptr, end, name)) - return false; - Utils::skipWs(ptr, end); - if(!Utils::skipDesc(ptr, end, ':')) - return false; - Utils::skipWs(ptr, end); - auto start = ptr; - while(ptr < end && !(*ptr == '!' || *ptr == ';' || *ptr == '}')) - ++ptr; - value.assign(start, Utils::rtrim(start, ptr)); - int specificity = 0x10; - if(Utils::skipDesc(ptr, end, '!')) - { - if(!Utils::skipDesc(ptr, end, "important")) - return false; - specificity = 0x1000; - } - - auto id = cssPropertyId(name); - if(id != PropertyId::Unknown) - declarations.set(id, value, specificity); - Utils::skipWsDelimiter(ptr, end, ';'); - } while(ptr < end && *ptr != '}'); - - return Utils::skipDesc(ptr, end, '}'); -} - -#define IS_SELECTOR_STARTNAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || (c) == '*' || (c) == '#' || (c) == '.' || (c) == '[' || (c) == ':') -bool CSSParser::parseSelector(const char*& ptr, const char* end, Selector& selector) const -{ - do { - SimpleSelector simpleSelector; - if(!parseSimpleSelector(ptr, end, simpleSelector)) - return false; - - selector.specificity += (simpleSelector.id == ElementId::Star) ? 0x0 : 0x1; - for(auto& attributeSelector : simpleSelector.attributeSelectors) - selector.specificity += (attributeSelector.id == PropertyId::Id) ? 0x10000 : 0x100; - - selector.simpleSelectors.push_back(simpleSelector); - Utils::skipWs(ptr, end); - } while(ptr < end && IS_SELECTOR_STARTNAMECHAR(*ptr)); - - return true; -} - -bool CSSParser::parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector) const -{ - std::string name; - if(Utils::skipDesc(ptr, end, '*')) - simpleSelector.id = ElementId::Star; - else if(readCSSIdentifier(ptr, end, name)) - simpleSelector.id = elementId(name); - - while(ptr < end) - { - if(Utils::skipDesc(ptr, end, '#')) - { - AttributeSelector a; - a.id = PropertyId::Id; - a.matchType = AttributeSelector::MatchType::Equal; - if(!readCSSIdentifier(ptr, end, a.value)) - return false; - simpleSelector.attributeSelectors.push_back(a); - continue; - } - - if(Utils::skipDesc(ptr, end, '.')) - { - AttributeSelector a; - a.id = PropertyId::Class; - a.matchType = AttributeSelector::MatchType::Includes; - if(!readCSSIdentifier(ptr, end, a.value)) - return false; - simpleSelector.attributeSelectors.push_back(a); - continue; - } - - if(Utils::skipDesc(ptr, end, '[')) - { - Utils::skipWs(ptr, end); - if(!readCSSIdentifier(ptr, end, name)) - return false; - AttributeSelector a; - a.id = propertyId(name); - if(Utils::skipDesc(ptr, end, '=')) - a.matchType = AttributeSelector::MatchType::Equal; - else if(Utils::skipDesc(ptr, end, "~=")) - a.matchType = AttributeSelector::MatchType::Includes; - else if(Utils::skipDesc(ptr, end, "|=")) - a.matchType = AttributeSelector::MatchType::DashMatch; - else if(Utils::skipDesc(ptr, end, "^=")) - a.matchType = AttributeSelector::MatchType::StartsWith; - else if(Utils::skipDesc(ptr, end, "$=")) - a.matchType = AttributeSelector::MatchType::EndsWith; - else if(Utils::skipDesc(ptr, end, "*=")) - a.matchType = AttributeSelector::MatchType::Contains; - if(a.matchType != AttributeSelector::MatchType::None) - { - Utils::skipWs(ptr, end); - if(!readCSSIdentifier(ptr, end, a.value)) - { - if(ptr >= end || !(*ptr == '\"' || *ptr == '\'')) - return false; - - auto quote = *ptr; - ++ptr; - if(!Utils::readUntil(ptr, end, quote, a.value)) - return false; - ++ptr; - } - } - - Utils::skipWs(ptr, end); - if(!Utils::skipDesc(ptr, end, ']')) - return false; - simpleSelector.attributeSelectors.push_back(a); - continue; - } - - if(Utils::skipDesc(ptr, end, ':')) - { - if(!readCSSIdentifier(ptr, end, name)) - return false; - PseudoClass pseudo; - if(name.compare("empty") == 0) - pseudo.type = PseudoClass::Type::Empty; - else if(name.compare("root") == 0) - pseudo.type = PseudoClass::Type::Root; - else if(name.compare("not") == 0) - pseudo.type = PseudoClass::Type::Not; - else if(name.compare("first-child") == 0) - pseudo.type = PseudoClass::Type::FirstChild; - else if(name.compare("last-child") == 0) - pseudo.type = PseudoClass::Type::LastChild; - else if(name.compare("only-child") == 0) - pseudo.type = PseudoClass::Type::OnlyChild; - else if(name.compare("first-of-type") == 0) - pseudo.type = PseudoClass::Type::FirstOfType; - else if(name.compare("last-of-type") == 0) - pseudo.type = PseudoClass::Type::LastOfType; - else if(name.compare("only-of-type") == 0) - pseudo.type = PseudoClass::Type::OnlyOfType; - if(pseudo.type == PseudoClass::Type::Not) - { - if(!Utils::skipDesc(ptr, end, '(')) - return false; - - Utils::skipWs(ptr, end); - if(!parseSelectors(ptr, end, pseudo.notSelectors)) - return false; - - Utils::skipWs(ptr, end); - if(!Utils::skipDesc(ptr, end, ')')) - return false; - } - - simpleSelector.pseudoClasses.push_back(pseudo); - continue; - } - - break; - } - - Utils::skipWs(ptr, end); - if(Utils::skipDesc(ptr, end, '>')) - simpleSelector.combinator = SimpleSelector::Combinator::Child; - else if(Utils::skipDesc(ptr, end, '+')) - simpleSelector.combinator = SimpleSelector::Combinator::DirectAdjacent; - else if(Utils::skipDesc(ptr, end, '~')) - simpleSelector.combinator = SimpleSelector::Combinator::InDirectAdjacent; - - return true; -} - -RuleMatchContext::RuleMatchContext(const std::vector& rules) -{ - for(auto& rule : rules) - for(auto& selector : rule.selectors) - m_selectors.emplace(selector.specificity, std::make_pair(&selector, &rule.declarations)); -} - -std::vector RuleMatchContext::match(const Element* element) const -{ - std::vector declarations; - auto it = m_selectors.begin(); - auto end = m_selectors.end(); - for(;it != end;++it) - { - auto& value = it->second; - if(!selectorMatch(std::get<0>(value), element)) - continue; - declarations.push_back(std::get<1>(value)); - } - - return declarations; -} - -bool RuleMatchContext::selectorMatch(const Selector* selector, const Element* element) const -{ - if(selector->simpleSelectors.empty()) - return false; - - if(selector->simpleSelectors.size() == 1) - return simpleSelectorMatch(selector->simpleSelectors.front(), element); - - auto it = selector->simpleSelectors.rbegin(); - auto end = selector->simpleSelectors.rend(); - if(!simpleSelectorMatch(*it, element)) + auto it = m_selector.rbegin(); + auto end = m_selector.rend(); + if(!matchSimpleSelector(*it, element)) return false; ++it; @@ -1485,14 +1171,14 @@ bool RuleMatchContext::selectorMatch(const Selector* selector, const Element* el break; case SimpleSelector::Combinator::DirectAdjacent: case SimpleSelector::Combinator::InDirectAdjacent: - element = element->previousSibling(); + element = element->previousElement(); break; } if(element == nullptr) return false; - auto match = simpleSelectorMatch(*it, element); + auto match = matchSimpleSelector(*it, element); if(!match && (it->combinator != SimpleSelector::Combinator::Descendant && it->combinator != SimpleSelector::Combinator::InDirectAdjacent)) return false; @@ -1503,23 +1189,23 @@ bool RuleMatchContext::selectorMatch(const Selector* selector, const Element* el return true; } -bool RuleMatchContext::simpleSelectorMatch(const SimpleSelector& selector, const Element* element) const +bool RuleData::matchSimpleSelector(const SimpleSelector& selector, const Element* element) const { - if(selector.id != ElementId::Star && selector.id != element->id) + if(selector.id != ElementID::Star && selector.id != element->id) return false; - for(auto& attributeSelector : selector.attributeSelectors) - if(!attributeSelectorMatch(attributeSelector, element)) + for(auto& sel : selector.attributeSelectors) + if(!matchAttributeSelector(sel, element)) return false; - for(auto& pseudoClass : selector.pseudoClasses) - if(!pseudoClassMatch(pseudoClass, element)) + for(auto& sel : selector.pseudoClassSelectors) + if(!matchPseudoClassSelector(sel, element)) return false; return true; } -bool RuleMatchContext::attributeSelectorMatch(const AttributeSelector& selector, const Element* element) const +bool RuleData::matchAttributeSelector(const AttributeSelector& selector, const Element* element) const { auto& value = element->get(selector.id); if(value.empty()) @@ -1583,52 +1269,70 @@ bool RuleMatchContext::attributeSelectorMatch(const AttributeSelector& selector, return false; } -bool RuleMatchContext::pseudoClassMatch(const PseudoClass& pseudo, const Element* element) const +bool RuleData::matchPseudoClassSelector(const PseudoClassSelector& selector, const Element* element) const { - if(pseudo.type == PseudoClass::Type::Empty) + if(selector.type == PseudoClassSelector::Type::Empty) return element->children.empty(); - if(pseudo.type == PseudoClass::Type::Root) + if(selector.type == PseudoClassSelector::Type::Root) return element->parent == nullptr; - if(pseudo.type == PseudoClass::Type::Not) + if(selector.type == PseudoClassSelector::Type::Is) { - for(auto& selector : pseudo.notSelectors) - if(selectorMatch(&selector, element)) - return false; - return true; - } - - if(pseudo.type == PseudoClass::Type::FirstChild) - return !element->previousSibling(); - - if(pseudo.type == PseudoClass::Type::LastChild) - return !element->nextSibling(); - - if(pseudo.type == PseudoClass::Type::OnlyChild) - return !(element->previousSibling() || element->nextSibling()); - - if(pseudo.type == PseudoClass::Type::FirstOfType) - { - auto sibling = element->previousSibling(); - while(sibling) - { - if(sibling->id == element->id) - return false; - sibling = element->previousSibling(); + for(auto& selector : selector.subSelectors) { + for(auto& sel : selector) { + if(!matchSimpleSelector(sel, element)) { + return false; + } + } } return true; } - if(pseudo.type == PseudoClass::Type::LastOfType) + if(selector.type == PseudoClassSelector::Type::Not) { - auto sibling = element->nextSibling(); + for(auto& selector : selector.subSelectors) { + for(auto& sel : selector) { + if(matchSimpleSelector(sel, element)) { + return false; + } + } + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::FirstChild) + return !element->previousElement(); + + if(selector.type == PseudoClassSelector::Type::LastChild) + return !element->nextElement(); + + if(selector.type == PseudoClassSelector::Type::OnlyChild) + return !(element->previousElement() || element->nextElement()); + + if(selector.type == PseudoClassSelector::Type::FirstOfType) + { + auto sibling = element->previousElement(); while(sibling) { if(sibling->id == element->id) return false; - sibling = element->nextSibling(); + sibling = element->previousElement(); + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::LastOfType) + { + auto sibling = element->nextElement(); + while(sibling) + { + if(sibling->id == element->id) + return false; + sibling = element->nextElement(); } return true; @@ -1637,50 +1341,372 @@ bool RuleMatchContext::pseudoClassMatch(const PseudoClass& pseudo, const Element return false; } -static inline std::unique_ptr createElement(ElementId id) +void StyleSheet::parse(const std::string& content) +{ + CSSParser::parseSheet(this, content); +} + +void StyleSheet::add(const Rule& rule) +{ + for(auto& selector : rule.selectors) { + uint32_t specificity = 0; + for(auto& simpleSelector : selector) { + specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1; + for(auto& attributeSelector : simpleSelector.attributeSelectors) { + specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100; + } + } + + m_rules.emplace(selector, rule.declarations, specificity, m_position); + } + + m_position += 1; +} + +std::vector StyleSheet::match(const Element *element) const +{ + std::vector declarations; + for(auto& rule : m_rules) { + if(!rule.match(element)) + continue; + declarations.push_back(&rule.properties()); + } + + return declarations; +} + +#define IS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_' || (c) == ':') +#define IS_NAMECHAR(c) (IS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-' || (c) == '.') +static inline bool readIdentifier(const char*& ptr, const char* end, std::string& value) +{ + if(ptr >= end || !IS_STARTNAMECHAR(*ptr)) + return false; + + auto start = ptr; + ++ptr; + while(ptr < end && IS_NAMECHAR(*ptr)) + ++ptr; + + value.assign(start, ptr); + return true; +} + +#define IS_CSS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_') +#define IS_CSS_NAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-') +static inline bool readCSSIdentifier(const char*& ptr, const char* end, std::string& value) +{ + if(ptr >= end || !IS_CSS_STARTNAMECHAR(*ptr)) + return false; + + auto start = ptr; + ++ptr; + while(ptr < end && IS_CSS_NAMECHAR(*ptr)) + ++ptr; + + value.assign(start, ptr); + return true; +} + +bool CSSParser::parseSheet(StyleSheet* sheet, const std::string& value) +{ + auto ptr = value.data(); + auto end = ptr + value.size(); + + Rule rule; + while(ptr < end) + { + Utils::skipWs(ptr, end); + if(Utils::skipDesc(ptr, end, '@')) + { + if(!parseAtRule(ptr, end)) + return false; + continue; + } + + if(!parseRule(ptr, end, rule)) + return false; + sheet->add(rule); + } + + return true; +} + +bool CSSParser::parseAtRule(const char*& ptr, const char* end) +{ + int depth = 0; + while(ptr < end) + { + auto ch = *ptr; + ++ptr; + if(ch == ';' && depth == 0) + break; + if(ch == '{') ++depth; + else if(ch == '}' && depth > 0) + { + if(depth == 1) + break; + --depth; + } + } + + return true; +} + +bool CSSParser::parseRule(const char*& ptr, const char* end, Rule& rule) +{ + if(!parseSelectors(ptr, end, rule.selectors)) + return false; + + if(!parseDeclarations(ptr, end, rule.declarations)) + return false; + + return true; +} + +bool CSSParser::parseSelectors(const char*& ptr, const char* end, SelectorList& selectors) +{ + selectors.clear(); + Selector selector; + if(!parseSelector(ptr, end, selector)) + return false; + selectors.push_back(selector); + + while(Utils::skipDesc(ptr, end, ',')) + { + Utils::skipWs(ptr, end); + Selector selector; + if(!parseSelector(ptr, end, selector)) + return false; + selectors.push_back(selector); + } + + return true; +} + +bool CSSParser::parseDeclarations(const char*& ptr, const char* end, PropertyList& declarations) +{ + declarations.clear(); + if(!Utils::skipDesc(ptr, end, '{')) + return false; + + std::string name; + std::string value; + Utils::skipWs(ptr, end); + do { + if(!readCSSIdentifier(ptr, end, name)) + return false; + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ':')) + return false; + Utils::skipWs(ptr, end); + auto start = ptr; + while(ptr < end && !(*ptr == '!' || *ptr == ';' || *ptr == '}')) + ++ptr; + value.assign(start, Utils::rtrim(start, ptr)); + int specificity = 0x10; + if(Utils::skipDesc(ptr, end, '!')) + { + if(!Utils::skipDesc(ptr, end, "important")) + return false; + specificity = 0x1000; + } + + auto id = cssPropertyId(name); + if(id != PropertyID::Unknown) + declarations.set(id, value, specificity); + Utils::skipWsDelimiter(ptr, end, ';'); + } while(ptr < end && *ptr != '}'); + + return Utils::skipDesc(ptr, end, '}'); +} + +#define IS_SELECTOR_STARTNAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || (c) == '*' || (c) == '#' || (c) == '.' || (c) == '[' || (c) == ':') +bool CSSParser::parseSelector(const char*& ptr, const char* end, Selector& selector) +{ + do { + SimpleSelector simpleSelector; + if(!parseSimpleSelector(ptr, end, simpleSelector)) + return false; + selector.push_back(simpleSelector); + Utils::skipWs(ptr, end); + } while(ptr < end && IS_SELECTOR_STARTNAMECHAR(*ptr)); + + return true; +} + +bool CSSParser::parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector) +{ + std::string name; + if(Utils::skipDesc(ptr, end, '*')) + simpleSelector.id = ElementID::Star; + else if(readCSSIdentifier(ptr, end, name)) + simpleSelector.id = elementId(name); + + while(ptr < end) + { + if(Utils::skipDesc(ptr, end, '#')) + { + AttributeSelector a; + a.id = PropertyID::Id; + a.matchType = AttributeSelector::MatchType::Equal; + if(!readCSSIdentifier(ptr, end, a.value)) + return false; + simpleSelector.attributeSelectors.push_back(a); + continue; + } + + if(Utils::skipDesc(ptr, end, '.')) + { + AttributeSelector a; + a.id = PropertyID::Class; + a.matchType = AttributeSelector::MatchType::Includes; + if(!readCSSIdentifier(ptr, end, a.value)) + return false; + simpleSelector.attributeSelectors.push_back(a); + continue; + } + + if(Utils::skipDesc(ptr, end, '[')) + { + Utils::skipWs(ptr, end); + if(!readCSSIdentifier(ptr, end, name)) + return false; + AttributeSelector a; + a.id = propertyId(name); + if(Utils::skipDesc(ptr, end, '=')) + a.matchType = AttributeSelector::MatchType::Equal; + else if(Utils::skipDesc(ptr, end, "~=")) + a.matchType = AttributeSelector::MatchType::Includes; + else if(Utils::skipDesc(ptr, end, "|=")) + a.matchType = AttributeSelector::MatchType::DashMatch; + else if(Utils::skipDesc(ptr, end, "^=")) + a.matchType = AttributeSelector::MatchType::StartsWith; + else if(Utils::skipDesc(ptr, end, "$=")) + a.matchType = AttributeSelector::MatchType::EndsWith; + else if(Utils::skipDesc(ptr, end, "*=")) + a.matchType = AttributeSelector::MatchType::Contains; + if(a.matchType != AttributeSelector::MatchType::None) + { + Utils::skipWs(ptr, end); + if(!readCSSIdentifier(ptr, end, a.value)) + { + if(ptr >= end || !(*ptr == '\"' || *ptr == '\'')) + return false; + + auto quote = *ptr; + ++ptr; + if(!Utils::readUntil(ptr, end, quote, a.value)) + return false; + ++ptr; + } + } + + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ']')) + return false; + simpleSelector.attributeSelectors.push_back(a); + continue; + } + + if(Utils::skipDesc(ptr, end, ':')) + { + if(!readCSSIdentifier(ptr, end, name)) + return false; + PseudoClassSelector selector; + if(name.compare("empty") == 0) + selector.type = PseudoClassSelector::Type::Empty; + else if(name.compare("root") == 0) + selector.type = PseudoClassSelector::Type::Root; + else if(name.compare("not") == 0) + selector.type = PseudoClassSelector::Type::Not; + else if(name.compare("first-child") == 0) + selector.type = PseudoClassSelector::Type::FirstChild; + else if(name.compare("last-child") == 0) + selector.type = PseudoClassSelector::Type::LastChild; + else if(name.compare("only-child") == 0) + selector.type = PseudoClassSelector::Type::OnlyChild; + else if(name.compare("first-of-type") == 0) + selector.type = PseudoClassSelector::Type::FirstOfType; + else if(name.compare("last-of-type") == 0) + selector.type = PseudoClassSelector::Type::LastOfType; + else if(name.compare("only-of-type") == 0) + selector.type = PseudoClassSelector::Type::OnlyOfType; + if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) + { + if(!Utils::skipDesc(ptr, end, '(')) + return false; + + Utils::skipWs(ptr, end); + if(!parseSelectors(ptr, end, selector.subSelectors)) + return false; + + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ')')) + return false; + } + + simpleSelector.pseudoClassSelectors.push_back(selector); + continue; + } + + break; + } + + Utils::skipWs(ptr, end); + if(Utils::skipDesc(ptr, end, '>')) + simpleSelector.combinator = SimpleSelector::Combinator::Child; + else if(Utils::skipDesc(ptr, end, '+')) + simpleSelector.combinator = SimpleSelector::Combinator::DirectAdjacent; + else if(Utils::skipDesc(ptr, end, '~')) + simpleSelector.combinator = SimpleSelector::Combinator::InDirectAdjacent; + + return true; +} + +static inline std::unique_ptr createElement(ElementID id) { switch(id) { - case ElementId::Svg: + case ElementID::Svg: return std::make_unique(); - case ElementId::Path: + case ElementID::Path: return std::make_unique(); - case ElementId::G: + case ElementID::G: return std::make_unique(); - case ElementId::Rect: + case ElementID::Rect: return std::make_unique(); - case ElementId::Circle: + case ElementID::Circle: return std::make_unique(); - case ElementId::Ellipse: + case ElementID::Ellipse: return std::make_unique(); - case ElementId::Line: + case ElementID::Line: return std::make_unique(); - case ElementId::Defs: + case ElementID::Defs: return std::make_unique(); - case ElementId::Polygon: + case ElementID::Polygon: return std::make_unique(); - case ElementId::Polyline: + case ElementID::Polyline: return std::make_unique(); - case ElementId::Stop: + case ElementID::Stop: return std::make_unique(); - case ElementId::LinearGradient: + case ElementID::LinearGradient: return std::make_unique(); - case ElementId::RadialGradient: + case ElementID::RadialGradient: return std::make_unique(); - case ElementId::Symbol: + case ElementID::Symbol: return std::make_unique(); - case ElementId::Use: + case ElementID::Use: return std::make_unique(); - case ElementId::Pattern: + case ElementID::Pattern: return std::make_unique(); - case ElementId::Mask: + case ElementID::Mask: return std::make_unique(); - case ElementId::ClipPath: + case ElementID::ClipPath: return std::make_unique(); - case ElementId::SolidColor: + case ElementID::SolidColor: return std::make_unique(); - case ElementId::Marker: + case ElementID::Marker: return std::make_unique(); - case ElementId::Style: + case ElementID::Style: return std::make_unique(); default: break; @@ -1790,26 +1816,22 @@ static inline void parseStyle(const std::string& string, Element* element) ++ptr; value.assign(start, Utils::rtrim(start, ptr)); auto id = cssPropertyId(name); - if(id != PropertyId::Unknown) + if(id != PropertyID::Unknown) element->set(id, value, 0x100); Utils::skipWsDelimiter(ptr, end, ';'); } } -ParseDocument::ParseDocument() -{ -} +TreeBuilder::TreeBuilder() = default; -ParseDocument::~ParseDocument() -{ -} +TreeBuilder::~TreeBuilder() = default; -bool ParseDocument::parse(const char* data, std::size_t size) +bool TreeBuilder::parse(const char* data, std::size_t size) { auto ptr = data; auto end = ptr + size; - CSSParser cssparser; + StyleSheet styleSheet; Element* current = nullptr; std::string name; std::string value; @@ -1825,7 +1847,7 @@ bool ParseDocument::parse(const char* data, std::size_t size) }; auto handle_text = [&](const char* start, const char* end, bool in_cdata) { - if(ignoring > 0 || current == nullptr || current->id != ElementId::Style) + if(ignoring > 0 || current == nullptr || current->id != ElementID::Style) return; if(in_cdata) @@ -1834,7 +1856,7 @@ bool ParseDocument::parse(const char* data, std::size_t size) decodeText(start, end, value); remove_comments(value); - cssparser.parseMore(value); + styleSheet.parse(value); }; while(ptr < end) @@ -1940,8 +1962,8 @@ bool ParseDocument::parse(const char* data, std::size_t size) if(!readIdentifier(ptr, end, name)) return false; - auto id = ignoring == 0 ? elementId(name) : ElementId::Unknown; - if(id == ElementId::Unknown) + auto id = ignoring == 0 ? elementId(name) : ElementID::Unknown; + if(id == ElementID::Unknown) ++ignoring; Element* element = nullptr; @@ -1952,7 +1974,7 @@ bool ParseDocument::parse(const char* data, std::size_t size) if(m_rootElement == nullptr) { - if(id != ElementId::Svg) + if(id != ElementID::Svg) return false; m_rootElement = std::make_unique(); @@ -1988,18 +2010,18 @@ bool ParseDocument::parse(const char* data, std::size_t size) if(ptr >= end || *ptr != quote) return false; - auto id = element ? propertyId(name) : PropertyId::Unknown; - if(id != PropertyId::Unknown) + auto id = element ? propertyId(name) : PropertyID::Unknown; + if(id != PropertyID::Unknown) { decodeText(start, Utils::rtrim(start, ptr), value); - if(id == PropertyId::Style) + if(id == PropertyID::Style) { remove_comments(value); parseStyle(value, element); } else { - if(id == PropertyId::Id) + if(id == PropertyID::Id) m_idCache.emplace(value, element); element->set(id, value, 0x1); } @@ -2037,16 +2059,14 @@ bool ParseDocument::parse(const char* data, std::size_t size) if(!m_rootElement || ptr < end || ignoring > 0) return false; - const auto& rules = cssparser.rules(); - if(!rules.empty()) + if(!styleSheet.empty()) { - RuleMatchContext context(rules); - m_rootElement->transverse([&context](Node* node) { + m_rootElement->transverse([&styleSheet](Node* node) { if(node->isText()) return false; auto element = static_cast(node); - auto declarations = context.match(element); + auto declarations = styleSheet.match(element); for(auto& declaration : declarations) element->properties.add(*declaration); return false; @@ -2056,7 +2076,7 @@ bool ParseDocument::parse(const char* data, std::size_t size) return true; } -Element* ParseDocument::getElementById(const std::string& id) const +Element* TreeBuilder::getElementById(const std::string& id) const { auto it = m_idCache.find(id); if(it == m_idCache.end()) @@ -2065,9 +2085,9 @@ Element* ParseDocument::getElementById(const std::string& id) const return it->second; } -std::unique_ptr ParseDocument::layout() const +std::unique_ptr TreeBuilder::build() const { - return m_rootElement->layoutDocument(this); + return m_rootElement->build(this); } } // namespace lunasvg diff --git a/source/parser.h b/source/parser.h index 6ced83e16..1f0e5e813 100644 --- a/source/parser.h +++ b/source/parser.h @@ -2,6 +2,7 @@ #define PARSER_H #include +#include #include "property.h" #include "element.h" @@ -63,7 +64,10 @@ private: static bool parseTransform(const char*& ptr, const char* end, TransformType& type, double* values, int& count); }; -struct Selector; +struct SimpleSelector; + +using Selector = std::vector; +using SelectorList = std::vector; struct AttributeSelector { @@ -78,20 +82,19 @@ struct AttributeSelector Contains }; - PropertyId id{PropertyId::Unknown}; - std::string value; MatchType matchType{MatchType::None}; + PropertyID id{PropertyID::Unknown}; + std::string value; }; -using SelectorList = std::vector; - -struct PseudoClass +struct PseudoClassSelector { enum class Type { Unknown, Empty, Root, + Is, Not, FirstChild, LastChild, @@ -102,7 +105,9 @@ struct PseudoClass }; Type type{Type::Unknown}; - SelectorList notSelectors; + int16_t a{0}; + int16_t b{0}; + SelectorList subSelectors; }; struct SimpleSelector @@ -115,16 +120,10 @@ struct SimpleSelector InDirectAdjacent }; - ElementId id{ElementId::Star}; - std::vector attributeSelectors; - std::vector pseudoClasses; Combinator combinator{Combinator::Descendant}; -}; - -struct Selector -{ - std::vector simpleSelectors; - int specificity{0}; + ElementID id{ElementID::Star}; + std::vector attributeSelectors; + std::vector pseudoClassSelectors; }; struct Rule @@ -133,21 +132,47 @@ struct Rule PropertyList declarations; }; -class RuleMatchContext -{ +class RuleData { public: - RuleMatchContext(const std::vector& rules); + RuleData(const Selector& selector, const PropertyList& properties, uint32_t specificity, uint32_t position) + : m_selector(selector), m_properties(properties), m_specificity(specificity), m_position(position) + {} + + const Selector& selector() const { return m_selector; } + const PropertyList& properties() const { return m_properties; } + const uint32_t& specificity() const { return m_specificity; } + const uint32_t& position() const { return m_position; } + + bool match(const Element* element) const; + +private: + bool matchSimpleSelector(const SimpleSelector& selector, const Element* element) const; + bool matchAttributeSelector(const AttributeSelector& selector, const Element* element) const; + bool matchPseudoClassSelector(const PseudoClassSelector& selector, const Element* element) const; + +private: + Selector m_selector; + PropertyList m_properties; + uint32_t m_specificity; + uint32_t m_position; +}; + +inline bool operator<(const RuleData& a, const RuleData& b) { return std::tie(a.specificity(), a.position()) < std::tie(b.specificity(), b.position()); } +inline bool operator>(const RuleData& a, const RuleData& b) { return std::tie(a.specificity(), a.position()) > std::tie(b.specificity(), b.position()); } + +class StyleSheet { +public: + StyleSheet() = default; + + void parse(const std::string& content); + void add(const Rule& rule); + bool empty() const { return m_position == 0; } std::vector match(const Element* element) const; private: - bool selectorMatch(const Selector* selector, const Element* element) const; - bool simpleSelectorMatch(const SimpleSelector& selector, const Element* element) const; - bool attributeSelectorMatch(const AttributeSelector& selector, const Element* element) const; - bool pseudoClassMatch(const PseudoClass& pseudo, const Element* element) const; - -private: - std::multimap, std::less> m_selectors; + std::multiset m_rules; + uint32_t m_position{0}; }; class CSSParser @@ -155,35 +180,29 @@ class CSSParser public: CSSParser() = default; - bool parseMore(const std::string& value); - - const std::vector& rules() const { return m_rules; } + static bool parseSheet(StyleSheet* sheet, const std::string& value); private: - bool parseAtRule(const char*& ptr, const char* end) const; - bool parseRule(const char*& ptr, const char* end, Rule& rule) const; - bool parseSelectors(const char*& ptr, const char* end, SelectorList& selectors) const; - bool parseDeclarations(const char*& ptr, const char* end, PropertyList& declarations) const; - bool parseSelector(const char*& ptr, const char* end, Selector& selector) const; - bool parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector) const; - -private: - std::vector m_rules; + static bool parseAtRule(const char*& ptr, const char* end); + static bool parseRule(const char*& ptr, const char* end, Rule& rule); + static bool parseSelectors(const char*& ptr, const char* end, SelectorList& selectors); + static bool parseDeclarations(const char*& ptr, const char* end, PropertyList& declarations); + static bool parseSelector(const char*& ptr, const char* end, Selector& selector); + static bool parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector); }; class LayoutSymbol; -class ParseDocument -{ +class TreeBuilder { public: - ParseDocument(); - ~ParseDocument(); + TreeBuilder(); + ~TreeBuilder(); bool parse(const char* data, std::size_t size); SVGElement* rootElement() const { return m_rootElement.get(); } Element* getElementById(const std::string& id) const; - std::unique_ptr layout() const; + std::unique_ptr build() const; private: std::unique_ptr m_rootElement; diff --git a/source/parserutils.h b/source/parserutils.h index e83746d6d..90fd3dcae 100644 --- a/source/parserutils.h +++ b/source/parserutils.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace lunasvg { @@ -262,6 +264,241 @@ inline bool parseNumber(const char*& ptr, const char* end, T& number) } // namespace Utils +class ParserString { +public: + explicit ParserString(const std::string_view& value) + : ParserString(value.data(), value.length()) + {} + + ParserString(const char* begin, size_t length) + : ParserString(begin, begin + length) + {} + + ParserString(const char* begin, const char* end) + : ParserString(begin, begin, end) + {} + + ParserString(const char* current, const char* begin, const char* end) + : m_current(current), m_begin(begin), m_end(end) + {} + + ParserString operator+(size_t count) const { + auto current = m_current + count; + assert(m_end >= current); + return ParserString(current, m_begin, m_end); + } + + ParserString operator-(size_t count) const { + auto current = m_current - count; + assert(current >= m_begin); + return ParserString(current, m_begin, m_end); + } + + ParserString& operator+=(size_t count) { + *this = *this + count; + return *this; + } + + ParserString& operator-=(size_t count) { + *this = *this - count; + return *this; + } + + const char& operator*() const { + assert(m_current < m_end); + return *m_current; + } + + char peek(size_t count = 0) const { + auto current = m_current + count; + assert(m_end >= current); + if(current == m_end) + return 0; + return *current; + } + + char advance(size_t count = 1) { + m_current += count; + assert(m_end >= m_current); + if(m_current == m_end) + return 0; + return *m_current; + } + + char get() const { + assert(m_end >= m_current); + if(m_current == m_end) + return 0; + return *m_current; + } + + std::string_view string(size_t offset, size_t count) const { return string().substr(offset, count); } + std::string_view substring(size_t offset, size_t count) const { return substring().substr(offset, count); } + + std::string_view string() const { return std::string_view(m_begin, length()); } + std::string_view substring() const { return std::string_view(m_current, sublength()); } + + size_t offset() const { return m_current - m_begin; } + size_t length() const { return m_end - m_begin; } + size_t sublength() const { return m_end - m_current; } + + const char* current() const { return m_current; } + const char* begin() const { return m_begin; } + const char* end() const { return m_end; } + + bool empty() const { return m_current == m_end; } + +private: + const char* m_current; + const char* m_begin; + const char* m_end; +}; + +constexpr bool isspace(int cc) { return (cc == ' ' || cc == '\n' || cc == '\t' || cc == '\r' || cc == '\f'); } +constexpr bool isdigit(int cc) { return (cc >= '0' && cc <= '9'); } +constexpr bool isupper(int cc) { return (cc >= 'A' && cc <= 'Z'); } +constexpr bool islower(int cc) { return (cc >= 'a' && cc <= 'z'); } +constexpr bool isalpha(int cc) { return isupper(cc) || islower(cc); } + +constexpr bool isxupper(int cc) { return (cc >= 'A' && cc <= 'F'); } +constexpr bool isxlower(int cc) { return (cc >= 'a' && cc <= 'f'); } +constexpr bool isxdigit(int cc) { return isdigit(cc) || isxupper(cc) || isxlower(cc); } + +constexpr int xdigit(int cc) { + if(isdigit(cc)) + return cc - '0'; + if(isxupper(cc)) + return 10 + cc - 'A'; + if(isxlower(cc)) + return 10 + cc - 'a'; + return 0; +} + +constexpr char tolower(int cc) { + if(isupper(cc)) + return cc + 0x20; + return cc; +} + +constexpr bool equals(int a, int b, bool caseSensitive) { + if(caseSensitive) + return a == b; + return tolower(a) == tolower(b); +} + +constexpr bool equals(const char* aData, size_t aLength, const char* bData, size_t bLength, bool caseSensitive) { + if(aLength != bLength) + return false; + + auto aEnd = aData + aLength; + while(aData != aEnd) { + if(!equals(*aData, *bData, caseSensitive)) + return false; + ++aData; + ++bData; + } + + return true; +} + +constexpr bool equals(const std::string_view& a, const std::string_view& b, bool caseSensitive) { + return equals(a.data(), a.length(), b.data(), b.length(), caseSensitive); +} + +constexpr bool contains(const std::string_view& value, const std::string_view& subvalue, bool caseSensitive) { + if(subvalue.empty() || subvalue.length() > value.length()) + return false; + auto it = value.data(); + auto end = it + value.length(); + while(it < end) { + size_t count = 0; + do { + if(!equals(*it, subvalue[count], caseSensitive)) + break; + ++count; + ++it; + } while(it < end && count < subvalue.length()); + if(count == subvalue.length()) + return true; + ++it; + } + + return false; +} + +constexpr bool includes(const std::string_view& value, const std::string_view& subvalue, bool caseSensitive) { + if(subvalue.empty() || subvalue.length() > value.length()) + return false; + auto it = value.data(); + auto end = it + value.length(); + while(true) { + while(it < end && isspace(*it)) + ++it; + if(it >= end) + return false; + size_t count = 0; + auto begin = it; + do { + ++count; + ++it; + } while(it < end && !isspace(*it)); + if(equals(begin, count, subvalue.data(), subvalue.length(), caseSensitive)) + return true; + ++it; + } + + return false; +} + +constexpr bool startswith(const std::string_view& value, const std::string_view& subvalue, bool caseSensitive) { + if(subvalue.empty() || subvalue.length() > value.length()) + return false; + return equals(value.substr(0, subvalue.size()), subvalue, caseSensitive); +} + +constexpr bool endswith(const std::string_view& value, const std::string_view& subvalue, bool caseSensitive) { + if(subvalue.empty() || subvalue.length() > value.length()) + return false; + return equals(value.substr(value.size() - subvalue.size(), subvalue.size()), subvalue, caseSensitive); +} + +constexpr bool dashequals(const std::string_view& value, const std::string_view& subvalue, bool caseSensitive) { + if(!startswith(value, subvalue, caseSensitive)) + return false; + return (value.length() == subvalue.length() || value.at(subvalue.length()) == '-'); +} + +inline void appendCodepoint(std::string& output, uint32_t cp) { + char c[5] = {0, 0, 0, 0, 0}; + if(cp < 0x80) { + c[1] = 0; + c[0] = cp; + } else if(cp < 0x800) { + c[2] = 0; + c[1] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[0] = cp | 0xC0; + } else if(cp < 0x10000) { + c[3] = 0; + c[2] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[1] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[0] = cp | 0xE0; + } else if(cp < 0x110000) { + c[4] = 0; + c[3] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[2] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[1] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[0] = cp | 0xF0; + } + + output.append(c); +} + } // namespace lunasvg #endif // PARSERUTILS_H diff --git a/source/pointer.h b/source/pointer.h new file mode 100644 index 000000000..9568f7e33 --- /dev/null +++ b/source/pointer.h @@ -0,0 +1,254 @@ +#ifndef POINTER_H +#define POINTER_H + +#include +#include + +namespace lunasvg { + +template +class RefCounted { +public: + RefCounted() = default; + + void ref() { ++m_refCount; } + void deref() { + if(--m_refCount == 0) { + delete static_cast(this); + } + } + + uint32_t refCount() const { return m_refCount; } + bool hasOneRefCount() const { return m_refCount == 1; } + +private: + uint32_t m_refCount{1}; +}; + +template +inline void refIfNotNull(T* ptr) +{ + if(ptr) + ptr->ref(); +} + +template +inline void derefIfNotNull(T* ptr) +{ + if(ptr) + ptr->deref(); +} + +template class RefPtr; +template RefPtr adoptPtr(T*); + +template +class RefPtr { +public: + RefPtr() = default; + RefPtr(std::nullptr_t) : m_ptr(nullptr) {} + RefPtr(T* ptr) : m_ptr(ptr) { refIfNotNull(m_ptr); } + RefPtr(T& ref) : m_ptr(&ref) { m_ptr->ref(); } + RefPtr(const RefPtr& p) : m_ptr(p.get()) { refIfNotNull(m_ptr); } + RefPtr(RefPtr&& p) : m_ptr(p.release()) {} + + template + RefPtr(const RefPtr& p) : m_ptr(p.get()) { refIfNotNull(m_ptr); } + + template + RefPtr(RefPtr&& p) : m_ptr(p.release()) {} + + ~RefPtr() { derefIfNotNull(m_ptr); } + + T* get() const { return m_ptr; } + T& operator*() const { return *m_ptr; } + T* operator->() const { return m_ptr; } + + bool empty() const { return !m_ptr; } + bool operator!() const { return !m_ptr; } + operator bool() const { return !!m_ptr; } + + RefPtr& operator=(std::nullptr_t) + { + clear(); + return *this; + } + + RefPtr& operator=(T* o) + { + RefPtr p = o; + swap(p); + return *this; + } + + RefPtr& operator=(T& o) + { + RefPtr p = o; + swap(p); + return *this; + } + + RefPtr& operator=(const RefPtr& o) + { + RefPtr p = o; + swap(p); + return *this; + } + + RefPtr& operator=(RefPtr&& o) + { + RefPtr p = std::move(o); + swap(p); + return *this; + } + + template + RefPtr& operator=(const RefPtr& o) + { + RefPtr p = o; + swap(p); + return *this; + } + + template + RefPtr& operator=(RefPtr&& o) + { + RefPtr p = std::move(o); + swap(p); + return *this; + } + + void swap(RefPtr& o) + { + std::swap(m_ptr, o.m_ptr); + } + + T* release() + { + T* ptr = m_ptr; + m_ptr = nullptr; + return ptr; + } + + void clear() + { + derefIfNotNull(m_ptr); + m_ptr = nullptr; + } + + friend RefPtr adoptPtr(T*); + +private: + RefPtr(T* ptr, std::nullptr_t) : m_ptr(ptr) {} + T* m_ptr{nullptr}; +}; + +template +inline RefPtr adoptPtr(T* ptr) +{ + return RefPtr(ptr, nullptr); +} + +template +inline void swap(RefPtr& a, RefPtr& b) +{ + a.swap(b); +} + +template +inline bool operator==(const RefPtr& a, const RefPtr& b) +{ + return a.get() == b.get(); +} + +template +inline bool operator==(const RefPtr& a, const U* b) +{ + return a.get() == b; +} + +template +inline bool operator==(const T* a, const RefPtr& b) +{ + return a == b.get(); +} + +template +inline bool operator==(const RefPtr& a, std::nullptr_t) +{ + return a.get() == nullptr; +} + +template +inline bool operator!=(const RefPtr& a, const RefPtr& b) +{ + return a.get() != b.get(); +} + +template +inline bool operator!=(const RefPtr& a, const U* b) +{ + return a.get() != b; +} + +template +inline bool operator!=(const T* a, const RefPtr& b) +{ + return a != b.get(); +} + +template +inline bool operator!=(const RefPtr& a, std::nullptr_t) +{ + return a.get() != nullptr; +} + +template +struct is { + template + static bool check(const U& value); +}; + +template +constexpr bool is_a(U& value) { + return is::check(value); +} + +template +constexpr bool is_a(const U& value) { + return is::check(value); +} + +template +constexpr bool is_a(U* value) { + return value && is::check(*value); +} + +template +constexpr bool is_a(const U* value) { + return value && is::check(*value); +} + +template +constexpr T* to(U& value) { + return is_a(value) ? static_cast(&value) : nullptr; +} + +template +constexpr const T* to(const U& value) { + return is_a(value) ? static_cast(&value) : nullptr; +} + +template +constexpr T* to(U* value) { + return is_a(value) ? static_cast(value) : nullptr; +} + +template +constexpr const T* to(const U* value) { + return is_a(value) ? static_cast(value) : nullptr; +} + +} // namespace lunasvg + +#endif // POINTER_H diff --git a/source/stopelement.cpp b/source/stopelement.cpp index 453138356..d36ff7fe8 100644 --- a/source/stopelement.cpp +++ b/source/stopelement.cpp @@ -4,13 +4,13 @@ namespace lunasvg { StopElement::StopElement() - : StyledElement(ElementId::Stop) + : StyledElement(ElementID::Stop) { } double StopElement::offset() const { - auto& value = get(PropertyId::Offset); + auto& value = get(PropertyID::Offset); return Parser::parseNumberPercentage(value, 0.0); } diff --git a/source/styledelement.cpp b/source/styledelement.cpp index bfb5ad8f0..fda779d87 100644 --- a/source/styledelement.cpp +++ b/source/styledelement.cpp @@ -3,164 +3,164 @@ namespace lunasvg { -StyledElement::StyledElement(ElementId id) +StyledElement::StyledElement(ElementID id) : Element(id) { } Paint StyledElement::fill() const { - auto& value = find(PropertyId::Fill); + auto& value = find(PropertyID::Fill); return Parser::parsePaint(value, this, Color::Black); } Paint StyledElement::stroke() const { - auto& value = find(PropertyId::Stroke); + auto& value = find(PropertyID::Stroke); return Parser::parsePaint(value, this, Color::Transparent); } Color StyledElement::color() const { - auto& value = find(PropertyId::Color); + auto& value = find(PropertyID::Color); return Parser::parseColor(value, this, Color::Black); } Color StyledElement::stop_color() const { - auto& value = find(PropertyId::Stop_Color); + auto& value = find(PropertyID::Stop_Color); return Parser::parseColor(value, this, Color::Black); } Color StyledElement::solid_color() const { - auto& value = find(PropertyId::Solid_Color); + auto& value = find(PropertyID::Solid_Color); return Parser::parseColor(value, this, Color::Black); } double StyledElement::opacity() const { - auto& value = get(PropertyId::Opacity); + auto& value = get(PropertyID::Opacity); return Parser::parseNumberPercentage(value, 1.0); } double StyledElement::fill_opacity() const { - auto& value = find(PropertyId::Fill_Opacity); + auto& value = find(PropertyID::Fill_Opacity); return Parser::parseNumberPercentage(value, 1.0); } double StyledElement::stroke_opacity() const { - auto& value = find(PropertyId::Stroke_Opacity); + auto& value = find(PropertyID::Stroke_Opacity); return Parser::parseNumberPercentage(value, 1.0); } double StyledElement::stop_opacity() const { - auto& value = find(PropertyId::Stop_Opacity); + auto& value = find(PropertyID::Stop_Opacity); return Parser::parseNumberPercentage(value, 1.0); } double StyledElement::solid_opacity() const { - auto& value = find(PropertyId::Solid_Opacity); + auto& value = find(PropertyID::Solid_Opacity); return Parser::parseNumberPercentage(value, 1.0); } double StyledElement::stroke_miterlimit() const { - auto& value = find(PropertyId::Stroke_Miterlimit); + auto& value = find(PropertyID::Stroke_Miterlimit); return Parser::parseNumber(value, 4.0); } Length StyledElement::stroke_width() const { - auto& value = find(PropertyId::Stroke_Width); + auto& value = find(PropertyID::Stroke_Width); return Parser::parseLength(value, ForbidNegativeLengths, Length::One); } Length StyledElement::stroke_dashoffset() const { - auto& value = find(PropertyId::Stroke_Dashoffset); + auto& value = find(PropertyID::Stroke_Dashoffset); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } LengthList StyledElement::stroke_dasharray() const { - auto& value = find(PropertyId::Stroke_Dasharray); + auto& value = find(PropertyID::Stroke_Dasharray); return Parser::parseLengthList(value, ForbidNegativeLengths); } WindRule StyledElement::fill_rule() const { - auto& value = find(PropertyId::Fill_Rule); + auto& value = find(PropertyID::Fill_Rule); return Parser::parseWindRule(value); } WindRule StyledElement::clip_rule() const { - auto& value = find(PropertyId::Clip_Rule); + auto& value = find(PropertyID::Clip_Rule); return Parser::parseWindRule(value); } LineCap StyledElement::stroke_linecap() const { - auto& value = find(PropertyId::Stroke_Linecap); + auto& value = find(PropertyID::Stroke_Linecap); return Parser::parseLineCap(value); } LineJoin StyledElement::stroke_linejoin() const { - auto& value = find(PropertyId::Stroke_Linejoin); + auto& value = find(PropertyID::Stroke_Linejoin); return Parser::parseLineJoin(value); } Display StyledElement::display() const { - auto& value = get(PropertyId::Display); + auto& value = get(PropertyID::Display); return Parser::parseDisplay(value); } Visibility StyledElement::visibility() const { - auto& value = find(PropertyId::Visibility); + auto& value = find(PropertyID::Visibility); return Parser::parseVisibility(value); } Overflow StyledElement::overflow() const { - auto& value = get(PropertyId::Overflow); + auto& value = get(PropertyID::Overflow); return Parser::parseOverflow(value, parent == nullptr ? Overflow::Visible : Overflow::Hidden); } std::string StyledElement::clip_path() const { - auto& value = get(PropertyId::Clip_Path); + auto& value = get(PropertyID::Clip_Path); return Parser::parseUrl(value); } std::string StyledElement::mask() const { - auto& value = get(PropertyId::Mask); + auto& value = get(PropertyID::Mask); return Parser::parseUrl(value); } std::string StyledElement::marker_start() const { - auto& value = find(PropertyId::Marker_Start); + auto& value = find(PropertyID::Marker_Start); return Parser::parseUrl(value); } std::string StyledElement::marker_mid() const { - auto& value = find(PropertyId::Marker_Mid); + auto& value = find(PropertyID::Marker_Mid); return Parser::parseUrl(value); } std::string StyledElement::marker_end() const { - auto& value = find(PropertyId::Marker_End); + auto& value = find(PropertyID::Marker_End); return Parser::parseUrl(value); } diff --git a/source/styledelement.h b/source/styledelement.h index 1257548ad..4d0ceb0a0 100644 --- a/source/styledelement.h +++ b/source/styledelement.h @@ -8,7 +8,7 @@ namespace lunasvg { class StyledElement : public Element { public: - StyledElement(ElementId id); + StyledElement(ElementID id); Paint fill() const; Paint stroke() const; diff --git a/source/styleelement.cpp b/source/styleelement.cpp index 800146cf5..3b1de5765 100644 --- a/source/styleelement.cpp +++ b/source/styleelement.cpp @@ -3,7 +3,7 @@ namespace lunasvg { StyleElement::StyleElement() - : Element(ElementId::Style) + : Element(ElementID::Style) { } diff --git a/source/svgelement.cpp b/source/svgelement.cpp index 1468045c8..e4b1a1d82 100644 --- a/source/svgelement.cpp +++ b/source/svgelement.cpp @@ -5,47 +5,47 @@ namespace lunasvg { SVGElement::SVGElement() - : GraphicsElement(ElementId::Svg) + : GraphicsElement(ElementID::Svg) { } Length SVGElement::x() const { - auto& value = get(PropertyId::X); + auto& value = get(PropertyID::X); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length SVGElement::y() const { - auto& value = get(PropertyId::Y); + auto& value = get(PropertyID::Y); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length SVGElement::width() const { - auto& value = get(PropertyId::Width); + auto& value = get(PropertyID::Width); return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); } Length SVGElement::height() const { - auto& value = get(PropertyId::Height); + auto& value = get(PropertyID::Height); return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); } Rect SVGElement::viewBox() const { - auto& value = get(PropertyId::ViewBox); + auto& value = get(PropertyID::ViewBox); return Parser::parseViewBox(value); } PreserveAspectRatio SVGElement::preserveAspectRatio() const { - auto& value = get(PropertyId::PreserveAspectRatio); + auto& value = get(PropertyID::PreserveAspectRatio); return Parser::parsePreserveAspectRatio(value); } -std::unique_ptr SVGElement::layoutDocument(const ParseDocument* document) const +std::unique_ptr SVGElement::build(const TreeBuilder* builder) const { if(isDisplayNone()) return nullptr; @@ -73,7 +73,7 @@ std::unique_ptr SVGElement::layoutDocument(const ParseDocument* do root->clip = isOverflowHidden() ? preserveAspectRatio.getClip(_w, _h, viewBox) : Rect::Invalid; root->opacity = opacity(); - LayoutContext context(document, root.get()); + LayoutContext context(builder, root.get()); root->masker = context.getMasker(mask()); root->clipper = context.getClipper(clip_path()); layoutChildren(&context, root.get()); diff --git a/source/svgelement.h b/source/svgelement.h index b6e62343e..789a7cad4 100644 --- a/source/svgelement.h +++ b/source/svgelement.h @@ -5,7 +5,7 @@ namespace lunasvg { -class ParseDocument; +class TreeBuilder; class LayoutSymbol; class SVGElement : public GraphicsElement @@ -20,7 +20,7 @@ public: Rect viewBox() const; PreserveAspectRatio preserveAspectRatio() const; - std::unique_ptr layoutDocument(const ParseDocument* document) const; + std::unique_ptr build(const TreeBuilder* builder) const; void layout(LayoutContext* context, LayoutContainer* current) const; std::unique_ptr clone() const; diff --git a/source/symbolelement.cpp b/source/symbolelement.cpp index 10d451cb7..03673d8a9 100644 --- a/source/symbolelement.cpp +++ b/source/symbolelement.cpp @@ -4,43 +4,43 @@ namespace lunasvg { SymbolElement::SymbolElement() - : StyledElement(ElementId::Symbol) + : StyledElement(ElementID::Symbol) { } Length SymbolElement::x() const { - auto& value = get(PropertyId::X); + auto& value = get(PropertyID::X); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length SymbolElement::y() const { - auto& value = get(PropertyId::Y); + auto& value = get(PropertyID::Y); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length SymbolElement::width() const { - auto& value = get(PropertyId::Width); + auto& value = get(PropertyID::Width); return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); } Length SymbolElement::height() const { - auto& value = get(PropertyId::Height); + auto& value = get(PropertyID::Height); return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); } Rect SymbolElement::viewBox() const { - auto& value = get(PropertyId::ViewBox); + auto& value = get(PropertyID::ViewBox); return Parser::parseViewBox(value); } PreserveAspectRatio SymbolElement::preserveAspectRatio() const { - auto& value = get(PropertyId::PreserveAspectRatio); + auto& value = get(PropertyID::PreserveAspectRatio); return Parser::parsePreserveAspectRatio(value); } diff --git a/source/useelement.cpp b/source/useelement.cpp index f67383303..ced0ec004 100644 --- a/source/useelement.cpp +++ b/source/useelement.cpp @@ -8,47 +8,47 @@ namespace lunasvg { UseElement::UseElement() - : GraphicsElement(ElementId::Use) + : GraphicsElement(ElementID::Use) { } Length UseElement::x() const { - auto& value = get(PropertyId::X); + auto& value = get(PropertyID::X); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length UseElement::y() const { - auto& value = get(PropertyId::Y); + auto& value = get(PropertyID::Y); return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); } Length UseElement::width() const { - auto& value = get(PropertyId::Width); + auto& value = get(PropertyID::Width); return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); } Length UseElement::height() const { - auto& value = get(PropertyId::Height); + auto& value = get(PropertyID::Height); return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); } std::string UseElement::href() const { - auto& value = get(PropertyId::Href); + auto& value = get(PropertyID::Href); return Parser::parseHref(value); } void UseElement::transferWidthAndHeight(Element* element) const { - auto& width = get(PropertyId::Width); - auto& height = get(PropertyId::Height); + auto& width = get(PropertyID::Width); + auto& height = get(PropertyID::Height); - element->set(PropertyId::Width, width, 0x0); - element->set(PropertyId::Height, height, 0x0); + element->set(PropertyID::Width, width, 0x0); + element->set(PropertyID::Height, height, 0x0); } void UseElement::layout(LayoutContext* context, LayoutContainer* current) const @@ -69,15 +69,15 @@ void UseElement::layout(LayoutContext* context, LayoutContainer* current) const auto _x = lengthContext.valueForLength(x(), LengthMode::Width); auto _y = lengthContext.valueForLength(y(), LengthMode::Height); - auto transform = get(PropertyId::Transform); + auto transform = get(PropertyID::Transform); transform += "translate("; transform += std::to_string(_x); transform += ' '; transform += std::to_string(_y); transform += ')'; - group->set(PropertyId::Transform, transform, 0x10); + group->set(PropertyID::Transform, transform, 0x10); - if(ref->id == ElementId::Svg || ref->id == ElementId::Symbol) + if(ref->id == ElementID::Svg || ref->id == ElementID::Symbol) { auto element = ref->cloneElement(); transferWidthAndHeight(element.get());