diff --git a/CMakeLists.txt b/CMakeLists.txt index 082bc434f..415d0def1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,10 @@ set(SOURCE_LITEHTML src/flex_line.cpp src/background.cpp src/gradient.cpp + include/litehtml/render_grid.h + src/render_grid.cpp + include/litehtml/grid.h + src/grid.cpp ) set(HEADER_LITEHTML diff --git a/include/litehtml/css_parser.h b/include/litehtml/css_parser.h index cbd88e3b8..fbb772061 100644 --- a/include/litehtml/css_parser.h +++ b/include/litehtml/css_parser.h @@ -43,6 +43,7 @@ template css_token_vector normalize(Input input, int options = 0, keep_whitespace_fn keep_whitespace = 0); vector parse_comma_separated_list(const css_token_vector& tokens); +vector parse_slash_separated_list(const css_token_vector& tokens); bool is_declaration_value(const css_token_vector& tokens, int index = 0); bool is_any_value(const css_token_vector& tokens); bool skip_whitespace(const css_token_vector& tokens, int& index); diff --git a/include/litehtml/element.h b/include/litehtml/element.h index 67dd1ae25..0a6b0a422 100644 --- a/include/litehtml/element.h +++ b/include/litehtml/element.h @@ -208,7 +208,7 @@ namespace litehtml css().get_display() == display_flex || css().get_display() == display_table || css().get_display() == display_list_item || - css().get_display() == display_flex) + css().get_display() == display_grid) { return true; } diff --git a/include/litehtml/grid.h b/include/litehtml/grid.h new file mode 100644 index 000000000..d9ed789b9 --- /dev/null +++ b/include/litehtml/grid.h @@ -0,0 +1,285 @@ +#ifndef LITEHTML_GRID_H +#define LITEHTML_GRID_H + +#include +#include +#include "string_id.h" + +namespace litehtml +{ + enum grid_auto_flow + { + grid_auto_flow_row = 0x1, + grid_auto_flow_column = 0x2, + grid_auto_flow_dense = 0x10 + }; + + // = + // auto | + // | + // [ [ | ] && ? ] | + // [ span && [ || ] ] + struct css_grid_line + { + typedef std::vector vector; + + struct span_def + { + string_id custom_ident; + int value; + + span_def() : custom_ident(empty_id),value(1) {} + span_def(string_id _custom_ident, int _val) : custom_ident(_custom_ident), value(_val) {} + }; + + struct integer_custom_ident_def + { + string_id custom_ident; + int value; + integer_custom_ident_def() : custom_ident(empty_id),value(0) {} + integer_custom_ident_def(string_id _custom_ident, int _val) : custom_ident(_custom_ident), value(_val) {} + }; + + struct custom_ident_def + { + string_id custom_ident; + + custom_ident_def() : custom_ident(empty_id) {} + explicit custom_ident_def(string_id _custom_ident) : custom_ident(_custom_ident) {} + }; + + struct auto_def { }; + + std::variant line; + + css_grid_line() : line(auto_def()) {} + + bool from_tokens(const css_token_vector& tokens); + template bool is() const { return std::holds_alternative(line); } + template const T& get() const { return std::get(*this); } + }; + + struct css_grid_template_areas + { + std::vector areas; + css_grid_template_areas() : areas() {} + + bool is_none() const { return areas.empty(); } + bool from_tokens(const css_token_vector& tokens); + bool from_token(const css_token& token); + void clear() + { + areas.clear(); + } + }; + + // grid-template-columns = + // none | + // | + // | + // subgrid ? + struct css_grid_template + { + struct none {}; + + struct line_names; + struct track_size; + struct track_repeat; + struct fixed_size; + struct auto_repeat; + struct fixed_repeat; + struct name_repeat; + + // = + // [ ? [ | ] ]+ ? + struct track_list + { + std::vector> value; + + bool parse(const css_token_vector& tokens); + }; + + // = + // [ ? [ | ] ]* ? [ ? [ | ] ]* ? + struct auto_track_list + { + std::vector> value; + + bool parse(const css_token_vector& tokens); + }; + + // = + // '[' * ']' + struct line_names + { + std::vector names; + + bool parse(const css_token& token); + }; + + // = + // repeat( [ | auto-fill ] , + ) + struct name_repeat + { + struct auto_fill {}; + + std::variant arg1; + std::vector arg2; + + bool parse(const css_token& token); + }; + + // subgrid + // = + // [ | ]+ + struct subgrid + { + std::vector> line_name_list; + + bool parse(const css_token_vector& tokens); + }; + + // = + // | + // | + // min-content | + // max-content | + // auto + struct track_breadth + { + struct flex + { + int n; + flex() : n(0) {} + }; + + std::variant value; + + bool parse(css_token_vector::const_iterator& iter, const css_token_vector::const_iterator& end); + }; + + // = + // | + // min-content | + // max-content | + // auto + struct inflexible_breadth + { + css_length value; + + bool parse(const css_token& token) + { + return value.from_token(token, f_length_percentage, minmax_predef_str); + } + }; + + // = + // | + // + // + // = + // + struct fixed_breadth + { + css_length value; + + bool parse(const css_token& token) + { + return value.from_token(token, f_length_percentage); + } + }; + + struct minmax + { + std::variant arg1; + std::variant arg2; + + bool parse(const css_token& token); + }; + + struct fit_content + { + fixed_breadth value; + + bool parse(const css_token& token); + }; + + // = + // | + // minmax( , ) | + // fit-content( ) + struct track_size + { + std::variant value; + + bool parse(css_token_vector::const_iterator& iter, const css_token_vector::const_iterator& end); + }; + + // = + // repeat( [ ] , [ ? ]+ ? ) + struct track_repeat + { + int arg1; + std::vector> arg2; + + bool parse(const css_token& token); + }; + + // = + // | + // minmax( , ) | + // minmax( , ) + struct fixed_size + { + std::variant value; + + bool parse(const css_token& token); + }; + + // = + // repeat( [ ] , [ ? ]+ ? ) + struct fixed_repeat + { + int arg1; + std::vector> arg2; + + fixed_repeat() : arg1(0) {} + bool parse(const css_token& token); + }; + + enum auto_repeat_type + { + auto_fill, + auto_fit, + }; + // = + // repeat( [ auto-fill | auto-fit ] , [ ? ]+ ? ) + struct auto_repeat + { + auto_repeat_type arg1; + std::vector> arg2; + + bool parse(const css_token& token); + }; + + static constexpr const char* minmax_predef_str = "auto;min-content;max-content"; + enum minmax_predef + { + minmax_auto, + minmax_min_content, + minmax_max_content, + }; + + std::variant value; + + bool from_tokens(const css_token_vector& tokens); + }; + + struct css_grid_auto_row_columns + { + std::vector value; + bool from_tokens(const css_token_vector& tokens); + }; +} + +#endif //LITEHTML_GRID_H diff --git a/include/litehtml/html.h b/include/litehtml/html.h index cd0500aab..7048c2509 100644 --- a/include/litehtml/html.h +++ b/include/litehtml/html.h @@ -30,11 +30,12 @@ namespace litehtml string index_value(int index, const string& strings, char delim = ';'); bool value_in_list(const string& val, const string& strings, char delim = ';'); string::size_type find_close_bracket(const string& s, string::size_type off, char open_b = '(', char close_b = ')'); - void split_string(const string& str, string_vector& tokens, const string& delims = whitespace, const string& delims_preserve = "", const string& quote = "\""); - string_vector split_string(const string& str, const string& delims = whitespace, const string& delims_preserve = "", const string& quote = "\""); + void split_string(const string& str, string_vector& tokens, const string& delims = whitespace, const string& delims_preserve = "", const string& quote = "\"", const string& chars_to_trim = ""); + string_vector split_string(const string& str, const string& delims = whitespace, const string& delims_preserve = "", const string& quote = "\"", const string& chars_to_trim = ""); void join_string(string& str, const string_vector& tokens, const string& delims); double t_strtod(const char* string, char** endPtr = nullptr); string get_escaped_string(const string& in_str); + bool starts_width(const std::string &str, const std::string_view &substr); template bool is_one_of(X x, A a) diff --git a/include/litehtml/render_block.h b/include/litehtml/render_block.h index 1874be332..7469f8821 100644 --- a/include/litehtml/render_block.h +++ b/include/litehtml/render_block.h @@ -24,6 +24,7 @@ namespace litehtml virtual void fix_line_width(element_float /*flt*/, const containing_block_context &/*containing_block_size*/, formatting_context* /*fmt_ctx*/) {} + void children_to_blocks(); public: explicit render_item_block(std::shared_ptr src_el) : render_item(std::move(src_el)) diff --git a/include/litehtml/render_grid.h b/include/litehtml/render_grid.h new file mode 100644 index 000000000..fcf1dce64 --- /dev/null +++ b/include/litehtml/render_grid.h @@ -0,0 +1,27 @@ +#ifndef LITEHTML_RENDER_GRID_H +#define LITEHTML_RENDER_GRID_H + +#include "render_block.h" + +namespace litehtml +{ + class render_item_grid : public render_item_block + { + int _render_content(int x, int y, bool second_pass, const containing_block_context &self_size, formatting_context* fmt_ctx) override; + + public: + explicit render_item_grid(std::shared_ptr src_el) : render_item_block(std::move(src_el)) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + std::shared_ptr init() override; + + int get_first_baseline() override; + int get_last_baseline() override; + }; +} + +#endif //LITEHTML_RENDER_GRID_H diff --git a/include/litehtml/render_item.h b/include/litehtml/render_item.h index d3535429c..3d1714c3e 100644 --- a/include/litehtml/render_item.h +++ b/include/litehtml/render_item.h @@ -376,6 +376,16 @@ namespace litehtml return false; } + bool is_grid_item() const + { + auto par = parent(); + if(par && (par->css().get_display() == display_inline_grid || par->css().get_display() == display_grid)) + { + return true; + } + return false; + } + int render(int x, int y, const containing_block_context& containing_block_size, formatting_context* fmt_ctx, bool second_pass = false); void apply_relative_shift(const containing_block_context &containing_block_size); void calc_outlines( int parent_width ); diff --git a/include/litehtml/string_id.h b/include/litehtml/string_id.h index a488b3279..a0e3c8e81 100644 --- a/include/litehtml/string_id.h +++ b/include/litehtml/string_id.h @@ -341,6 +341,25 @@ STRING_ID( _device_aspect_ratio_, _color_index_, _monochrome_, + + _grid_column_, + _grid_row_, + _grid_column_start_, + _grid_column_end_, + _grid_row_start_, + _grid_row_end_, + _grid_area_, + _grid_template_areas_, + _grid_template_rows_, + _grid_template_columns_, + _grid_template_, + _grid_, + _grid_auto_flow_, + _grid_auto_rows_, + _grid_auto_columns_, + _gap_, + _row_gap_, + _column_gap_ ) #undef STRING_ID extern const string_id empty_id; // _id("") diff --git a/include/litehtml/style.h b/include/litehtml/style.h index 256fd8f3c..dd7badee2 100644 --- a/include/litehtml/style.h +++ b/include/litehtml/style.h @@ -2,6 +2,8 @@ #define LH_STYLE_H #include "css_tokenizer.h" +#include +#include "grid.h" namespace litehtml { @@ -21,7 +23,11 @@ namespace litehtml string, string_vector, size_vector, - css_token_vector + css_token_vector, + css_grid_line, + css_grid_template_areas, + css_grid_template, + css_grid_auto_row_columns > { bool m_important = false; @@ -86,7 +92,13 @@ namespace litehtml void parse_flex_flow(const css_token_vector& tokens, bool important); void parse_flex(const css_token_vector& tokens, bool important); void parse_align_self(string_id name, const css_token_vector& tokens, bool important); - + + void parse_grid_row_col(string_id name, const css_token_vector& tokens, bool important); + void parse_grid_area(const css_token_vector& tokens, bool important); + bool parse_grid_template(const css_token_vector& tokens, bool important); + void parse_grid(const css_token_vector& tokens, bool important); + void parse_grid_auto_flow(const css_token_vector& tokens, bool important); + void add_parsed_property(string_id name, const property_value& propval); void add_length_property(string_id name, css_token val, string keywords, int options, bool important); template void add_four_properties(string_id top_name, T val[4], int n, bool important); diff --git a/include/litehtml/types.h b/include/litehtml/types.h index 5097108f7..856c6eff1 100644 --- a/include/litehtml/types.h +++ b/include/litehtml/types.h @@ -317,7 +317,7 @@ namespace litehtml } }; -#define style_display_strings "none;block;inline;inline-block;inline-table;list-item;table;table-caption;table-cell;table-column;table-column-group;table-footer-group;table-header-group;table-row;table-row-group;inline-text;flex;inline-flex" +#define style_display_strings "none;block;inline;inline-block;inline-table;list-item;table;table-caption;table-cell;table-column;table-column-group;table-footer-group;table-header-group;table-row;table-row-group;inline-text;flex;inline-flex;grid;inline-grid" enum style_display { @@ -339,6 +339,8 @@ namespace litehtml display_inline_text, display_flex, display_inline_flex, + display_grid, + display_inline_grid, }; #define font_size_strings "xx-small;x-small;small;medium;large;x-large;xx-large;smaller;larger" @@ -487,7 +489,7 @@ namespace litehtml clear_both }; -#define css_units_strings "none;%;in;cm;mm;em;ex;pt;pc;px;vw;vh;vmin;vmax;rem" +#define css_units_strings "none;%;in;cm;mm;em;ex;pt;pc;px;vw;vh;vmin;vmax;rem;fr" enum css_units : byte // see css_length { @@ -506,6 +508,7 @@ namespace litehtml css_units_vmin, css_units_vmax, css_units_rem, + css_units_fr, }; #define background_attachment_strings "scroll;fixed" @@ -878,7 +881,7 @@ namespace litehtml render_fixed_only, }; - const char* const split_delims_spaces = " \t\r\n\f\v"; +#define split_delims_spaces " \t\r\n\f\v" // List of the Void Elements (can't have any contents) const char* const void_elements = "area;base;br;col;command;embed;hr;img;input;keygen;link;meta;param;source;track;wbr"; diff --git a/src/css_parser.cpp b/src/css_parser.cpp index 24e49a78c..7b5012ff4 100644 --- a/src/css_parser.cpp +++ b/src/css_parser.cpp @@ -471,6 +471,26 @@ vector parse_comma_separated_list(const css_token_vector& toke return result; } +vector parse_slash_separated_list(const css_token_vector& tokens) +{ + vector result; + + css_token_vector list; + for (auto& tok : tokens) + { + if (tok.type == '/') // Note: EOF token is not stored in arrays + { + result.push_back(list); + list.clear(); + continue; + } + list.push_back(tok); + } + result.push_back(list); + + return result; +} + // https://drafts.csswg.org/css-syntax-3/#typedef-any-value // assumes that tokens have been componentized bool is_any_value(const css_token_vector& tokens) diff --git a/src/css_properties.cpp b/src/css_properties.cpp index f8cbec47f..5ccd83817 100644 --- a/src/css_properties.cpp +++ b/src/css_properties.cpp @@ -447,6 +447,9 @@ void litehtml::css_properties::compute_flex(const html_tag* el, const document:: } else if(m_display == display_inline_flex) { m_display = display_flex; + } else if(m_display == display_inline_grid) + { + m_display = display_grid; } } } diff --git a/src/element.cpp b/src/element.cpp index fb4c2c294..74fe0c056 100644 --- a/src/element.cpp +++ b/src/element.cpp @@ -6,6 +6,7 @@ #include "render_inline.h" #include "render_table.h" #include "el_before_after.h" +#include "render_grid.h" namespace litehtml { @@ -53,7 +54,8 @@ bool element::is_inline() const css().get_display() == display_inline_table || css().get_display() == display_inline_block || css().get_display() == display_inline_text || - css().get_display() == display_inline_flex) + css().get_display() == display_inline_flex || + css().get_display() == display_inline_grid) { return true; } @@ -64,7 +66,8 @@ bool element::is_inline_box() const { if( css().get_display() == display_inline_table || css().get_display() == display_inline_block || - css().get_display() == display_inline_flex) + css().get_display() == display_inline_flex || + css().get_display() == display_inline_grid) { return true; } @@ -158,6 +161,9 @@ std::shared_ptr element::create_render_item(const std::shared_ptr(shared_from_this()); + } else if(css().get_display() == display_grid || css().get_display() == display_inline_grid) + { + ret = std::make_shared(shared_from_this()); } if(ret) { @@ -277,7 +283,8 @@ bool element::is_block_formatting_context() const if(m_css.get_display() == display_block) { auto par = parent(); - if(par && (par->css().get_display() == display_inline_flex || par->css().get_display() == display_flex)) + if(par && (par->css().get_display() == display_inline_flex || par->css().get_display() == display_flex || + par->css().get_display() == display_inline_grid || par->css().get_display() == display_grid)) { return true; } @@ -286,6 +293,8 @@ bool element::is_block_formatting_context() const m_css.get_display() == display_table_cell || m_css.get_display() == display_inline_flex || m_css.get_display() == display_flex || + m_css.get_display() == display_inline_grid || + m_css.get_display() == display_grid || m_css.get_display() == display_table_caption || is_root() || m_css.get_float() != float_none || diff --git a/src/grid.cpp b/src/grid.cpp new file mode 100644 index 000000000..a8ccd8ccd --- /dev/null +++ b/src/grid.cpp @@ -0,0 +1,538 @@ +#include "html.h" +#include "types.h" +#include "grid.h" +#include "css_parser.h" +#include "style.h" + +bool litehtml::css_grid_line::from_tokens(const css_token_vector& tokens) +{ + if(tokens.size() == 1 && tokens[0].type == IDENT && tokens[0].name == "auto") + { + line = auto_def(); + return true; + } + + bool span = false; + int integer = 0; + std::string custom_ident; + for(const auto& token : tokens) + { + if(token.type == IDENT) + { + if(token.name == "span") + { + span = true; + } + if(custom_ident.empty()) + { + custom_ident = token.name; + } else + return false; + } else if(token.type == NUMBER && token.n.number_type == css_number_integer) + { + // Only one number is allowed + // Number without units required. 0 is invalid + if(integer != 0 || token.n.number_type != css_number_integer || token.n.number == 0) return false; + integer = (int) token.n.number; + } else + return false; + } + + if(span) + { + // Number >= 1 or custom ident are required + if(integer < 1 && custom_ident.empty()) return false; + line = span_def(_id(custom_ident), integer); + return true; + } + if(integer != 0) + { + line = integer_custom_ident_def(_id(custom_ident), integer); + return true; + } + if(!custom_ident.empty()) + { + line = custom_ident_def(_id(custom_ident)); + return true; + } + + return false; +} + +bool litehtml::css_grid_template_areas::from_tokens(const css_token_vector& tokens) +{ + if(tokens.size() == 1 && tokens[0].type == IDENT && tokens[0].name == "none") + { + return true; + } + + for(auto& line_tok : tokens) + { + if(!from_token(line_tok)) + { + return false; + } + } + return true; +} + +bool litehtml::css_grid_template_areas::from_token(const litehtml::css_token &token) +{ + if(token.type == STRING) + { + trim(token.str, "\"'"); + string_vector cols; + split_string(token.str, cols, split_delims_spaces, "", "", split_delims_spaces); + if (cols.empty()) return false; + areas.emplace_back(cols); + return true; + } + return false; +} + +// grid-template-columns = +// none | +// | +// | +// subgrid ? +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::from_tokens(const css_token_vector& tokens) +{ + if(tokens.size() == 1 && tokens[0].type == IDENT && tokens[0].name == "none") + { + value = none(); + return true; + } + + track_list tl; + if(tl.parse(tokens)) + { + value = tl; + return true; + } + + auto_track_list atl; + if(atl.parse(tokens)) + { + value = atl; + return true; + } + + subgrid sg; + if(sg.parse(tokens)) + { + value = sg; + return true; + } + return false; +} + +// = +// '[' * ']' +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::line_names::parse(const litehtml::css_token &token) +{ + if(token.type == SQUARE_BLOCK) + { + for(const auto &item : token.value) + { + if (item.type == IDENT) + { + names.emplace_back(item.name); + } else return false; + } + return true; + } + return false; +} + +// = +// repeat( [ | auto-fill ] , + ) +//https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::name_repeat::parse(const litehtml::css_token &token) +{ + if(token.type == CV_FUNCTION && token.name == "repeat") + { + auto args = parse_comma_separated_list(token.value); + if(args.size() != 2) return false; + + if(args[0].size() != 1) return false; + if(args[0][0].type == NUMBER && args[0][0].n.number_type == css_number_integer && args[0][0].n.number >= 1) + { + arg1 = (int) args[0][0].n.number; + } else if(args[0][0].type == IDENT && args[0][0].name == "auto-fill") + { + arg1 = auto_fill(); + } else return false; + + for(const auto &item : args[1]) + { + line_names val; + if(val.parse(item)) + { + arg2.emplace_back(val); + } else + return false; + } + return true; + } + return false; +} + +// subgrid +// = +// [ | ]+ +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::subgrid::parse(const litehtml::css_token_vector &tokens) +{ + if(tokens.size() <= 1) return false; + + if(tokens[0].type == IDENT && tokens[0].name == "subgrid") + { + for(const auto &item : tokens) + { + line_names ln; + name_repeat rpt; + if(ln.parse(item)) + { + line_name_list.emplace_back(ln); + } else if(rpt.parse(item)) + { + line_name_list.emplace_back(rpt); + } else return false; + } + return true; + } + + return false; +} + +// = +// | +// | +// min-content | +// max-content | +// auto +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::track_breadth::parse(css_token_vector::const_iterator& iter, const css_token_vector::const_iterator& end) +{ + auto iter_ = iter; + + if(iter_->type == IDENT && iter_->name == "subgrid") + { + iter_ = std::next(iter_); + if(iter_ == end) return false; + if(iter_->type == NUMBER && iter_->n.number_type == css_number_integer && iter_->n.number >= 0) + { + flex f; + f.n = (int) iter_->n.number; + value = f; + iter = iter_; + return true; + } + } + + css_length len; + if(len.from_token((*iter_), f_length_percentage, minmax_predef_str)) + { + value = len; + return true; + } + + return false; +} + +// = +// [ ? [ | ] ]+ ? +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::track_list::parse(const litehtml::css_token_vector &tokens) +{ + for(auto iter = tokens.cbegin(); iter != tokens.cend(); iter++) + { + line_names ln; + track_size ts; + track_repeat tr; + if(ln.parse(*iter)) + { + value.emplace_back(ln); + continue; + } + if(ts.parse(iter, tokens.cend())) + { + value.emplace_back(ts); + continue; + } + if(tr.parse(*iter)) + { + value.emplace_back(ts); + continue; + } + return false; + } + return !value.empty(); +} + +// = +// | +// minmax( , ) | +// fit-content( ) +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::track_size::parse(css_token_vector::const_iterator &iter, const css_token_vector::const_iterator& end) +{ + minmax mm; + if(mm.parse(*iter)) + { + value = mm; + return true; + } + fit_content fc; + if(fc.parse(*iter)) + { + value = fc; + return true; + } + track_breadth tb; + if(tb.parse(iter, end)) + { + value = tb; + return true; + } + + return false; +} + +bool litehtml::css_grid_template::minmax::parse(const css_token& token) +{ + if(token.type == CV_FUNCTION && token.name == "minmax") + { + auto args = parse_comma_separated_list(token.value); + if(args.size() != 2) return false; + + if(args[0].size() == 1) + { + fixed_breadth fb; + inflexible_breadth ifb; + if(fb.parse(args[0][0])) + { + arg1 = fb; + } else if(ifb.parse(args[0][0])) + { + arg1 = ifb; + } else return false; + } else return false; + + if(!args[1].empty()) + { + track_breadth tb; + fixed_breadth fb; + auto iter = args[1].cbegin(); + if(tb.parse(iter, args[1].cend())) + { + arg2 = tb; + } else if(fb.parse(args[0][0])) + { + arg2 = fb; + } else return false; + } else return false; + return true; + } + return false; +} + +// = +// | +// minmax( , ) | +// minmax( , ) +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::fixed_size::parse(const litehtml::css_token &token) +{ + fixed_breadth fb; + if(fb.parse(token)) + { + value = fb; + return true; + } + + minmax mm; + if(mm.parse(token)) + { + value = mm; + return true; + } + + return false; +} + +// = +// repeat( [ ] , [ ? ]+ ? ) +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::fixed_repeat::parse(const litehtml::css_token &token) +{ + if(token.type == CV_FUNCTION && token.name == "repeat") + { + auto args = parse_comma_separated_list(token.value); + if(args.size() != 2) return false; + + if(args[0].size() == 1 && args[0][0].type == NUMBER and args[0][0].n.number_type == css_number_integer && args[0][0].n.number >= 1) + { + arg1 = (int) args[0][0].n.number; + } else return false; + + for(const auto &item : args[1]) + { + line_names ln; + fixed_size fs; + if(ln.parse(item)) + { + arg2.emplace_back(ln); + } else if(fs.parse(item)) + { + arg2.emplace_back(fs); + } else + return false; + } + return !arg2.empty(); + } + + return false; +} + +// = +// repeat( [ ] , [ ? ]+ ? ) +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::track_repeat::parse(const litehtml::css_token &token) +{ + if(token.type == CV_FUNCTION && token.name == "repeat") + { + auto args = parse_comma_separated_list(token.value); + if(args.size() != 2) return false; + + if(args[0].size() == 1 && args[0][0].type == NUMBER and args[0][0].n.number_type == css_number_integer && args[0][0].n.number >= 1) + { + arg1 = (int) args[0][0].n.number; + } else return false; + + for(auto iter = args[1].cbegin(); iter != args[1].cend(); iter++) + { + line_names ln; + track_size fs; + if(ln.parse(*iter)) + { + arg2.emplace_back(ln); + } else if(fs.parse(iter, args[1].cend())) + { + arg2.emplace_back(fs); + } else + return false; + } + return !arg2.empty(); + } + + return false; +} + +// = +// repeat( [ auto-fill | auto-fit ] , [ ? ]+ ? ) +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::auto_repeat::parse(const litehtml::css_token &token) +{ + if(token.type == CV_FUNCTION && token.name == "repeat") + { + auto args = parse_comma_separated_list(token.value); + if(args.size() != 2) return false; + + if(args[0].size() == 1 && args[0][0].type == IDENT and is_one_of(args[0][0].name, "auto-fill", "auto-fit")) + { + if(args[0][0].name == "auto-fill") + { + arg1 = auto_fill; + } else + { + arg1 = auto_fit; + } + } else return false; + + for(const auto &item : args[1]) + { + line_names ln; + fixed_size fs; + if(ln.parse(item)) + { + arg2.emplace_back(ln); + } else if(fs.parse(item)) + { + arg2.emplace_back(fs); + } else + return false; + } + return !arg2.empty(); + } + + return false; +} + +// = +// [ ? [ | ] ]* ? [ ? [ | ] ]* ? +// https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns#formal_syntax +bool litehtml::css_grid_template::auto_track_list::parse(const litehtml::css_token_vector &tokens) +{ + int auto_repeat_found = false; + for(const auto &token : tokens) + { + line_names ln; + fixed_size fs; + auto_repeat ar; + fixed_repeat fr; + + if(ln.parse(token)) + { + value.emplace_back(ln); + continue; + } + if(fs.parse(token)) + { + value.emplace_back(fs); + continue; + } + if(ar.parse(token)) + { + if(auto_repeat_found) return false; + value.emplace_back(ar); + auto_repeat_found = true; + continue; + } + if(fr.parse(token)) + { + value.emplace_back(fr); + continue; + } + return false; + } + return auto_repeat_found; +} + +bool litehtml::css_grid_template::fit_content::parse(const litehtml::css_token &token) +{ + if(token.type == CV_FUNCTION && token.name == "repeat") + { + if(token.value.size() == 1) + { + return value.parse(token.value[0]); + } + } + return false; +} + +bool litehtml::css_grid_auto_row_columns::from_tokens(const litehtml::css_token_vector &tokens) +{ + for(auto iter = tokens.cbegin(); iter != tokens.cend(); ++iter) + { + css_grid_template::track_size ts; + if(ts.parse(iter, tokens.cend())) + { + value.emplace_back(ts); + } else + return false; + } + return true; +} diff --git a/src/html.cpp b/src/html.cpp index 27f1cfe55..0fc64ec67 100644 --- a/src/html.cpp +++ b/src/html.cpp @@ -120,14 +120,14 @@ bool value_in_list( const string& val, const string& strings, char delim ) return false; } -string_vector split_string(const string& str, const string& delims, const string& delims_preserve, const string& quote) +string_vector split_string(const string& str, const string& delims, const string& delims_preserve, const string& quote, const string& chars_to_trim) { string_vector result; - split_string(str, result, delims, delims_preserve, quote); + split_string(str, result, delims, delims_preserve, quote, chars_to_trim); return result; } -void split_string(const string& str, string_vector& tokens, const string& delims, const string& delims_preserve, const string& quote) +void split_string(const string& str, string_vector& tokens, const string& delims, const string& delims_preserve, const string& quote, const string& chars_to_trim) { if(str.empty() || (delims.empty() && delims_preserve.empty())) { @@ -174,6 +174,10 @@ void split_string(const string& str, string_vector& tokens, const string& delims token = str.substr(token_start, token_len); if(!token.empty()) { + if(!chars_to_trim.empty()) + { + trim(token, chars_to_trim); + } tokens.push_back( token ); } if(token_end != string::npos && !delims_preserve.empty() && delims_preserve.find_first_of(str[token_end]) != string::npos) @@ -307,4 +311,15 @@ bool is_number(const string& string, const bool allow_dot) { return true; } +bool starts_width(const std::string &str, const std::string_view &substr) +{ + if(substr.length() > str.length()) return false; + if(substr.length() == str.length()) return str == substr; + for(size_t i = 0; i < substr.length(); i++) + { + if(substr[i] != str[i]) return false; + } + return true; +} + } // namespace litehtml \ No newline at end of file diff --git a/src/iterators.cpp b/src/iterators.cpp index f0a87555c..730b763c3 100644 --- a/src/iterators.cpp +++ b/src/iterators.cpp @@ -64,6 +64,7 @@ bool litehtml::inline_selector::select(const std::shared_ptr& el) el->src_el()->css().get_display() == display_inline_table || el->src_el()->css().get_display() == display_inline_block || el->src_el()->css().get_display() == display_inline_flex || + el->src_el()->css().get_display() == display_inline_grid || el->src_el()->css().get_float() != float_none) { return true; diff --git a/src/render_block.cpp b/src/render_block.cpp index c1fdff8b3..8e2df33d6 100644 --- a/src/render_block.cpp +++ b/src/render_block.cpp @@ -337,3 +337,74 @@ int litehtml::render_item_block::_render(int x, int y, const containing_block_co return ret_width + content_offset_width(); } + +void litehtml::render_item_block::children_to_blocks() +{ + auto doc = src_el()->get_document(); + decltype(m_children) new_children; + decltype(m_children) inlines; + + auto convert_inlines = [&]() + { + if(!inlines.empty()) + { + // Find last not space + auto not_space = std::find_if(inlines.rbegin(), inlines.rend(), [&](const std::shared_ptr& el) + { + return !el->src_el()->is_space(); + }); + if(not_space != inlines.rend()) + { + // Erase all spaces at the end + inlines.erase((not_space.base()), inlines.end()); + } + + auto anon_el = std::make_shared(src_el()); + auto anon_ri = std::make_shared(anon_el); + for(const auto& inl : inlines) + { + anon_ri->add_child(inl); + } + anon_ri->parent(shared_from_this()); + + new_children.push_back(anon_ri->init()); + inlines.clear(); + } + }; + + for (const auto& el : m_children) + { + if(el->src_el()->css().get_display() == display_inline_text) + { + if(!inlines.empty()) + { + inlines.push_back(el); + } else + { + if (!el->src_el()->is_white_space()) + { + inlines.push_back(el); + } + } + } else + { + convert_inlines(); + if(el->src_el()->is_block_box()) + { + // Add block boxes as is + el->parent(shared_from_this()); + new_children.push_back(el->init()); + } else + { + // Wrap inlines with anonymous block box + auto anon_el = std::make_shared(el->src_el()); + auto anon_ri = std::make_shared(anon_el); + anon_ri->add_child(el->init()); + anon_ri->parent(shared_from_this()); + new_children.push_back(anon_ri->init()); + } + } + } + convert_inlines(); + children() = new_children; +} diff --git a/src/render_flex.cpp b/src/render_flex.cpp index 7b497cf31..a3069d68d 100644 --- a/src/render_flex.cpp +++ b/src/render_flex.cpp @@ -322,74 +322,7 @@ std::list litehtml::render_item_flex::get_lines(const liteh std::shared_ptr litehtml::render_item_flex::init() { - auto doc = src_el()->get_document(); - decltype(m_children) new_children; - decltype(m_children) inlines; - - auto convert_inlines = [&]() - { - if(!inlines.empty()) - { - // Find last not space - auto not_space = std::find_if(inlines.rbegin(), inlines.rend(), [&](const std::shared_ptr& el) - { - return !el->src_el()->is_space(); - }); - if(not_space != inlines.rend()) - { - // Erase all spaces at the end - inlines.erase((not_space.base()), inlines.end()); - } - - auto anon_el = std::make_shared(src_el()); - auto anon_ri = std::make_shared(anon_el); - for(const auto& inl : inlines) - { - anon_ri->add_child(inl); - } - anon_ri->parent(shared_from_this()); - - new_children.push_back(anon_ri->init()); - inlines.clear(); - } - }; - - for (const auto& el : m_children) - { - if(el->src_el()->css().get_display() == display_inline_text) - { - if(!inlines.empty()) - { - inlines.push_back(el); - } else - { - if (!el->src_el()->is_white_space()) - { - inlines.push_back(el); - } - } - } else - { - convert_inlines(); - if(el->src_el()->is_block_box()) - { - // Add block boxes as is - el->parent(shared_from_this()); - new_children.push_back(el->init()); - } else - { - // Wrap inlines with anonymous block box - auto anon_el = std::make_shared(el->src_el()); - auto anon_ri = std::make_shared(anon_el); - anon_ri->add_child(el->init()); - anon_ri->parent(shared_from_this()); - new_children.push_back(anon_ri->init()); - } - } - } - convert_inlines(); - children() = new_children; - + children_to_blocks(); return shared_from_this(); } diff --git a/src/render_grid.cpp b/src/render_grid.cpp new file mode 100644 index 000000000..df9c744c6 --- /dev/null +++ b/src/render_grid.cpp @@ -0,0 +1,26 @@ +#include "html.h" +#include "types.h" +#include "render_grid.h" + +int litehtml::render_item_grid::_render_content(int x, int y, bool second_pass, + const litehtml::containing_block_context &self_size, + litehtml::formatting_context *fmt_ctx) +{ + return render_item_block::_render_content(x, y, second_pass, self_size, fmt_ctx); +} + +std::shared_ptr litehtml::render_item_grid::init() +{ + children_to_blocks(); + return shared_from_this(); +} + +int litehtml::render_item_grid::get_first_baseline() +{ + return render_item::get_first_baseline(); +} + +int litehtml::render_item_grid::get_last_baseline() +{ + return render_item::get_last_baseline(); +} diff --git a/src/render_item.cpp b/src/render_item.cpp index fa39510e5..53a29ef90 100644 --- a/src/render_item.cpp +++ b/src/render_item.cpp @@ -644,7 +644,7 @@ void litehtml::render_item::draw_children(uint_ptr hdc, int x, int y, const posi if (el->src_el()->is_inline() && el->src_el()->css().get_float() == float_none && !el->src_el()->is_positioned()) { el->src_el()->draw(hdc, pos.x, pos.y, clip, el); - if (el->src_el()->css().get_display() == display_inline_block || el->src_el()->css().get_display() == display_inline_flex) + if (el->src_el()->css().get_display() == display_inline_block || el->src_el()->css().get_display() == display_inline_flex || el->src_el()->css().get_display() == display_inline_grid) { el->draw_stacking_context(hdc, pos.x, pos.y, clip, false); process = false; @@ -754,7 +754,8 @@ std::shared_ptr litehtml::render_item::get_child_by_point(in { if(el->src_el()->css().get_display() == display_inline_block || el->src_el()->css().get_display() == display_inline_table || - el->src_el()->css().get_display() == display_inline_flex) + el->src_el()->css().get_display() == display_inline_flex || + el->src_el()->css().get_display() == display_inline_grid) { ret = el->get_element_by_point(el_pos.x, el_pos.y, client_x, client_y); el = nullptr; @@ -781,7 +782,9 @@ std::shared_ptr litehtml::render_item::get_child_by_point(in } else { if( el->src_el()->css().get_float() == float_none && - el->src_el()->css().get_display() != display_inline_block && el->src_el()->css().get_display() != display_inline_flex) + el->src_el()->css().get_display() != display_inline_block && + el->src_el()->css().get_display() != display_inline_flex && + el->src_el()->css().get_display() != display_inline_grid) { element::ptr child = el->get_child_by_point(el_pos.x, el_pos.y, client_x, client_y, flag, zindex); if(child) diff --git a/src/style.cpp b/src/style.cpp index e72694f19..ea4245edf 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -482,6 +482,103 @@ void style::add_property(string_id name, const css_token_vector& value, const st add_parsed_property(name, property_value(str, important)); break; + case _grid_column_start_: + case _grid_column_end_: + case _grid_row_start_: + case _grid_row_end_: + { + css_grid_line line; + if(line.from_tokens(value)) + { + add_parsed_property(name, property_value(line, important)); + } + } + break; + + case _grid_row_: + case _grid_column_: + parse_grid_row_col(name, value, important); + break; + + case _grid_area_: + parse_grid_area(value, important); + break; + + case _grid_template_areas_: + { + css_grid_template_areas tpl_areas; + if(tpl_areas.from_tokens(value)) + { + add_parsed_property(name, property_value(tpl_areas, important)); + } + } + break; + + case _grid_template_columns_: + case _grid_template_rows_: + { + css_grid_template tpl; + if(tpl.from_tokens(value)) + { + add_parsed_property(name, property_value(tpl, important)); + } + } + break; + + case _grid_template_: + parse_grid_template(value, important); + break; + + case _grid_auto_flow_: + parse_grid_auto_flow(value, important); + break; + + case _grid_: + parse_grid(value, important); + break; + + case _grid_auto_columns_: + case _grid_auto_rows_: + { + css_grid_auto_row_columns auto_rc; + if(auto_rc.from_tokens(value)) + { + add_parsed_property(name, property_value(auto_rc, important)); + } + } + break; + + case _row_gap_: + case _column_gap_: + add_length_property(name, val, "normal", f_number|f_length_percentage|f_positive, important); + break; + + case _gap_: + { + if(value.size() == 1) + { + css_length length; + if (length.from_token(value[0], f_number|f_length_percentage|f_positive, "normal")) + { + add_parsed_property(_row_gap_, property_value(length, important)); + add_parsed_property(_column_gap_, property_value(length, important)); + } + } else if(value.size() == 2) + { + css_length row_gap; + css_length column_gap; + if (row_gap.from_token(value[0], f_number|f_length_percentage|f_positive, "normal")) + { + if (column_gap.from_token(value[1], f_number|f_length_percentage|f_positive, "normal")) + { + add_parsed_property(_row_gap_, property_value(row_gap, important)); + add_parsed_property(_column_gap_, property_value(column_gap, important)); + } + } + } + } + break; + // ============================= CUSTOM PROPERTY ============================= // https://drafts.csswg.org/css-variables-2/#defining-variables @@ -1496,4 +1593,266 @@ void style::subst_vars(const html_tag* el) } } +void style::parse_grid_row_col(string_id name, const css_token_vector &tokens, bool important) +{ + auto parts = parse_slash_separated_list(tokens); + if(parts.size() > 2 || parts.empty()) return; + + string_id id_start = name == _grid_row_ ? _grid_row_start_ : _grid_column_start_; + string_id id_end = name == _grid_row_ ? _grid_row_end_ : _grid_column_end_; + + if(parts.size() == 1) + { + css_grid_line line; + if(line.from_tokens(parts[0])) + { + add_parsed_property(id_start, property_value(line, important)); + if(line.is()) + { + add_parsed_property(id_end, property_value(line, important)); + } + } else if(tokens.size() >= 2) + { + css_grid_line line1; + css_grid_line line2; + if(line1.from_tokens(parts[0]) && line2.from_tokens(parts[1])) + { + add_parsed_property(id_start, property_value(line1, important)); + add_parsed_property(id_end, property_value(line2, important)); + } + } + } +} + +void style::parse_grid_area(const css_token_vector &tokens, bool important) +{ + auto parts = parse_slash_separated_list(tokens); + if(parts.size() > 4) return; + + css_grid_line row_start; + css_grid_line column_start; + css_grid_line row_end; + css_grid_line column_end; + + if(!tokens.empty()) + { + if(row_start.from_tokens(parts[0])) + { + if (row_start.is()) + { + column_start = row_start; + row_end = row_start; + column_end = row_start; + } + } else return; + } + + if(parts.size() >= 2) + { + if(column_start.from_tokens(parts[1])) + { + if (column_start.is()) + { + column_end = row_start; + } + } else return; + } + + if(parts.size() >= 3) + { + row_end.from_tokens(parts[2]); + } + + if(parts.size() >= 4) + { + column_end.from_tokens(parts[3]); + } + + add_parsed_property(_grid_row_start_, property_value(row_start, important)); + add_parsed_property(_grid_row_end_, property_value(row_end, important)); + add_parsed_property(_grid_column_start_, property_value(column_start, important)); + add_parsed_property(_grid_column_end_, property_value(column_end, important)); +} + +// none | +// [ <'grid-template-rows'> / <'grid-template-columns'> ] | +// [ ? ? ? ]+ [ / ]? +bool style::parse_grid_template(const css_token_vector &tokens, bool important) +{ + if(tokens.size() == 1 && tokens[0].type == IDENT && tokens[0].name == "none") + { + add_parsed_property(_grid_template_columns_, property_value(css_grid_template(), important)); + add_parsed_property(_grid_template_rows_, property_value(css_grid_template(), important)); + add_parsed_property(_grid_template_areas_, property_value(css_grid_template_areas(), important)); + return false; + } + + auto parts = parse_slash_separated_list(tokens); + if(parts.empty() || parts.size() > 2) return false; + + if(parts.size() == 2) + { + css_grid_template rows; + css_grid_template cols; + if(rows.from_tokens(parts[0])) + { + if(cols.from_tokens(parts[0])) + { + add_parsed_property(_grid_template_columns_, property_value(cols, important)); + add_parsed_property(_grid_template_rows_, property_value(rows, important)); + add_parsed_property(_grid_template_areas_, property_value(css_grid_template_areas(), important)); + return false; + } + } + } + + css_grid_template_areas areas; + css_grid_template::track_list tl; + int sb = 0; + for(auto p_iter = parts[0].cbegin(); p_iter != parts[0].cend(); p_iter++) + { + if((*p_iter).type == SQUARE_BLOCK) + { + css_grid_template::line_names ln; + if(ln.parse((*p_iter))) + { + if(sb == 1) + { + // filling in auto for any missing sizes + if(!std::holds_alternative(tl.value.back())) + { + css_token_vector t = {css_token(IDENT, "auto")}; + css_grid_template::track_size ts; + auto iter = t.cbegin(); + if(ts.parse(iter, t.cend())) + { + tl.value.emplace_back(ts); + } + } + sb = 0; + } else + { + sb++; + } + tl.value.emplace_back(ln); + } else + return false; + } else if((*p_iter).type == STRING) + { + if(!areas.from_token((*p_iter))) return false; + } else + { + css_grid_template::track_size ts; + if(ts.parse(p_iter, parts[0].cend())) + { + tl.value.emplace_back(ts); + } else + return false; + } + } + + if(areas.is_none()) return false; + + css_grid_template rows; + rows.value = tl; + css_grid_template cols; + if(parts.size() == 2) + { + if(!cols.from_tokens(parts[1])) return false; + } + add_parsed_property(_grid_template_columns_, property_value(cols, important)); + add_parsed_property(_grid_template_rows_, property_value(rows, important)); + add_parsed_property(_grid_template_areas_, property_value(areas, important)); + return true; +} + +// <'grid-template'> | +// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | +// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> +void style::parse_grid(const css_token_vector &tokens, bool important) +{ + if(parse_grid_template(tokens, important)) return; + + auto parts = parse_slash_separated_list(tokens); + if(parts.size() != 2) return; + + // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> + if(parts[0][0].type == IDENT && is_one_of(parts[0][0].name, "auto-flow", "dense")) + { + css_grid_template cols; + if(!cols.from_tokens(parts[1])) return; + int auto_flow = 0; + if(parts[0][0].name == "auto-flow") + { + parts[0].erase(parts[0].begin()); + auto_flow = grid_auto_flow_row; + } + if(parts[0][0].type == IDENT && parts[0][0].name == "dense") + { + parts[0].erase(parts[0].begin()); + auto_flow = grid_auto_flow_dense; + } + if(!(auto_flow & grid_auto_flow_row)) return; + + css_grid_auto_row_columns auto_rows; + if(!auto_rows.from_tokens(parts[0])) return; + + add_parsed_property(_grid_auto_flow_, property_value(auto_flow, important)); + add_parsed_property(_grid_auto_rows_, property_value(auto_rows, important)); + add_parsed_property(_grid_template_columns_, property_value(cols, important)); + } + // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? + else if(parts[1][0].type == IDENT && is_one_of(parts[1][0].name, "auto-flow", "dense")) + { + css_grid_template rows; + if(!rows.from_tokens(parts[0])) return; + int auto_flow = 0; + if(parts[1][0].name == "auto-flow") + { + parts[1].erase(parts[0].begin()); + auto_flow = grid_auto_flow_column; + } + if(parts[1][0].type == IDENT && parts[1][0].name == "dense") + { + parts[1].erase(parts[1].begin()); + auto_flow = grid_auto_flow_dense; + } + if(!(auto_flow & grid_auto_flow_column)) return; + + css_grid_auto_row_columns auto_cols; + if(!auto_cols.from_tokens(parts[1])) return; + + add_parsed_property(_grid_auto_flow_, property_value(auto_flow, important)); + add_parsed_property(_grid_auto_columns_, property_value(auto_cols, important)); + add_parsed_property(_grid_template_rows_, property_value(rows, important)); + } +} + +// [ row | column ] || dense +void style::parse_grid_auto_flow(const css_token_vector &tokens, bool important) +{ + if(tokens.empty() || tokens.size() > 2) return; + int auto_flow = 0; + for(const auto& item : tokens) + { + if(item.type == IDENT) + { + if(item.name == "row") + { + if(auto_flow & (grid_auto_flow_row | grid_auto_flow_column)) return; + auto_flow |= grid_auto_flow_row; + } else if(item.name == "column") + { + if(auto_flow & (grid_auto_flow_row | grid_auto_flow_column)) return; + auto_flow |= grid_auto_flow_column; + } else if(item.name == "dense") + { + if(auto_flow & grid_auto_flow_dense) return; + auto_flow |= grid_auto_flow_dense; + } + } + } + add_parsed_property(_grid_auto_flow_, property_value(auto_flow, important)); +} + } // namespace litehtml \ No newline at end of file