summaryrefslogtreecommitdiff
path: root/libs/litehtml/src/style.cpp
diff options
context:
space:
mode:
authorGeorge Hazan <george.hazan@gmail.com>2024-10-09 18:13:40 +0300
committerGeorge Hazan <george.hazan@gmail.com>2024-10-09 18:13:40 +0300
commit0e86b853be3b5f809ed1decbf636221c1144a386 (patch)
tree4ce84d9849646a559d5afece33fc6e64d39e4a50 /libs/litehtml/src/style.cpp
parent834cbb58d74215980165eab257538ba918a378cd (diff)
litehtml update
Diffstat (limited to 'libs/litehtml/src/style.cpp')
-rw-r--r--libs/litehtml/src/style.cpp1907
1 files changed, 1076 insertions, 831 deletions
diff --git a/libs/litehtml/src/style.cpp b/libs/litehtml/src/style.cpp
index 05d366d867..10768700a6 100644
--- a/libs/litehtml/src/style.cpp
+++ b/libs/litehtml/src/style.cpp
@@ -1,9 +1,25 @@
#include "html.h"
#include "style.h"
+#include "css_parser.h"
+#include "internal.h"
+
+
+// All functions here assume that whitespace have been removed.
namespace litehtml
{
+bool parse_bg_image(const css_token& token, image& bg_image, document_container* container);
+bool parse_bg_position_size(const css_token_vector& tokens, int& index, css_length& x, css_length& y, css_size& size);
+bool parse_bg_size(const css_token_vector& tokens, int& index, css_size& size);
+bool parse_two_lengths(const css_token_vector& tokens, css_length len[2], int options);
+template<class T, class... Args>
+int parse_1234_values(const css_token_vector& tokens, T result[4], bool (*func)(const css_token&, T&, Args...), Args... args);
+int parse_1234_lengths(const css_token_vector& tokens, css_length len[4], int options, string keywords = "");
+bool parse_border_width(const css_token& tok, css_length& width);
+bool parse_font_family(const css_token_vector& tokens, string& font_family);
+bool parse_font_weight(const css_token& tok, css_length& weight);
+
std::map<string_id, string> style::m_valid_values =
{
{ _display_, style_display_strings },
@@ -41,91 +57,137 @@ std::map<string_id, string> style::m_valid_values =
{ _flex_direction_, flex_direction_strings },
{ _flex_wrap_, flex_wrap_strings },
{ _justify_content_, flex_justify_content_strings },
- { _align_items_, flex_align_items_strings },
{ _align_content_, flex_align_content_strings },
+ { _align_items_, flex_align_items_strings },
{ _align_self_, flex_align_items_strings },
{ _caption_side_, caption_side_strings },
};
-void style::parse(const string& txt, const string& baseurl, document_container* container)
+std::map<string_id, vector<string_id>> shorthands =
{
- std::vector<string> properties;
- split_string(txt, properties, ";", "", "\"'");
+ { _font_, {_font_style_, _font_variant_, _font_weight_, _font_size_, _line_height_, _font_family_}},
+
+ { _background_, {
+ _background_color_,
+ _background_position_x_,
+ _background_position_y_,
+ _background_repeat_,
+ _background_attachment_,
+ _background_image_,
+ _background_image_baseurl_,
+ _background_size_,
+ _background_origin_,
+ _background_clip_
+ } },
+
+ { _list_style_, {_list_style_image_, _list_style_image_baseurl_, _list_style_position_, _list_style_type_}},
+
+ { _margin_, {_margin_top_, _margin_right_, _margin_bottom_, _margin_left_}},
+ { _padding_, {_padding_top_, _padding_right_, _padding_bottom_, _padding_left_}},
+
+ { _border_width_, {_border_top_width_, _border_right_width_, _border_bottom_width_, _border_left_width_}},
+ { _border_style_, {_border_top_style_, _border_right_style_, _border_bottom_style_, _border_left_style_}},
+ { _border_color_, {_border_top_color_, _border_right_color_, _border_bottom_color_, _border_left_color_}},
+ { _border_top_, {_border_top_width_, _border_top_style_, _border_top_color_}},
+ { _border_right_, {_border_right_width_, _border_right_style_, _border_right_color_}},
+ { _border_bottom_, {_border_bottom_width_, _border_bottom_style_, _border_bottom_color_}},
+ { _border_left_, {_border_left_width_, _border_left_style_, _border_left_color_}},
+ { _border_, {
+ _border_top_width_, _border_right_width_, _border_bottom_width_, _border_left_width_,
+ _border_top_style_, _border_right_style_, _border_bottom_style_, _border_left_style_,
+ _border_top_color_, _border_right_color_, _border_bottom_color_, _border_left_color_
+ } },
+
+ { _flex_, {_flex_grow_, _flex_shrink_, _flex_basis_}},
+ { _flex_flow_, {_flex_direction_, _flex_wrap_}},
+};
- for(const auto & property : properties)
+void style::add(const string& txt, const string& baseurl, document_container* container)
+{
+ auto tokens = normalize(txt, f_componentize);
+ add(tokens, baseurl, container);
+}
+
+void style::add(const css_token_vector& tokens, const string& baseurl, document_container* container)
+{
+ raw_declaration::vector decls;
+ raw_rule::vector rules;
+ css_parser(tokens).consume_style_block_contents(decls, rules);
+ if (!rules.empty())
+ css_parse_error("rule inside a style block");
+ if (decls.empty())
+ return;
+
+ // Parse each declaration
+ for (auto& decl : decls)
{
- parse_property(property, baseurl, container);
+ remove_whitespace(decl.value);
+ // Note: decl.value is already componentized, see consume_qualified_rule and consume_style_block_contents.
+ // Note: decl.value may be empty.
+ string name = decl.name.substr(0, 2) == "--" ? decl.name : lowcase(decl.name);
+ add_property(_id(name), decl.value, baseurl, decl.important, container);
}
}
-void style::parse_property(const string& txt, const string& baseurl, document_container* container)
+bool has_var(const css_token_vector& tokens)
{
- string::size_type pos = txt.find_first_of(':');
- if(pos != string::npos)
+ for (auto& tok : tokens)
{
- string name = txt.substr(0, pos);
- string val = txt.substr(pos + 1);
-
- trim(name); lcase(name);
- trim(val);
-
- if(!name.empty() && !val.empty())
- {
- string_vector vals;
- split_string(val, vals, "!");
- if(vals.size() == 1)
- {
- add_property(_id(name), val, baseurl, false, container);
- } else if(vals.size() > 1)
- {
- trim(vals[0]);
- lcase(vals[1]);
- add_property(_id(name), vals[0], baseurl, vals[1] == "important", container);
- }
- }
+ if (tok.type == CV_FUNCTION && lowcase(tok.name) == "var")
+ return true;
+ if (tok.is_component_value() && has_var(tok.value))
+ return true;
}
+ return false;
}
void style::inherit_property(string_id name, bool important)
{
- switch (name)
+ auto atomic_properties = at(shorthands, name);
+ if (!atomic_properties.empty())
{
- case _font_:
- add_parsed_property(_font_style_, property_value(inherit(), important));
- add_parsed_property(_font_variant_, property_value(inherit(), important));
- add_parsed_property(_font_weight_, property_value(inherit(), important));
- add_parsed_property(_font_size_, property_value(inherit(), important));
- add_parsed_property(_line_height_, property_value(inherit(), important));
- break;
- case _background_:
- add_parsed_property(_background_color_, property_value(inherit(), important));
- add_parsed_property(_background_position_x_, property_value(inherit(), important));
- add_parsed_property(_background_position_y_, property_value(inherit(), important));
- add_parsed_property(_background_repeat_, property_value(inherit(), important));
- add_parsed_property(_background_attachment_, property_value(inherit(), important));
- add_parsed_property(_background_image_, property_value(inherit(), important));
- add_parsed_property(_background_image_baseurl_, property_value(inherit(), important));
- add_parsed_property(_background_size_, property_value(inherit(), important));
- add_parsed_property(_background_origin_, property_value(inherit(), important));
- add_parsed_property(_background_clip_, property_value(inherit(), important));
- break;
- default:
- add_parsed_property(name, property_value(inherit(), important));
+ for (auto atomic : atomic_properties)
+ add_parsed_property(atomic, property_value(inherit(), important));
}
+ else
+ add_parsed_property(name, property_value(inherit(), important));
}
-void style::add_property(string_id name, const string& val, const string& baseurl, bool important, document_container* container)
+void style::add_length_property(string_id name, css_token val, string keywords, int options, bool important)
{
- if (val.find("var(") != string::npos) return add_parsed_property(name, property_value(val, important, true));
- if (val == "inherit") return inherit_property(name, important);
+ css_length length;
+ if (length.from_token(val, options, keywords))
+ add_parsed_property(name, property_value(length, important));
+}
- string url;
- css_length len[4], length;
+// `value` is a list of component values with all whitespace tokens removed, including those inside component values
+void style::add_property(string_id name, const css_token_vector& value, const string& baseurl, bool important, document_container* container)
+{
+ // Note: empty value is a valid value for a custom property.
+ if (value.empty() && _s(name).substr(0, 2) != "--")
+ return;
+
+ if (has_var(value))
+ return add_parsed_property(name, property_value(value, important, true));
+
+ // valid only if value contains a single token
+ css_token val = value.size() == 1 ? value[0] : css_token();
+ // nonempty if value is a single identifier
+ string ident = val.ident();
+
+ if (ident == "inherit")
+ return inherit_property(name, important);
+
+ int idx[4];
+ web_color clr[4];
+ css_length len[4];
+ string str;
switch (name)
{
- // keyword-only properties
+ // ============================= SINGLE KEYWORD =============================
+
case _display_:
case _visibility_:
case _position_:
@@ -141,7 +203,6 @@ void style::add_property(string_id name, const string& val, const string& baseur
case _font_style_:
case _font_variant_:
- case _font_weight_:
case _list_style_type_:
case _list_style_position_:
@@ -158,1036 +219,1174 @@ void style::add_property(string_id name, const string& val, const string& baseur
case _align_content_:
case _caption_side_:
- {
- int idx = value_index(val, m_valid_values[name]);
- if (idx >= 0)
- {
- add_parsed_property(name, property_value(idx, important));
- }
- break;
- }
- case _align_items_:
- case _align_self_:
- parse_align_self(name, val, important);
+
+ if (int index = value_index(ident, m_valid_values[name]); index >= 0)
+ add_parsed_property(name, property_value(index, important));
break;
- // <length>
+ // ============================= LENGTH =============================
+
+ // auto | <integer> https://developer.mozilla.org/en-US/docs/Web/CSS/z-index#formal_syntax
+ case _z_index_:
+ return add_length_property(name, val, "auto", f_integer, important);
+
+ // <length-percentage> https://developer.mozilla.org/en-US/docs/Web/CSS/text-indent#formal_syntax
case _text_indent_:
+ return add_length_property(name, val, "", f_length_percentage, important);
+
+ // <length-percentage [0,∞]> https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left
case _padding_left_:
case _padding_right_:
case _padding_top_:
case _padding_bottom_:
- length.fromString(val);
- add_parsed_property(name, property_value(length, important));
- break;
-
- // <length> | auto
+ return add_length_property(name, val, "", f_length_percentage|f_positive, important);
+
+ // auto | <length-percentage> https://developer.mozilla.org/en-US/docs/Web/CSS/left
case _left_:
case _right_:
case _top_:
case _bottom_:
- case _z_index_: // <integer> | auto
- case _width_:
- case _height_:
- case _min_width_:
- case _min_height_:
case _margin_left_:
case _margin_right_:
case _margin_top_:
case _margin_bottom_:
- length.fromString(val, "auto", -1);
- add_parsed_property(name, property_value(length, important));
- break;
+ return add_length_property(name, val, "auto", f_length_percentage, important);
+
+ // auto | min-content | max-content | fit-content | <length-percentage [0,∞]> https://developer.mozilla.org/en-US/docs/Web/CSS/width#formal_syntax
+ case _width_:
+ case _height_:
+ case _min_width_:
+ case _min_height_:
+ return add_length_property(name, val, "auto", f_length_percentage|f_positive, important);
- // <length> | none
+ // none | min-content | max-content | fit-content | <length-percentage [0,∞]> https://developer.mozilla.org/en-US/docs/Web/CSS/max-width#formal_syntax
case _max_width_:
case _max_height_:
- length.fromString(val, "none", -1);
- add_parsed_property(name, property_value(length, important));
- break;
-
+ return add_length_property(name, val, "none", f_length_percentage|f_positive, important);
+
+ // normal | <number [0,∞]> | <length-percentage [0,∞]> https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#formal_syntax
case _line_height_:
- length.fromString(val, "normal", -1);
- add_parsed_property(name, property_value(length, important));
- break;
+ return add_length_property(name, val, "normal", f_number|f_length_percentage|f_positive, important);
+ // font-size = <absolute-size> | <relative-size> | <length-percentage [0,∞]> https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#formal_syntax
case _font_size_:
- length.fromString(val, font_size_strings, -1);
- add_parsed_property(name, property_value(length, important));
+ return add_length_property(name, val, font_size_strings, f_length_percentage|f_positive, important);
+
+ case _margin_:
+ if (int n = parse_1234_lengths(value, len, f_length_percentage, "auto"))
+ add_four_properties(_margin_top_, len, n, important);
break;
- // Parse background shorthand properties
- case _background_:
- parse_background(val, baseurl, important, container);
+ case _padding_:
+ if (int n = parse_1234_lengths(value, len, f_length_percentage | f_positive))
+ add_four_properties(_padding_top_, len, n, important);
break;
+
+ // ============================= COLOR =============================
- case _background_image_:
- parse_background_image(val, container, baseurl, important);
+ case _color_:
+ if (ident == "currentcolor") return inherit_property(name, important);
+ // fallthrough
+ case _background_color_:
+ case _border_top_color_:
+ case _border_bottom_color_:
+ case _border_left_color_:
+ case _border_right_color_:
+ if (parse_color(val, *clr, container))
+ add_parsed_property(name, property_value(*clr, important));
break;
- case _background_attachment_:
- case _background_repeat_:
- case _background_clip_:
- case _background_origin_:
- parse_keyword_comma_list(name, val, important);
+ // ============================= BACKGROUND =============================
+
+ case _background_:
+ parse_background(value, baseurl, important, container);
+ break;
+
+ case _background_image_:
+ parse_background_image(value, baseurl, important, container);
break;
case _background_position_:
- parse_background_position(val, important);
+ parse_background_position(value, important);
break;
case _background_size_:
- parse_background_size(val, important);
+ parse_background_size(value, important);
break;
- // Parse border spacing properties
- case _border_spacing_:
- parse_two_lengths(val, len);
- add_parsed_property(__litehtml_border_spacing_x_, property_value(len[0], important));
- add_parsed_property(__litehtml_border_spacing_y_, property_value(len[1], important));
+ case _background_repeat_:
+ case _background_attachment_:
+ case _background_origin_:
+ case _background_clip_:
+ parse_keyword_comma_list(name, value, important);
break;
- // Parse borders shorthand properties
+ // ============================= BORDER =============================
+
case _border_:
- {
- string_vector tokens;
- split_string(val, tokens, " ", "", "(");
- for (const auto& token : tokens)
- {
- int idx = value_index(token, border_style_strings);
- if (idx >= 0)
- {
- property_value style(idx, important);
- add_parsed_property(_border_left_style_, style);
- add_parsed_property(_border_right_style_, style);
- add_parsed_property(_border_top_style_, style);
- add_parsed_property(_border_bottom_style_, style);
- }
- else if (t_isdigit(token[0]) || token[0] == '.' ||
- value_in_list(token, border_width_strings))
- {
- property_value width(parse_border_width(token), important);
- add_parsed_property(_border_left_width_, width);
- add_parsed_property(_border_right_width_, width);
- add_parsed_property(_border_top_width_, width);
- add_parsed_property(_border_bottom_width_, width);
- }
- else if (web_color::is_color(token, container))
- {
- web_color _color = web_color::from_string(token, container);
- property_value color(_color, important);
- add_parsed_property(_border_left_color_, color);
- add_parsed_property(_border_right_color_, color);
- add_parsed_property(_border_top_color_, color);
- add_parsed_property(_border_bottom_color_, color);
- }
- }
+ parse_border(value, important, container);
break;
- }
case _border_left_:
case _border_right_:
case _border_top_:
case _border_bottom_:
- {
- string_vector tokens;
- split_string(val, tokens, " ", "", "(");
- for (const auto& token : tokens)
- {
- int idx = value_index(token, border_style_strings);
- if (idx >= 0)
- {
- add_parsed_property(_id(_s(name) + "-style"), property_value(idx, important));
- }
- else if (t_isdigit(token[0]) || token[0] == '.' ||
- value_in_list(token, border_width_strings))
- {
- property_value width(parse_border_width(token), important);
- add_parsed_property(_id(_s(name) + "-width"), width);
- }
- else if (web_color::is_color(token, container))
- {
- web_color color = web_color::from_string(token, container);
- add_parsed_property(_id(_s(name) + "-color"), property_value(color, important));
- }
- }
+ parse_border_side(name, value, important, container);
break;
- }
- // Parse border-width/style/color shorthand properties
case _border_width_:
+ if (int n = parse_1234_values(value, len, parse_border_width))
+ add_four_properties(_border_top_width_, len, n, important);
+ break;
case _border_style_:
+ if (int n = parse_1234_values(value, idx, parse_keyword, (string)border_style_strings, 0))
+ add_four_properties(_border_top_style_, idx, n, important);
+ break;
case _border_color_:
- {
- string prop = name == _border_width_ ? "-width" : name == _border_style_ ? "-style" : "-color";
-
- string_vector tokens;
- split_string(val, tokens, " ");
- if (tokens.size() == 4)
- {
- add_property(_id("border-top" + prop), tokens[0], baseurl, important, container);
- add_property(_id("border-right" + prop), tokens[1], baseurl, important, container);
- add_property(_id("border-bottom" + prop), tokens[2], baseurl, important, container);
- add_property(_id("border-left" + prop), tokens[3], baseurl, important, container);
- }
- else if (tokens.size() == 3)
- {
- add_property(_id("border-top" + prop), tokens[0], baseurl, important, container);
- add_property(_id("border-right" + prop), tokens[1], baseurl, important, container);
- add_property(_id("border-left" + prop), tokens[1], baseurl, important, container);
- add_property(_id("border-bottom" + prop), tokens[2], baseurl, important, container);
- }
- else if (tokens.size() == 2)
- {
- add_property(_id("border-top" + prop), tokens[0], baseurl, important, container);
- add_property(_id("border-bottom" + prop), tokens[0], baseurl, important, container);
- add_property(_id("border-right" + prop), tokens[1], baseurl, important, container);
- add_property(_id("border-left" + prop), tokens[1], baseurl, important, container);
- }
- else if (tokens.size() == 1)
- {
- add_property(_id("border-top" + prop), tokens[0], baseurl, important, container);
- add_property(_id("border-bottom" + prop), tokens[0], baseurl, important, container);
- add_property(_id("border-right" + prop), tokens[0], baseurl, important, container);
- add_property(_id("border-left" + prop), tokens[0], baseurl, important, container);
- }
+ if (int n = parse_1234_values(value, clr, parse_color, container))
+ add_four_properties(_border_top_color_, clr, n, important);
break;
- }
case _border_top_width_:
case _border_bottom_width_:
case _border_left_width_:
case _border_right_width_:
- length = parse_border_width(val);
- add_parsed_property(name, property_value(length, important));
+ if (parse_border_width(val, *len))
+ add_parsed_property(name, property_value(*len, important));
break;
- case _color_:
- case _background_color_:
- case _border_top_color_:
- case _border_bottom_color_:
- case _border_left_color_:
- case _border_right_color_:
- if (web_color::is_color(val, container))
- {
- web_color color = web_color::from_string(val, container);
- add_parsed_property(name, property_value(color, important));
- }
- break;
-
- // Parse border radius shorthand properties
+ // border-bottom-left-radius = <length-percentage [0,∞]>{1,2} https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-left-radius
case _border_bottom_left_radius_:
case _border_bottom_right_radius_:
case _border_top_right_radius_:
case _border_top_left_radius_:
- parse_two_lengths(val, len);
- add_parsed_property(_id(_s(name) + "-x"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-y"), property_value(len[1], important));
- break;
-
- // Parse border-radius shorthand properties
- case _border_radius_:
- {
- string_vector tokens;
- split_string(val, tokens, "/");
- if (tokens.size() == 1)
+ if (parse_two_lengths(value, len, f_length_percentage | f_positive))
{
- add_property(_border_radius_x_, tokens[0], baseurl, important, container);
- add_property(_border_radius_y_, tokens[0], baseurl, important, container);
- }
- else if (tokens.size() >= 2)
- {
- add_property(_border_radius_x_, tokens[0], baseurl, important, container);
- add_property(_border_radius_y_, tokens[1], baseurl, important, container);
+ add_parsed_property(_id(_s(name) + "-x"), property_value(len[0], important));
+ add_parsed_property(_id(_s(name) + "-y"), property_value(len[1], important));
}
break;
- }
+
case _border_radius_x_:
case _border_radius_y_:
{
- string_id top_left, top_right, bottom_right, bottom_left;
- if (name == _border_radius_x_)
- {
- top_left = _border_top_left_radius_x_;
- top_right = _border_top_right_radius_x_;
- bottom_right = _border_bottom_right_radius_x_;
- bottom_left = _border_bottom_left_radius_x_;
- }
- else
- {
- top_left = _border_top_left_radius_y_;
- top_right = _border_top_right_radius_y_;
- bottom_right = _border_bottom_right_radius_y_;
- bottom_left = _border_bottom_left_radius_y_;
- }
+ string_id top_left = name == _border_radius_x_ ?
+ _border_top_left_radius_x_ :
+ _border_top_left_radius_y_;
- switch (parse_four_lengths(val, len))
- {
- case 1:
- add_parsed_property(top_left, property_value(len[0], important));
- add_parsed_property(top_right, property_value(len[0], important));
- add_parsed_property(bottom_right, property_value(len[0], important));
- add_parsed_property(bottom_left, property_value(len[0], important));
- break;
- case 2:
- add_parsed_property(top_left, property_value(len[0], important));
- add_parsed_property(top_right, property_value(len[1], important));
- add_parsed_property(bottom_right, property_value(len[0], important));
- add_parsed_property(bottom_left, property_value(len[1], important));
- break;
- case 3:
- add_parsed_property(top_left, property_value(len[0], important));
- add_parsed_property(top_right, property_value(len[1], important));
- add_parsed_property(bottom_right, property_value(len[2], important));
- add_parsed_property(bottom_left, property_value(len[1], important));
- break;
- case 4:
- add_parsed_property(top_left, property_value(len[0], important));
- add_parsed_property(top_right, property_value(len[1], important));
- add_parsed_property(bottom_right, property_value(len[2], important));
- add_parsed_property(bottom_left, property_value(len[3], important));
- break;
- }
+ if (int n = parse_1234_lengths(value, len, f_length_percentage | f_positive))
+ add_four_properties(top_left, len, n, important);
break;
}
- // Parse list-style shorthand properties
- case _list_style_:
- {
- add_parsed_property(_list_style_type_, property_value(list_style_type_disc, important));
- add_parsed_property(_list_style_position_, property_value(list_style_position_outside, important));
- add_parsed_property(_list_style_image_, property_value("", important));
- add_parsed_property(_list_style_image_baseurl_, property_value("", important));
-
- string_vector tokens;
- split_string(val, tokens, " ", "", "(");
- for (const auto& token : tokens)
+ case _border_radius_:
+ parse_border_radius(value, important);
+ break;
+
+ // border-spacing = <length>{1,2} https://developer.mozilla.org/en-US/docs/Web/CSS/border-spacing
+ // Lengths may not be negative. https://drafts.csswg.org/css2/#separated-borders
+ case _border_spacing_:
+ if (parse_two_lengths(value, len, f_length | f_positive))
{
- int idx = value_index(token, list_style_type_strings);
- if (idx >= 0)
- {
- add_parsed_property(_list_style_type_, property_value(idx, important));
- }
- else
- {
- idx = value_index(token, list_style_position_strings);
- if (idx >= 0)
- {
- add_parsed_property(_list_style_position_, property_value(idx, important));
- }
- else if (!strncmp(token.c_str(), "url", 3))
- {
- css::parse_css_url(token, url);
- add_parsed_property(_list_style_image_, property_value(url, important));
- add_parsed_property(_list_style_image_baseurl_, property_value(baseurl, important));
- }
- }
+ add_parsed_property(__litehtml_border_spacing_x_, property_value(len[0], important));
+ add_parsed_property(__litehtml_border_spacing_y_, property_value(len[1], important));
}
break;
- }
- case _list_style_image_:
- css::parse_css_url(val, url);
- add_parsed_property(_list_style_image_, property_value(url, important));
- add_parsed_property(_list_style_image_baseurl_, property_value(baseurl, important));
- break;
+ // ============================= LIST =============================
- // Parse margin and padding shorthand properties
- case _margin_:
- case _padding_:
- {
- switch (parse_four_lengths(val, len))
+ case _list_style_image_:
+ if (string url; parse_list_style_image(val, url))
{
- case 4:
- add_parsed_property(_id(_s(name) + "-top"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-right"), property_value(len[1], important));
- add_parsed_property(_id(_s(name) + "-bottom"), property_value(len[2], important));
- add_parsed_property(_id(_s(name) + "-left"), property_value(len[3], important));
- break;
- case 3:
- add_parsed_property(_id(_s(name) + "-top"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-right"), property_value(len[1], important));
- add_parsed_property(_id(_s(name) + "-left"), property_value(len[1], important));
- add_parsed_property(_id(_s(name) + "-bottom"), property_value(len[2], important));
- break;
- case 2:
- add_parsed_property(_id(_s(name) + "-top"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-bottom"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-right"), property_value(len[1], important));
- add_parsed_property(_id(_s(name) + "-left"), property_value(len[1], important));
- break;
- case 1:
- add_parsed_property(_id(_s(name) + "-top"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-bottom"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-right"), property_value(len[0], important));
- add_parsed_property(_id(_s(name) + "-left"), property_value(len[0], important));
- break;
+ add_parsed_property(_list_style_image_, property_value(url, important));
+ add_parsed_property(_list_style_image_baseurl_, property_value(baseurl, important));
}
break;
- }
- // Parse font shorthand properties
+ case _list_style_:
+ parse_list_style(value, baseurl, important);
+ break;
+
+ // ============================= FONT =============================
+
case _font_:
- parse_font(val, important);
+ parse_font(value, important);
break;
- // Parse flex-flow shorthand properties
- case _flex_flow_:
- {
- string_vector tokens;
- split_string(val, tokens, " ");
- for (const auto& tok : tokens)
- {
- int idx;
- if ((idx = value_index(tok, flex_direction_strings)) >= 0)
- {
- add_parsed_property(_flex_direction_, property_value(idx, important));
- }
- else if ((idx = value_index(tok, flex_wrap_strings)) >= 0)
- {
- add_parsed_property(_flex_wrap_, property_value(idx, important));
- }
- }
+ case _font_family_:
+ if (parse_font_family(value, str))
+ add_parsed_property(name, property_value(str, important));
break;
- }
- // Parse flex shorthand properties
+ case _font_weight_:
+ if (parse_font_weight(val, *len))
+ add_parsed_property(name, property_value(*len, important));
+ break;
+
+ case _text_decoration_:
+ str = get_repr(value, 0, -1, true);
+ add_parsed_property(name, property_value(str, important));
+ break;
+
+ // ============================= FLEX =============================
+
case _flex_:
- parse_flex(val, important);
+ parse_flex(value, important);
break;
- case _flex_grow_:
+ case _flex_grow_: // <number [0,∞]>
case _flex_shrink_:
- add_parsed_property(name, property_value(t_strtof(val), important));
+ if (val.type == NUMBER && val.n.number >= 0)
+ add_parsed_property(name, property_value(val.n.number, important));
break;
case _flex_basis_:
- length.fromString(val, flex_basis_strings, -1);
- add_parsed_property(_flex_basis_, property_value(length, important));
+ add_length_property(name, val, flex_basis_strings, f_length_percentage|f_positive, important);
+ break;
+
+ case _flex_flow_:
+ parse_flex_flow(value, important);
+ break;
+
+ case _align_items_:
+ case _align_self_:
+ parse_align_self(name, value, important);
break;
case _order_: // <integer>
- {
- char* end;
- int int_val = (int) strtol(val.c_str(), &end, 10);
- if(end[0] == '\0')
- {
- add_parsed_property(name, property_value(int_val, important));
- }
- }
+ if (val.type == NUMBER && val.n.number_type == css_number_integer)
+ add_parsed_property(name, property_value((int)val.n.number, important));
break;
+
+ // ============================= COUNTER, CONTENT =============================
+
case _counter_increment_:
case _counter_reset_:
- {
- string_vector tokens;
- split_string(val, tokens, " ");
- add_parsed_property(name, property_value(tokens, important));
+ {
+ // TODO: parse it properly here
+ string_vector strings;
+ for (const auto& tok : value) strings.push_back(tok.get_repr(true));
+ add_parsed_property(name, property_value(strings, important));
break;
}
+ case _content_:
+ // TODO: parse it properly here
+ str = get_repr(value, 0, -1, true);
+ add_parsed_property(name, property_value(str, important));
+ break;
+
+ // ================================== OTHER ==================================
+
+ case _cursor_:
+ str = get_repr(value, 0, -1, true);
+ add_parsed_property(name, property_value(str, important));
+ break;
+
+ // ============================= CUSTOM PROPERTY =============================
+
+ // https://drafts.csswg.org/css-variables-2/#defining-variables
default:
- add_parsed_property(name, property_value(val, important));
+ if (_s(name).substr(0, 2) == "--" && _s(name).size() >= 3 &&
+ (value.empty() || is_declaration_value(value)))
+ add_parsed_property(name, property_value(value, important));
}
}
-css_length style::parse_border_width(const string& str)
+void style::add_property(string_id name, const string& value, const string& baseurl, bool important, document_container* container)
{
- css_length len;
- if (t_isdigit(str[0]) || str[0] == '.')
+ auto tokens = normalize(value, f_componentize | f_remove_whitespace);
+ add_property(name, tokens, baseurl, important, container);
+}
+
+// This should be the same as parse_bg_image, but list-style-image is currently a string (not an image).
+bool style::parse_list_style_image(const css_token& tok, string& url)
+{
+ if (tok.ident() == "none")
{
- len.fromString(str);
+ url = "";
+ return true;
}
- else
+
+ return parse_url(tok, url);
+}
+
+// https://drafts.csswg.org/css-lists/#list-style-property
+// <'list-style-position'> || <'list-style-image'> || <'list-style-type'>
+void style::parse_list_style(const css_token_vector& tokens, string baseurl, bool important)
+{
+ // initial values: https://developer.mozilla.org/en-US/docs/Web/CSS/list-style#formal_definition
+ int type = list_style_type_disc;
+ int position = list_style_position_outside;
+ string image = ""; // none
+
+ bool type_found = false;
+ bool position_found = false;
+ bool image_found = false;
+ int none_count = 0;
+
+ for (const auto& token : tokens)
{
- int idx = value_index(str, border_width_strings);
- if (idx >= 0)
- {
- len.set_value(border_width_values[idx], css_units_px);
+ // "...none is a valid value for both list-style-image and list-style-type. To resolve this ambiguity,
+ // a value of none ... must be applied to whichever of the two properties aren’t otherwise set by the shorthand."
+ if (token.ident() == "none") {
+ none_count++;
+ continue;
}
+ if (!type_found && parse_keyword(token, type, list_style_type_strings))
+ type_found = true;
+ else if (!position_found && parse_keyword(token, position, list_style_position_strings))
+ position_found = true;
+ else if (!image_found && parse_list_style_image(token, image))
+ image_found = true;
+ else
+ return; // syntax error
+ }
+
+ switch (none_count)
+ {
+ case 0:
+ break;
+ case 1:
+ if (type_found && image_found) return;
+ if (!type_found) type = list_style_type_none;
+ break;
+ case 2:
+ if (type_found || image_found) return;
+ type = list_style_type_none;
+ break;
+ default:
+ return; // syntax error
}
- return len;
+
+ add_parsed_property(_list_style_type_, property_value(type, important));
+ add_parsed_property(_list_style_position_, property_value(position, important));
+ add_parsed_property(_list_style_image_, property_value(image, important));
+ add_parsed_property(_list_style_image_baseurl_, property_value(baseurl, important));
}
-void style::parse_two_lengths(const string& str, css_length len[2])
+// https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius
+// border-radius = <length-percentage [0,∞]>{1,4} [ / <length-percentage [0,∞]>{1,4} ]?
+void style::parse_border_radius(const css_token_vector& tokens, bool important)
{
- string_vector tokens;
- split_string(str, tokens, " ");
- if (tokens.size() == 1)
+ int i;
+ for (i = 0; i < (int)tokens.size() && tokens[i].ch != '/'; i++) {}
+
+ if (i == (int)tokens.size()) // no '/'
{
- css_length length;
- length.fromString(tokens[0]);
- len[0] = len[1] = length;
+ css_length len[4];
+ if (int n = parse_1234_lengths(tokens, len, f_length_percentage | f_positive))
+ {
+ add_four_properties(_border_top_left_radius_x_, len, n, important);
+ add_four_properties(_border_top_left_radius_y_, len, n, important);
+ }
}
- else if (tokens.size() == 2)
+ else
{
- len[0].fromString(tokens[0]);
- len[1].fromString(tokens[1]);
+ auto raduis_x = slice(tokens, 0, i);
+ auto raduis_y = slice(tokens, i + 1);
+
+ css_length rx[4], ry[4];
+ int n = parse_1234_lengths(raduis_x, rx, f_length_percentage | f_positive);
+ int m = parse_1234_lengths(raduis_y, ry, f_length_percentage | f_positive);
+
+ if (n && m)
+ {
+ add_four_properties(_border_top_left_radius_x_, rx, n, important);
+ add_four_properties(_border_top_left_radius_y_, ry, m, important);
+ }
}
}
-int style::parse_four_lengths(const string& str, css_length len[4])
+bool parse_border_width(const css_token& token, css_length& w)
{
- string_vector tokens;
- split_string(str, tokens, " ");
- if (tokens.size() == 0 || tokens.size() > 4)
+ css_length width;
+ if (!width.from_token(token, f_length | f_positive, border_width_strings))
+ return false;
+
+ if (width.is_predefined())
+ width.set_value(border_width_values[width.predef()], css_units_px);
+
+ w = width;
+ return true;
+}
+
+// https://drafts.csswg.org/css-backgrounds/#propdef-border
+// <line-width> || <line-style> || <color>
+// <line-width> = <length [0,∞]> | thin | medium | thick
+// <line-style> = none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset
+bool parse_border_helper(const css_token_vector& tokens, document_container* container,
+ css_length& width, border_style& style, web_color& color)
+{
+ // initial values: https://developer.mozilla.org/en-US/docs/Web/CSS/border#formal_definition
+ css_length _width = border_width_medium_value;
+ border_style _style = border_style_none;
+ web_color _color = web_color::current_color;
+
+ bool width_found = false;
+ bool style_found = false;
+ bool color_found = false;
+
+ for (const auto& token : tokens)
{
- return 0;
+ if (!width_found && parse_border_width(token, _width))
+ width_found = true;
+ else if (!style_found && parse_keyword(token, _style, border_style_strings))
+ style_found = true;
+ else if (!color_found && parse_color(token, _color, container))
+ color_found = true;
+ else
+ return false;
}
+
+ width = _width;
+ style = _style;
+ color = _color;
+ return true;
+}
+
+void style::parse_border(const css_token_vector& tokens, bool important, document_container* container)
+{
+ css_length width;
+ border_style style;
+ web_color color;
+
+ if (!parse_border_helper(tokens, container, width, style, color))
+ return;
+
+ for (auto name : {_border_left_width_, _border_right_width_, _border_top_width_, _border_bottom_width_})
+ add_parsed_property(name, property_value(width, important));
+
+ for (auto name : {_border_left_style_, _border_right_style_, _border_top_style_, _border_bottom_style_})
+ add_parsed_property(name, property_value(style, important));
+
+ for (auto name : {_border_left_color_, _border_right_color_, _border_top_color_, _border_bottom_color_})
+ add_parsed_property(name, property_value(color, important));
+}
+
+// https://drafts.csswg.org/css-backgrounds/#border-shorthands
+// border-top, border-right, border-bottom, border-left
+void style::parse_border_side(string_id name, const css_token_vector& tokens, bool important, document_container* container)
+{
+ css_length width;
+ border_style style;
+ web_color color;
+
+ if (!parse_border_helper(tokens, container, width, style, color))
+ return;
+
+ add_parsed_property(_id(_s(name) + "-width"), property_value(width, important));
+ add_parsed_property(_id(_s(name) + "-style"), property_value(style, important));
+ add_parsed_property(_id(_s(name) + "-color"), property_value(color, important));
+}
+
+bool parse_length(const css_token& tok, css_length& length, int options, string keywords)
+{
+ return length.from_token(tok, options, keywords);
+}
+
+// parses 1 or 2 lengths, but always returns 2 lengths
+bool parse_two_lengths(const css_token_vector& tokens, css_length len[2], int options)
+{
+ auto n = tokens.size();
+ if (n != 1 && n != 2) return false;
+
+ css_length a, b;
+ if (!a.from_token(tokens[0], options)) return false;
+ if (n == 1) b = a;
+
+ if (n == 2 && !b.from_token(tokens[1], options)) return false;
+
+ len[0] = a;
+ len[1] = b;
+ return true;
+}
+
+// parses 1,2,3 or 4 tokens, returns number of tokens parsed or 0 if error
+template<class T, class... Args>
+int parse_1234_values(const css_token_vector& tokens, T result[4], bool (*parse)(const css_token&, T&, Args...), Args... args)
+{
+ if (tokens.size() > 4)
+ return 0;
+
for (size_t i = 0; i < tokens.size(); i++)
{
- len[i].fromString(tokens[i]);
+ if (!parse(tokens[i], result[i], args...))
+ return 0;
}
return (int)tokens.size();
}
-void style::parse_background(const string& val, const string& baseurl, bool important, document_container* container)
+int parse_1234_lengths(const css_token_vector& tokens, css_length len[4], int options, string keywords)
{
- string_vector tokens;
- split_string(val, tokens, ",", "", "(");
- if (tokens.empty()) return;
+ return parse_1234_values(tokens, len, parse_length, options, keywords);
+}
+
+// This function implements the logic of the kind "if two values are specified, the first one applies to
+// top and bottom, the second one to left and right". Works in conjunction with parse_1234_values.
+template<class T>
+void style::add_four_properties(string_id top_name, T val[4], int n, bool important)
+{
+ // These always go in trbl order, see comment for "CSS property names" in string_id.
+ string_id top = top_name; // top-left for corners
+ string_id right = string_id(top_name + 1);
+ string_id bottom = string_id(top_name + 2);
+ string_id left = string_id(top_name + 3);
+
+ // n 4 3 2 1
+ // top 0 0 0 0 0
+ // right 1 1 1 0 n>1
+ // bottom 2 2 0 0 n/3*2
+ // left 3 1 1 0 n/2+n/4
+
+ add_parsed_property(top, property_value(val[0], important));
+ add_parsed_property(right, property_value(val[n>1], important));
+ add_parsed_property(bottom, property_value(val[n/3*2], important));
+ add_parsed_property(left, property_value(val[n/2+n/4], important));
+}
+
+void style::parse_background(const css_token_vector& tokens, const string& baseurl, bool important, document_container* container)
+{
+ auto layers = parse_comma_separated_list(tokens);
+ if (layers.empty()) return;
web_color color;
- std::vector<background_image> images;
- int_vector repeats, origins, clips, attachments;
+ std::vector<image> images;
length_vector x_positions, y_positions;
size_vector sizes;
- background_gradient grad;
+ int_vector repeats, attachments, origins, clips;
- for (const auto& token : tokens)
+ for (size_t i = 0; i < layers.size(); i++)
{
background bg;
- if (!parse_one_background(token, container, bg))
+ if (!parse_bg_layer(layers[i], container, bg, i == layers.size() - 1))
return;
color = bg.m_color;
images.push_back(bg.m_image[0]);
- repeats.push_back(bg.m_repeat[0]);
- origins.push_back(bg.m_origin[0]);
- clips.push_back(bg.m_clip[0]);
- attachments.push_back(bg.m_attachment[0]);
x_positions.push_back(bg.m_position_x[0]);
y_positions.push_back(bg.m_position_y[0]);
sizes.push_back(bg.m_size[0]);
+ repeats.push_back(bg.m_repeat[0]);
+ attachments.push_back(bg.m_attachment[0]);
+ origins.push_back(bg.m_origin[0]);
+ clips.push_back(bg.m_clip[0]);
}
add_parsed_property(_background_color_, property_value(color, important));
add_parsed_property(_background_image_, property_value(images, important));
add_parsed_property(_background_image_baseurl_, property_value(baseurl, important));
- add_parsed_property(_background_repeat_, property_value(repeats, important));
- add_parsed_property(_background_origin_, property_value(origins, important));
- add_parsed_property(_background_clip_, property_value(clips, important));
- add_parsed_property(_background_attachment_, property_value(attachments, important));
add_parsed_property(_background_position_x_, property_value(x_positions, important));
add_parsed_property(_background_position_y_, property_value(y_positions, important));
add_parsed_property(_background_size_, property_value(sizes, important));
+ add_parsed_property(_background_repeat_, property_value(repeats, important));
+ add_parsed_property(_background_attachment_, property_value(attachments, important));
+ add_parsed_property(_background_origin_, property_value(origins, important));
+ add_parsed_property(_background_clip_, property_value(clips, important));
}
-bool style::parse_one_background(const string& val, document_container* container, background& bg)
+// https://drafts.csswg.org/css-backgrounds/#typedef-bg-layer
+// <bg-layer> = <bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style> || <attachment> || <visual-box> || <visual-box>
+// <final-bg-layer> = <bg-layer> || <'background-color'>
+bool style::parse_bg_layer(const css_token_vector& tokens, document_container* container, background& bg, bool final_layer)
{
bg.m_color = web_color::transparent;
- bg.m_image = { background_image() };
- bg.m_repeat = { background_repeat_repeat };
- bg.m_origin = { background_box_padding };
- bg.m_clip = { background_box_border };
- bg.m_attachment = { background_attachment_scroll };
+ bg.m_image = {{}};
bg.m_position_x = { css_length(0, css_units_percentage) };
bg.m_position_y = { css_length(0, css_units_percentage) };
bg.m_size = { css_size(css_length::predef_value(background_size_auto), css_length::predef_value(background_size_auto)) };
-
- if(val == "none")
- {
- return true;
- }
-
- string_vector tokens;
- split_string(val, tokens, " \t\n\r", "", "(");
+ bg.m_repeat = { background_repeat_repeat };
+ bg.m_attachment = { background_attachment_scroll };
+ bg.m_origin = { background_box_padding };
+ bg.m_clip = { background_box_border };
bool color_found = false;
bool image_found = false;
- bool origin_found = false;
- bool clip_found = false;
+ bool position_found = false;
bool repeat_found = false;
bool attachment_found = false;
+ bool origin_found = false;
+ bool clip_found = false;
- string position;
- for(const auto& token : tokens)
+ for (int i = 0; i < (int)tokens.size(); i++)
{
- int idx;
- if(token.substr(0, 3) == "url")
- {
- if (image_found) return false;
- string url;
- css::parse_css_url(token, url);
- background_image img;
- img.type = background_image::bg_image_type_url;
- img.url = url;
- bg.m_image = { img };
+ if (!color_found && final_layer && parse_color(tokens[i], bg.m_color, container))
+ color_found = true;
+ else if (!image_found && parse_bg_image(tokens[i], bg.m_image[0], container))
image_found = true;
- } else if( (idx = value_index(token, background_repeat_strings)) >= 0 )
- {
- if (repeat_found) return false;
- bg.m_repeat = { idx };
+ else if (!position_found && parse_bg_position_size(tokens, i, bg.m_position_x[0], bg.m_position_y[0], bg.m_size[0]))
+ position_found = true, i--;
+ // Note: double keyword <repeat-style> values are not supported yet.
+ else if (!repeat_found && parse_keyword(tokens[i], bg.m_repeat[0], background_repeat_strings))
repeat_found = true;
- } else if( (idx = value_index(token, background_attachment_strings)) >= 0 )
- {
- if (attachment_found) return false;
- bg.m_attachment = { idx };
+ else if (!attachment_found && parse_keyword(tokens[i], bg.m_attachment[0], background_attachment_strings))
attachment_found = true;
- } else if( (idx = value_index(token, background_box_strings)) >= 0 )
- {
- if(!origin_found)
- {
- bg.m_origin = { idx };
- origin_found = true;
- } else
- {
- if (clip_found) return false;
- bg.m_clip = { idx };
- clip_found = true;
- }
- } else if( value_in_list(token, background_position_strings) ||
- token.find('/') != string::npos ||
- t_isdigit(token[0]) ||
- token[0] == '+' ||
- token[0] == '-' ||
- token[0] == '.' )
- {
- position += " " + token;
- } else if (web_color::is_color(token, container))
- {
- if (color_found) return false;
- bg.m_color = web_color::from_string(token, container);
- color_found = true;
- } else if ( token.substr(0, 15) == "linear-gradient" || token.substr(0, 25) == "repeating-linear-gradient" ||
- token.substr(0, 15) == "radial-gradient" || token.substr(0, 25) == "repeating-radial-gradient" ||
- token.substr(0, 14) == "conic-gradient" || token.substr(0, 24) == "repeating-conic-gradient")
- {
- if (image_found) return false;
- background_image img;
- img.type = background_image::bg_image_type_gradient;
- css::parse_gradient(token, container, img.gradient);
- if(img.is_empty()) return false;
- bg.m_image = { img };
- image_found = true;
- }
+ // If one <visual-box> value is present then it sets both background-origin and background-clip to that value.
+ // If two values are present, then the first sets background-origin and the second background-clip.
+ else if (!origin_found && parse_keyword(tokens[i], bg.m_origin[0], background_box_strings))
+ origin_found = true, bg.m_clip[0] = bg.m_origin[0];
+ else if (!clip_found && parse_keyword(tokens[i], bg.m_clip[0], background_box_strings))
+ clip_found = true;
else
- {
return false;
- }
}
+ return true;
+}
+
+// <bg-position> [ / <bg-size> ]?
+bool parse_bg_position_size(const css_token_vector& tokens, int& index, css_length& x, css_length& y, css_size& size)
+{
+ if (!parse_bg_position(tokens, index, x, y, true))
+ return false;
+
+ if (at(tokens, index).ch != '/')
+ return true; // no [ / <bg-size> ]
- if (!position.empty())
+ if (!parse_bg_size(tokens, ++index, size))
{
- tokens.clear();
- split_string(position, tokens, "/");
+ index--; // restore index to point to '/'
+ return false; // has '/', but <bg-size> failed to parse
+ }
- if (tokens.size() > 2) return false;
+ return true; // both <bg-position> and <bg-size> parsed successfully
+}
- if (tokens.size() == 2 && !parse_one_background_size(tokens[1], bg.m_size[0]))
- return false;
+// https://drafts.csswg.org/css-backgrounds/#typedef-bg-size
+// <bg-size> = [ <length-percentage [0,∞]> | auto ]{1,2} | cover | contain
+bool parse_bg_size(const css_token_vector& tokens, int& index, css_size& size)
+{
+ css_length a, b;
+
+ if (!a.from_token(at(tokens, index), f_length_percentage | f_positive, background_size_strings))
+ return false;
+
+ // cover | contain
+ if (a.is_predefined() && a.predef() != background_size_auto)
+ {
+ size.width = size.height = a;
+ index++;
+ return true;
+ }
- if (!tokens.empty() && !parse_one_background_position(tokens[0], bg.m_position_x[0], bg.m_position_y[0]))
+ if (b.from_token(at(tokens, index + 1), f_length_percentage | f_positive, "auto"))
+ index += 2;
+ else
+ {
+ b.predef(background_size_auto); // If only one value is given the second is assumed to be auto.
+ index++;
+ }
+
+ size.width = a;
+ size.height = b;
+ return true;
+}
+
+bool is_one_of_predef(const css_length& x, int idx1, int idx2)
+{
+ return x.is_predefined() && is_one_of(x.predef(), idx1, idx2);
+}
+
+// https://drafts.csswg.org/css-backgrounds/#typedef-bg-position
+// https://www.w3.org/TR/css-values-4/#position
+// <bg-position> = [ left | center | right | top | bottom | <length-percentage> ] |
+// [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ]
+// Side-relative values (like bottom 10%) are not supported, so only 1 or 2 values are parsed (not 3 or 4).
+bool parse_bg_position(const css_token_vector& tokens, int& index, css_length& x, css_length& y, bool convert_keywords_to_percents)
+{
+ enum {
+ left = background_position_left,
+ right = background_position_right,
+ top = background_position_top,
+ bottom = background_position_bottom,
+ center = background_position_center
+ };
+
+ css_length a, b;
+
+ if (!a.from_token(at(tokens, index), f_length_percentage, background_position_strings))
+ return false;
+
+ if (!b.from_token(at(tokens, index + 1), f_length_percentage, background_position_strings))
+ {
+ // If only one value is specified, the second value is assumed to be center.
+ b.predef(center);
+
+ // fix wrong order
+ if (is_one_of_predef(a, top, bottom))
+ swap(a, b);
+
+ index++;
+ }
+ else // two values
+ {
+ // try to fix wrong order
+ // A pair of keywords can be reordered, while a combination of keyword and length or percentage cannot.
+ if ((is_one_of_predef(a, top, bottom) && b.is_predefined()) ||
+ (a.is_predefined() && is_one_of_predef(b, left, right)))
+ swap(a, b);
+
+ // check for wrong order
+ if (is_one_of_predef(a, top, bottom) || is_one_of_predef(b, left, right))
return false;
+
+ index += 2;
+ }
+
+ if (convert_keywords_to_percents)
+ {
+ if (a.is_predefined())
+ a.set_value(background_position_percentages[a.predef()], css_units_percentage);
+ if (b.is_predefined())
+ b.set_value(background_position_percentages[b.predef()], css_units_percentage);
}
+ x = a;
+ y = b;
return true;
}
-void style::parse_background_image(const string& val, document_container* container, const string& baseurl, bool important)
+void style::parse_background_image(const css_token_vector& tokens, const string& baseurl, bool important, document_container* container)
{
- string_vector tokens;
- split_string(val, tokens, ",", "", "(");
- if (tokens.empty()) return;
+ auto layers = parse_comma_separated_list(tokens);
+ if (layers.empty()) return;
+
+ std::vector<image> images;
- std::vector<background_image> images;
+ for (const auto& layer : layers)
+ {
+ image image;
+ if (layer.size() != 1)
+ return;
+ if (!parse_bg_image(layer[0], image, container))
+ return;
+ images.push_back(image);
+ }
- for (auto& token : tokens)
+ add_parsed_property(_background_image_, property_value(images, important));
+ add_parsed_property(_background_image_baseurl_, property_value(baseurl, important));
+}
+
+// <bg-image> = <image> | none
+// <image> = <url> | <gradient>
+bool parse_bg_image(const css_token& tok, image& bg_image, document_container* container)
+{
+ if (tok.ident() == "none")
{
- trim(token);
- if(token.substr(0, 3) == "url")
- {
- string url;
- css::parse_css_url(token, url);
- background_image img;
- img.type = background_image::bg_image_type_url;
- img.url = url;
- images.emplace_back(img);
- } else if (token.substr(0, 15) == "linear-gradient" || token.substr(0, 25) == "repeating-linear-gradient" ||
- token.substr(0, 15) == "radial-gradient" || token.substr(0, 25) == "repeating-radial-gradient" ||
- token.substr(0, 14) == "conic-gradient" || token.substr(0, 24) == "repeating-conic-gradient")
- {
- background_image img;
- img.type = background_image::bg_image_type_gradient;
- css::parse_gradient(token, container, img.gradient);
- if(!img.is_empty())
- {
- images.emplace_back(img);
- }
- }
+ bg_image.type = image::type_none;
+ return true;
}
- if(!images.empty())
+ string url;
+ if (parse_url(tok, url))
{
- add_parsed_property(_background_image_, property_value(images, important));
- add_parsed_property(_background_image_baseurl_, property_value(baseurl, important));
+ bg_image.type = image::type_url;
+ bg_image.url = url;
+ return true;
}
+
+ if (parse_gradient(tok, bg_image.m_gradient, container))
+ {
+ bg_image.type = image::type_gradient;
+ return true;
+ }
+
+ return false;
}
-void style::parse_keyword_comma_list(string_id name, const string& val, bool important)
+// https://drafts.csswg.org/css-values-4/#urls
+bool parse_url(const css_token& tok, string& url)
{
- string_vector tokens;
- split_string(val, tokens, ",");
- if (tokens.empty()) return;
+ if (tok.type == URL) // legacy syntax without quotes: url(x.com)
+ {
+ url = trim(tok.str);
+ return true;
+ }
+
+ if (tok.type == CV_FUNCTION && is_one_of(lowcase(tok.name), "url", "src") &&
+ // note: relying on whitespace having been removed from tok.value
+ tok.value.size() == 1 && tok.value[0].type == STRING)
+ {
+ url = trim(tok.value[0].str);
+ return true;
+ }
+
+ return false;
+}
+
+void style::parse_keyword_comma_list(string_id name, const css_token_vector& tokens, bool important)
+{
+ auto layers = parse_comma_separated_list(tokens);
+ if (layers.empty()) return;
int_vector vec;
- for (auto& token : tokens)
+ for (const auto& layer : layers)
{
- trim(token);
- int idx = value_index(token, m_valid_values[name]);
- if (idx == -1) return;
+ int idx;
+ if (layer.size() != 1) return;
+ if (!parse_keyword(layer[0], idx, m_valid_values[name])) return;
vec.push_back(idx);
}
add_parsed_property(name, property_value(vec, important));
}
-void style::parse_background_position(const string& val, bool important)
+void style::parse_background_position(const css_token_vector& tokens, bool important)
{
- string_vector tokens;
- split_string(val, tokens, ",");
- if (tokens.empty()) return;
+ auto layers = parse_comma_separated_list(tokens);
+ if (layers.empty()) return;
length_vector x_positions, y_positions;
- for (const auto& token : tokens)
+ for (const auto& layer : layers)
{
css_length x, y;
- if(!parse_one_background_position(token, x, y)) return;
+ int index = 0;
+ if (!parse_bg_position(layer, index, x, y, true) || index != (int)layer.size())
+ return;
+
x_positions.push_back(x);
y_positions.push_back(y);
}
-
+
add_parsed_property(_background_position_x_, property_value(x_positions, important));
add_parsed_property(_background_position_y_, property_value(y_positions, important));
}
-bool style::parse_one_background_position(const string& val, css_length& x, css_length& y)
+void style::parse_background_size(const css_token_vector& tokens, bool important)
{
- string_vector pos;
- split_string(val, pos, split_delims_spaces);
-
- if (pos.empty() || pos.size() > 2)
+ auto layers = parse_comma_separated_list(tokens);
+ if (layers.empty()) return;
+
+ size_vector sizes;
+
+ for (const auto& layer : layers)
{
- return false;
+ css_size size;
+ int index = 0;
+ if (!parse_bg_size(layer, index, size) || index != (int)layer.size())
+ return;
+
+ sizes.push_back(size);
}
-
- if (pos.size() == 1)
+
+ add_parsed_property(_background_size_, property_value(sizes, important));
+}
+
+bool parse_font_weight(const css_token& tok, css_length& weight)
+{
+ if (int idx = value_index(tok.ident(), font_weight_strings); idx >= 0)
{
- if (value_in_list(pos[0], "left;right;center"))
- {
- x.fromString(pos[0], "left;right;center");
- y.set_value(50, css_units_percentage);
- }
- else if (value_in_list(pos[0], "top;bottom;center"))
- {
- y.fromString(pos[0], "top;bottom;center");
- x.set_value(50, css_units_percentage);
- }
- else
- {
- x.fromString(pos[0], "left;right;center");
- y.set_value(50, css_units_percentage);
- }
+ weight.predef(idx);
+ return true;
}
- else if (pos.size() == 2)
+
+ // https://drafts.csswg.org/css-fonts/#font-weight-absolute-values
+ // Note: fractional values are allowed.
+ if (tok.type == NUMBER && tok.n.number >= 1 && tok.n.number <= 1000)
{
- if (value_in_list(pos[0], "left;right"))
- {
- x.fromString(pos[0], "left;right;center");
- y.fromString(pos[1], "top;bottom;center");
- }
- else if (value_in_list(pos[0], "top;bottom"))
+ weight.set_value(tok.n.number, css_units_none);
+ return true;
+ }
+
+ return false;
+}
+
+// <font-style> || <font-variant-css2> || <font-weight>
+// None of the allowed values intersect with <font-size>, it cannot consume <font-size>.
+// Note: <font-weight> can be a number >=1, but <font-size> inside <font> is <length-percentage>,
+// so it can be only 0 number. <font-size> as a standalone property does allow <number>s in quirks mode.
+bool parse_font_style_variant_weight(const css_token_vector& tokens, int& index,
+ int& style, int& variant, css_length& weight)
+{
+ bool style_found = false;
+ bool variant_found = false;
+ bool weight_found = false;
+ bool res = false;
+
+ int i = index, count = 0;
+ while (i < (int)tokens.size() && count++ < 3)
+ {
+ const auto& tok = tokens[i++];
+ // All three properties can have value "normal", and it changes nothing because initial
+ // values of all three properties are "normal".
+ if (tok.ident() == "normal")
{
- x.fromString(pos[1], "left;right;center");
- y.fromString(pos[0], "top;bottom;center");
- }
- else if (value_in_list(pos[1], "left;right"))
+ index++;
+ res = true;
+ } else if (!style_found && parse_keyword(tok, style, font_style_strings))
{
- x.fromString(pos[1], "left;right;center");
- y.fromString(pos[0], "top;bottom;center");
+ style_found = true;
+ index++;
+ res = true;
}
- else if (value_in_list(pos[1], "top;bottom"))
+ else if (!variant_found && parse_keyword(tok, variant, font_variant_strings))
{
- x.fromString(pos[0], "left;right;center");
- y.fromString(pos[1], "top;bottom;center");
+ variant_found = true;
+ index++;
+ res = true;
}
- else
+ else if (!weight_found && parse_font_weight(tok, weight))
{
- x.fromString(pos[0], "left;right;center");
- y.fromString(pos[1], "top;bottom;center");
- }
+ weight_found = true;
+ index++;
+ res = true;
+ } else break;
}
+ return res;
+}
- if (x.is_predefined())
+// https://www.w3.org/TR/css-values-4/#custom-idents
+bool is_custom_ident(const css_token& tok)
+{
+ if (tok.type != IDENT) return false;
+ // Custom identifiers are case-sensitive, but they should not case-insensitively match any of
+ // CSS-wide keywords or "default".
+ return !is_one_of(lowcase(tok.name), "default", "initial", "inherit", "unset");
+}
+
+// https://drafts.csswg.org/css-fonts/#propdef-font-family
+// font-family = [ <family-name> | <generic-family> ]#
+// <family-name> = <string> | <custom-ident>+
+// <generic-family> = generic( <custom-ident>+ ) | <string> | <custom-ident>+
+bool parse_font_family(const css_token_vector& tokens, string& font_family)
+{
+ auto list = parse_comma_separated_list(tokens);
+ if (list.empty()) return false;
+
+ string result;
+ for (const auto& name : list)
{
- switch (x.predef())
+ if (name.size() == 1 && name[0].type == STRING)
{
- case 0:
- x.set_value(0, css_units_percentage);
- break;
- case 1:
- x.set_value(100, css_units_percentage);
- break;
- case 2:
- x.set_value(50, css_units_percentage);
- break;
+ //result.push_back(name[0].str);
+ result += name[0].str + ',';
+ continue;
}
- }
- if (y.is_predefined())
- {
- switch (y.predef())
+
+ // Otherwise: name must be a list of <custom-ident>s
+ // Note: generic( <custom-ident>+ ) is not supported
+ string str;
+ for (const auto& tok : name)
{
- case 0:
- y.set_value(0, css_units_percentage);
- break;
- case 1:
- y.set_value(100, css_units_percentage);
- break;
- case 2:
- y.set_value(50, css_units_percentage);
- break;
+ if (!is_custom_ident(tok)) return false;
+ str += tok.name + ' ';
}
+ //result.push_back(trim(str));
+ result += trim(str) + ',';
}
+ result.resize(result.size() - 1); // remove last ','
+ font_family = result;
return true;
}
-void style::parse_background_size(const string& val, bool important)
+// https://developer.mozilla.org/en-US/docs/Web/CSS/font
+// https://drafts.csswg.org/css-fonts/#font-prop
+// font = <font-style-weight>? <font-size> [ / <line-height> ]? <font-family>
+// font = <system-family-name*>
+// <font-style-weight> = <font-style> || <font-variant-css2> || <font-weight> || <font-width-css3*>
+// values marked * are not supported
+void style::parse_font(css_token_vector tokens, bool important)
{
- string_vector tokens;
- split_string(val, tokens, ",");
- if (tokens.empty()) return;
-
- size_vector sizes;
-
- for (const auto& token : tokens)
+ // initial values
+ int style = font_style_normal;
+ int variant = font_variant_normal;
+ css_length weight = css_length::predef_value(font_weight_normal);
+ css_length size = css_length::predef_value(font_size_medium);
+ css_length line_height = css_length::predef_value(line_height_normal);
+ string font_family; // this argument is mandatory, no need to set initial value
+
+ if(tokens.size() == 1 && (tokens[0].type == STRING || tokens[0].type == IDENT) && value_in_list(tokens[0].str, font_system_family_name_strings))
{
- css_size size;
- if (!parse_one_background_size(token, size)) return;
- sizes.push_back(size);
- }
+ font_family = tokens[0].str;
+ } else
+ {
+ int index = 0;
+ parse_font_style_variant_weight(tokens, index, style, variant, weight);
- add_parsed_property(_background_size_, property_value(sizes, important));
-}
+ // font-size = <absolute-size> | <relative-size> | <length-percentage [0,∞]> | math
+ if (!size.from_token(at(tokens, index), f_length_percentage | f_positive, font_size_strings))
+ return;
+ index++;
-bool style::parse_one_background_size(const string& val, css_size& size)
-{
- string_vector res;
- split_string(val, res, split_delims_spaces);
- if (res.empty())
- {
- return false;
- }
+ if (at(tokens, index).ch == '/')
+ {
+ index++;
+ // https://drafts.csswg.org/css2/#propdef-line-height
+ // line-height = normal | <number> | <length> | <percentage>
+ if (!line_height.from_token(at(tokens, index), f_number | f_length_percentage, line_height_strings))
+ return;
+ index++;
+ }
- size.width.fromString(res[0], background_size_strings);
- if (res.size() > 1)
- {
- size.height.fromString(res[1], background_size_strings);
- }
- else
- {
- size.height.predef(background_size_auto);
+ remove(tokens, 0, index);
+ if (!parse_font_family(tokens, font_family))
+ return;
}
- return true;
+ add_parsed_property(_font_style_, property_value(style, important));
+ add_parsed_property(_font_variant_, property_value(variant, important));
+ add_parsed_property(_font_weight_, property_value(weight, important));
+ add_parsed_property(_font_size_, property_value(size, important));
+ add_parsed_property(_line_height_, property_value(line_height, important));
+ add_parsed_property(_font_family_, property_value(font_family, important));
}
-void style::parse_font(const string& val, bool important)
+// https://developer.mozilla.org/en-US/docs/Web/CSS/flex
+// https://drafts.csswg.org/css-flexbox/#flex-property
+// flex = none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
+void style::parse_flex(const css_token_vector& tokens, bool important)
{
- add_parsed_property(_font_style_, property_value(font_style_normal, important));
- add_parsed_property(_font_variant_, property_value(font_variant_normal, important));
- add_parsed_property(_font_weight_, property_value(font_weight_normal, important));
- add_parsed_property(_font_size_, property_value(font_size_medium, important));
- add_parsed_property(_line_height_, property_value(line_height_normal, important));
-
- string_vector tokens;
- split_string(val, tokens, " ", "", "\"");
-
- int idx;
- bool is_family = false;
- string font_family;
- for(const auto& token : tokens)
+ auto n = tokens.size();
+ if (n > 3) return;
+
+ const auto& a = at(tokens, 0);
+ const auto& b = at(tokens, 1);
+ const auto& c = at(tokens, 2);
+
+ struct flex
{
- if(is_family)
- {
- font_family += token;
- continue;
- }
+ float m_grow = 1; // flex-grow is set to 1 when omitted
+ float m_shrink = 1;
+ css_length m_basis = 0;
- if((idx = value_index(token, font_style_strings)) >= 0)
+ bool grow(const css_token& tok)
{
- if(idx == 0)
- {
- add_parsed_property(_font_style_, property_value(font_style_normal, important));
- add_parsed_property(_font_variant_, property_value(font_variant_normal, important));
- add_parsed_property(_font_weight_, property_value(font_weight_normal, important));
- } else
- {
- add_parsed_property(_font_style_, property_value(idx, important));
- }
- } else if((idx = value_index(token, font_weight_strings)) >= 0)
+ if (tok.type != NUMBER || tok.n.number < 0) return false;
+ m_grow = tok.n.number;
+ return true;
+ }
+ bool shrink(const css_token& tok)
{
- add_parsed_property(_font_weight_, property_value(idx, important));
- } else if((idx = value_index(token, font_variant_strings)) >= 0)
+ if (tok.type != NUMBER || tok.n.number < 0) return false;
+ m_shrink = tok.n.number;
+ return true;
+ }
+ // unitless_zero_allowed enforces the rule "A unitless zero that is not already preceded by two flex factors must be interpreted as a flex factor."
+ bool basis(const css_token& tok, bool unitless_zero_allowed = false)
{
- add_parsed_property(_font_variant_, property_value(idx, important));
+ if (!unitless_zero_allowed && tok.type == NUMBER && tok.n.number == 0)
+ return false;
+ return m_basis.from_token(tok, f_length_percentage | f_positive, flex_basis_strings);
}
- else if(t_isdigit(token[0]) || token[0] == '.' ||
- value_in_list(token, font_size_strings) || token.find('/') != string::npos)
+ };
+ flex flex;
+
+ if (n == 1)
+ {
+ string_id ident = _id(a.ident());
+ if (is_one_of(ident, _initial_, _auto_, _none_))
{
- string_vector szlh;
- split_string(token, szlh, "/");
- if(!szlh.empty())
+ css_length _auto = css_length::predef_value(flex_basis_auto);
+
+ switch (ident)
{
- auto size = css_length::from_string(szlh[0], font_size_strings, -1);
- add_parsed_property(_font_size_, property_value(size, important));
-
- if (szlh.size() == 2)
- {
- auto height = css_length::from_string(szlh[1], "normal", -1);
- add_parsed_property(_line_height_, property_value(height, important));
- }
+ case _initial_: flex = {0, 1, _auto}; break;
+ case _auto_: flex = {1, 1, _auto}; break; // can be handled by else
+ case _none_: flex = {0, 0, _auto}; break;
+ default:;
}
- } else
+ }
+ else
{
- is_family = true;
- font_family += token;
+ bool ok = flex.grow(a) || flex.basis(a);
+ if (!ok) return;
}
}
- add_parsed_property(_font_family_, property_value(font_family, important));
-}
-
-void style::parse_flex(const string& val, bool important)
-{
- css_length _auto = css_length::predef_value(flex_basis_auto);
-
- if (val == "initial")
- {
- // 0 1 auto
- add_parsed_property(_flex_grow_, property_value(0.f, important));
- add_parsed_property(_flex_shrink_, property_value(1.f, important));
- add_parsed_property(_flex_basis_, property_value(_auto, important));
- }
- else if (val == "auto")
+ else if (n == 2)
{
- // 1 1 auto
- add_parsed_property(_flex_grow_, property_value(1.f, important));
- add_parsed_property(_flex_shrink_, property_value(1.f, important));
- add_parsed_property(_flex_basis_, property_value(_auto, important));
+ // <number> <number>
+ // <number> <basis>
+ // <basis> <number>
+ bool ok =
+ (flex.grow(a) && (flex.shrink(b) || flex.basis(b))) ||
+ (flex.basis(a) && flex.grow(b));
+
+ if (!ok) return;
}
- else if (val == "none")
+ else // n == 3
{
- // 0 0 auto
- add_parsed_property(_flex_grow_, property_value(0.f, important));
- add_parsed_property(_flex_shrink_, property_value(0.f, important));
- add_parsed_property(_flex_basis_, property_value(_auto, important));
+ // <number> <number> <basis>
+ // <basis> <number> <number>
+ bool ok =
+ (flex.grow(a) && flex.shrink(b) && flex.basis(c, true)) ||
+ (flex.basis(a) && flex.grow(b) && flex.shrink(c));
+
+ if (!ok) return;
}
- else
+
+ add_parsed_property(_flex_grow_, property_value(flex.m_grow, important));
+ add_parsed_property(_flex_shrink_, property_value(flex.m_shrink, important));
+ add_parsed_property(_flex_basis_, property_value(flex.m_basis, important));
+}
+
+// flex-flow = <'flex-direction'> || <'flex-wrap'>
+void style::parse_flex_flow(const css_token_vector& tokens, bool important)
+{
+ // initial values: https://developer.mozilla.org/en-US/docs/Web/CSS/flex-flow#formal_definition
+ int flex_direction = flex_direction_row;
+ int flex_wrap = flex_wrap_nowrap;
+
+ bool direction_found = false;
+ bool wrap_found = false;
+
+ for (const auto& token : tokens)
{
- string_vector tokens;
- split_string(val, tokens, " ");
- if (tokens.size() == 3)
- {
- float grow = t_strtof(tokens[0]);
- float shrink = t_strtof(tokens[1]);
- auto basis = css_length::from_string(tokens[2], flex_basis_strings, -1);
- if(!basis.is_predefined() && basis.units() == css_units_none && basis.val() == 0)
- {
- basis.set_value(basis.val(), css_units_px);
- }
-
- add_parsed_property(_flex_grow_, property_value(grow, important));
- add_parsed_property(_flex_shrink_, property_value(shrink, important));
- add_parsed_property(_flex_basis_, property_value(basis, important));
- }
- else if (tokens.size() == 2)
- {
- float grow = t_strtof(tokens[0]);
- add_parsed_property(_flex_grow_, property_value(grow, important));
-
- if (litehtml::is_number(tokens[1]))
- {
- float shrink = t_strtof(tokens[1]);
- add_parsed_property(_flex_shrink_, property_value(shrink, important));
- add_parsed_property(_flex_basis_, property_value(css_length(0), important));
- }
- else
- {
- auto basis = css_length::from_string(tokens[1], flex_basis_strings, -1);
- add_parsed_property(_flex_basis_, property_value(basis, important));
- }
- }
- else if (tokens.size() == 1)
- {
- if (is_number(tokens[0]))
- {
- float grow = t_strtof(tokens[0]);
- add_parsed_property(_flex_grow_, property_value(grow, important));
- add_parsed_property(_flex_shrink_, property_value(1.f, important));
- add_parsed_property(_flex_basis_, property_value(css_length(0), important));
- }
- else
- {
- auto basis = css_length::from_string(tokens[0], flex_basis_strings, -1);
- add_parsed_property(_flex_grow_, property_value(1.f, important));
- add_parsed_property(_flex_shrink_, property_value(1.f, important));
- add_parsed_property(_flex_basis_, property_value(basis, important));
- }
- }
+ if (!direction_found && parse_keyword(token, flex_direction, flex_direction_strings))
+ direction_found = true;
+ else if (!wrap_found && parse_keyword(token, flex_wrap, flex_wrap_strings))
+ wrap_found = true;
+ else
+ return;
}
+
+ add_parsed_property(_flex_direction_, property_value(flex_direction, important));
+ add_parsed_property(_flex_wrap_, property_value(flex_wrap, important));
}
-void style::parse_align_self(string_id name, const string& val, bool important)
+// https://www.w3.org/TR/css-align/#align-self-property
+// value = auto | normal | stretch | [ [first | last]? && baseline ] | [safe | unsafe]? <self-position>
+// <self-position> = center | start | end | self-start | self-end | flex-start | flex-end
+// https://www.w3.org/TR/css-align/#align-items-property
+// same as align-self, except auto is not allowed
+void style::parse_align_self(string_id name, const css_token_vector& tokens, bool important)
{
- string_vector tokens;
- split_string(val, tokens, " ");
- if(tokens.size() == 1)
+ auto n = tokens.size();
+ if (n > 2)
+ return;
+ if (tokens[0].type != IDENT || (n == 2 && tokens[1].type != IDENT))
+ return;
+
+ string a = tokens[0].ident();
+
+ if (name == _align_items_ && a == "auto")
+ return;
+
+ if (n == 1)
{
- int idx = value_index(val, m_valid_values[name]);
+ int idx = value_index(a, flex_align_items_strings);
if (idx >= 0)
- {
add_parsed_property(name, property_value(idx, important));
- }
- } else
+ return;
+ }
+
+ // Otherwise: n == 2
+
+ string b = tokens[1].ident();
+
+ if (a == "baseline") swap(a, b);
+ if (b == "baseline" && is_one_of(a, "first", "last"))
{
- int val1 = 0;
- int val2 = -1;
- for(auto &token : tokens)
- {
- if(token == "first")
- {
- val1 |= flex_align_items_first;
- } else if(token == "last")
- {
- val1 |= flex_align_items_last;
- } else if(token == "safe")
- {
- val1 |= flex_align_items_safe;
- } else if(token == "unsafe")
- {
- val1 |= flex_align_items_unsafe;
- } else
- {
- int idx = value_index(token, m_valid_values[name]);
- if(idx >= 0)
- {
- val2 = idx;
- }
- }
- }
- if(val2 >= 0)
- {
- add_parsed_property(name, property_value(val1 | val2, important));
- }
+ int idx = flex_align_items_baseline | (a == "first" ? flex_align_items_first : flex_align_items_last);
+ add_parsed_property(name, property_value(idx, important));
+ return;
+ }
+
+ // <overflow-position> <self-position>
+ int idx = value_index(b, self_position_strings);
+ if (idx >= 0 && is_one_of(a, "safe", "unsafe"))
+ {
+ idx |= (a == "safe" ? flex_align_items_safe : flex_align_items_unsafe);
+ add_parsed_property(name, property_value(idx, important));
}
}
@@ -1234,24 +1433,70 @@ const property_value& style::get_property(string_id name) const
{
return it->second;
}
- static property_value _invalid(invalid(), false);
- return _invalid;
+ static property_value dummy;
+ return dummy;
+}
+
+// var( <custom-property-name> , <declaration-value>? )
+bool check_var_syntax(const css_token_vector& args)
+{
+ if (args.empty()) return false;
+
+ string name = args[0].ident();
+ if (name.substr(0, 2) != "--" || name.size() <= 2)
+ return false;
+
+ if (args.size() > 1 && args[1].ch != ',')
+ return false;
+ if (args.size() > 2 && !is_declaration_value(args, 2))
+ return false;
+
+ return true;
}
-void style::subst_vars_(string& str, const html_tag* el)
+// https://drafts.csswg.org/css-variables/#using-variables
+// var( <custom-property-name> , <declaration-value>? )
+// returns true if one var() was substituted
+// returns true if there was error or var() was not found
+bool subst_var(css_token_vector& tokens, const html_tag* el, std::set<string_id>& used_vars)
{
- while (true)
+ for (int i = 0; i < (int)tokens.size(); i++)
{
- auto start = str.find("var(");
- if (start == string::npos) break;
- if (start > 0 && isalnum(str[start - 1])) break;
- auto end = str.find(')', start + 4);
- if (end == string::npos) break;
- auto name = str.substr(start + 4, end - start - 4);
- trim(name);
- string val = el->get_custom_property(_id(name), "");
- str.replace(start, end - start + 1, val);
+ auto& tok = tokens[i];
+ if (tok.type == CV_FUNCTION && lowcase(tok.name) == "var")
+ {
+ auto args = tok.value; // copy is intentional
+ if (!check_var_syntax(args)) return false;
+
+ auto name = _id(args[0].name);
+ if (name in used_vars) return false; // dependency cycle https://drafts.csswg.org/css-variables/#cycles
+ used_vars.insert(name);
+
+ css_token_vector value;
+ if (el->get_custom_property(name, value))
+ {
+ remove(tokens, i);
+ insert(tokens, i, value);
+ }
+ else // custom property not defined
+ {
+ if (args.size() == 1) return false; // default value not provided
+ remove(args, 0, 2);
+ remove(tokens, i);
+ insert(tokens, i, args);
+ }
+ return true;
+ }
+ if (tok.is_component_value() && subst_var(tok.value, el, used_vars))
+ return true;
}
+ return false;
+}
+
+void subst_vars_(string_id name, css_token_vector& tokens, const html_tag* el)
+{
+ std::set<string_id> used_vars = {name};
+ while (subst_var(tokens, el, used_vars));
}
void style::subst_vars(const html_tag* el)
@@ -1260,12 +1505,12 @@ void style::subst_vars(const html_tag* el)
{
if (prop.second.m_has_var)
{
- string str = prop.second.get<string>();
- subst_vars_(str, el);
+ auto& value = prop.second.get<css_token_vector>();
+ subst_vars_(prop.first, value, el);
// re-adding the same property
- // if it is a custom property it will be readded as a string
+ // if it is a custom property it will be readded as a css_token_vector
// if it is a standard css property it will be parsed and properly added as typed property
- add_property(prop.first, str, "", prop.second.m_important, el->get_document()->container());
+ add_property(prop.first, value, "", prop.second.m_important, el->get_document()->container());
}
}
}