diff options
93 files changed, 6231 insertions, 4734 deletions
diff --git a/libs/litehtml/CMakeLists.txt b/libs/litehtml/CMakeLists.txt index 056bba6c9d..6759be7dfb 100644 --- a/libs/litehtml/CMakeLists.txt +++ b/libs/litehtml/CMakeLists.txt @@ -2,14 +2,9 @@ cmake_minimum_required(VERSION 3.11) project(litehtml LANGUAGES C CXX) +option(LITEHTML_BUILD_TESTING "enable testing for litehtml" OFF) -option(LITEHTML_BUILD_TESTING "enable testing for litehtml" ON) - -if(LITEHTML_BUILD_TESTING) - include(CTest) - enable_testing() -endif() - +if (NOT LITEHTML_BUILD_TESTING) # Soname # MAJOR is incremented when symbols are removed or changed in an incompatible way # MINOR is incremented when new symbols are added @@ -19,169 +14,168 @@ set(PROJECT_MINOR 0) option(EXTERNAL_GUMBO "Link against external gumbo instead of shipping a bundled copy" OFF) if(NOT EXTERNAL_GUMBO) - add_subdirectory(src/gumbo) + add_subdirectory(src/gumbo) endif() set(SOURCE_LITEHTML - src/codepoint.cpp - src/css_length.cpp - src/css_selector.cpp - src/document.cpp - src/document_container.cpp - src/el_anchor.cpp - src/el_base.cpp - src/el_before_after.cpp - src/el_body.cpp - src/el_break.cpp - src/el_cdata.cpp - src/el_comment.cpp - src/el_div.cpp - src/element.cpp - src/el_font.cpp - src/el_image.cpp - src/el_link.cpp - src/el_para.cpp - src/el_script.cpp - src/el_space.cpp - src/el_style.cpp - src/el_table.cpp - src/el_td.cpp - src/el_text.cpp - src/el_title.cpp - src/el_tr.cpp - src/html.cpp - src/html_tag.cpp - src/iterators.cpp - src/media_query.cpp - src/style.cpp - src/stylesheet.cpp - src/table.cpp - src/tstring_view.cpp - src/url.cpp - src/url_path.cpp - src/utf8_strings.cpp - src/web_color.cpp - src/num_cvt.cpp - src/strtod.cpp - src/string_id.cpp - src/css_properties.cpp - src/line_box.cpp - src/css_borders.cpp - src/render_item.cpp - src/render_block_context.cpp - src/render_block.cpp - src/render_inline_context.cpp - src/render_table.cpp - src/render_flex.cpp - src/render_image.cpp - src/formatting_context.cpp - src/flex_item.cpp - src/flex_line.cpp) - -set(HEADER_LITEHTML - include/litehtml.h - include/litehtml/background.h - include/litehtml/borders.h - include/litehtml/codepoint.h - include/litehtml/css_length.h - include/litehtml/css_margins.h - include/litehtml/css_offsets.h - include/litehtml/css_position.h - include/litehtml/css_selector.h - include/litehtml/document.h - include/litehtml/document_container.h - include/litehtml/el_anchor.h - include/litehtml/el_base.h - include/litehtml/el_before_after.h - include/litehtml/el_body.h - include/litehtml/el_break.h - include/litehtml/el_cdata.h - include/litehtml/el_comment.h - include/litehtml/el_div.h - include/litehtml/el_font.h - include/litehtml/el_image.h - include/litehtml/el_link.h - include/litehtml/el_para.h - include/litehtml/el_script.h - include/litehtml/el_space.h - include/litehtml/el_style.h - include/litehtml/el_table.h - include/litehtml/el_td.h - include/litehtml/el_text.h - include/litehtml/el_title.h - include/litehtml/el_tr.h - include/litehtml/element.h - include/litehtml/html.h - include/litehtml/html_tag.h - include/litehtml/iterators.h - include/litehtml/media_query.h - include/litehtml/os_types.h - include/litehtml/style.h - include/litehtml/stylesheet.h - include/litehtml/table.h - include/litehtml/tstring_view.h - include/litehtml/types.h - include/litehtml/url.h - include/litehtml/url_path.h - include/litehtml/utf8_strings.h - include/litehtml/web_color.h - include/litehtml/num_cvt.h - include/litehtml/css_properties.h - include/litehtml/line_box.h - include/litehtml/render_item.h - include/litehtml/render_flex.h - include/litehtml/render_image.h - include/litehtml/render_inline.h - include/litehtml/render_table.h - include/litehtml/render_inline_context.h - include/litehtml/render_block_context.h - include/litehtml/render_block.h - include/litehtml/master_css.h - include/litehtml/string_id.h - include/litehtml/formatting_context.h - include/litehtml/flex_item.h - include/litehtml/flex_line.h + src/codepoint.cpp + src/css_length.cpp + src/css_selector.cpp + src/css_tokenizer.cpp + src/css_parser.cpp + src/document.cpp + src/document_container.cpp + src/el_anchor.cpp + src/el_base.cpp + src/el_before_after.cpp + src/el_body.cpp + src/el_break.cpp + src/el_cdata.cpp + src/el_comment.cpp + src/el_div.cpp + src/element.cpp + src/el_font.cpp + src/el_image.cpp + src/el_link.cpp + src/el_para.cpp + src/el_script.cpp + src/el_space.cpp + src/el_style.cpp + src/el_table.cpp + src/el_td.cpp + src/el_text.cpp + src/el_title.cpp + src/el_tr.cpp + src/encodings.cpp + src/html.cpp + src/html_tag.cpp + src/html_microsyntaxes.cpp + src/iterators.cpp + src/media_query.cpp + src/style.cpp + src/stylesheet.cpp + src/table.cpp + src/tstring_view.cpp + src/url.cpp + src/url_path.cpp + src/utf8_strings.cpp + src/web_color.cpp + src/num_cvt.cpp + src/strtod.cpp + src/string_id.cpp + src/css_properties.cpp + src/line_box.cpp + src/css_borders.cpp + src/render_item.cpp + src/render_block_context.cpp + src/render_block.cpp + src/render_inline_context.cpp + src/render_table.cpp + src/render_flex.cpp + src/render_image.cpp + src/formatting_context.cpp + src/flex_item.cpp + src/flex_line.cpp + src/background.cpp + src/gradient.cpp ) -set(TEST_LITEHTML - test/cssTest.cpp - test/mediaQueryTest.cpp - test/codepoint_test.cpp - test/tstring_view_test.cpp - test/url_test.cpp - test/url_path_test.cpp - test/render_test.cpp - containers/test/test_container.cpp - containers/test/Font.cpp - containers/test/Bitmap.cpp - containers/test/lodepng.cpp +set(HEADER_LITEHTML + include/litehtml.h + include/litehtml/background.h + include/litehtml/borders.h + include/litehtml/codepoint.h + include/litehtml/css_length.h + include/litehtml/css_margins.h + include/litehtml/css_offsets.h + include/litehtml/css_position.h + include/litehtml/css_selector.h + include/litehtml/css_parser.h + include/litehtml/css_tokenizer.h + include/litehtml/document.h + include/litehtml/document_container.h + include/litehtml/el_anchor.h + include/litehtml/el_base.h + include/litehtml/el_before_after.h + include/litehtml/el_body.h + include/litehtml/el_break.h + include/litehtml/el_cdata.h + include/litehtml/el_comment.h + include/litehtml/el_div.h + include/litehtml/el_font.h + include/litehtml/el_image.h + include/litehtml/el_link.h + include/litehtml/el_para.h + include/litehtml/el_script.h + include/litehtml/el_space.h + include/litehtml/el_style.h + include/litehtml/el_table.h + include/litehtml/el_td.h + include/litehtml/el_text.h + include/litehtml/el_title.h + include/litehtml/el_tr.h + include/litehtml/element.h + include/litehtml/encodings.h + include/litehtml/html.h + include/litehtml/html_tag.h + include/litehtml/html_microsyntaxes.h + include/litehtml/iterators.h + include/litehtml/media_query.h + include/litehtml/os_types.h + include/litehtml/style.h + include/litehtml/stylesheet.h + include/litehtml/table.h + include/litehtml/tstring_view.h + include/litehtml/types.h + include/litehtml/url.h + include/litehtml/url_path.h + include/litehtml/utf8_strings.h + include/litehtml/web_color.h + include/litehtml/num_cvt.h + include/litehtml/css_properties.h + include/litehtml/line_box.h + include/litehtml/render_item.h + include/litehtml/render_flex.h + include/litehtml/render_image.h + include/litehtml/render_inline.h + include/litehtml/render_table.h + include/litehtml/render_inline_context.h + include/litehtml/render_block_context.h + include/litehtml/render_block.h + include/litehtml/master_css.h + include/litehtml/string_id.h + include/litehtml/formatting_context.h + include/litehtml/flex_item.h + include/litehtml/flex_line.h + include/litehtml/gradient.h ) set(PROJECT_LIB_VERSION ${PROJECT_MAJOR}.${PROJECT_MINOR}.0) set(PROJECT_SO_VERSION ${PROJECT_MAJOR}) if (MSVC) - # warning level 4 - add_compile_options(/W4) + # warning level 4 + add_compile_options(/W4) + add_compile_options(/permissive- /utf-8) else() - # additional warnings - add_compile_options(-Wall -Wextra -Wpedantic) + # additional warnings + add_compile_options(-Wall -Wextra -Wpedantic) endif() -add_library(${PROJECT_NAME} ${SOURCE_LITEHTML}) +add_library(${PROJECT_NAME} ${HEADER_LITEHTML} ${SOURCE_LITEHTML}) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_LIB_VERSION} SOVERSION ${PROJECT_SO_VERSION}) set_target_properties(${PROJECT_NAME} PROPERTIES - CXX_STANDARD 11 - C_STANDARD 99 - PUBLIC_HEADER "${HEADER_LITEHTML}" + CXX_STANDARD 17 + C_STANDARD 99 + PUBLIC_HEADER "${HEADER_LITEHTML}" ) # Export litehtml includes. target_include_directories(${PROJECT_NAME} PUBLIC - $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> - $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> - $<INSTALL_INTERFACE:include/${PROJECT_NAME}>) + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:include/${PROJECT_NAME}>) target_include_directories(${PROJECT_NAME} PRIVATE include/${PROJECT_NAME}) # Gumbo @@ -189,63 +183,26 @@ target_link_libraries(${PROJECT_NAME} PUBLIC gumbo) # install and export install(TARGETS ${PROJECT_NAME} - EXPORT litehtmlTargets - RUNTIME DESTINATION bin COMPONENT libraries - ARCHIVE DESTINATION lib${LIB_SUFFIX} COMPONENT libraries - LIBRARY DESTINATION lib${LIB_SUFFIX} COMPONENT libraries - PUBLIC_HEADER DESTINATION include/litehtml + EXPORT litehtmlTargets + RUNTIME DESTINATION bin COMPONENT libraries + ARCHIVE DESTINATION lib${LIB_SUFFIX} COMPONENT libraries + LIBRARY DESTINATION lib${LIB_SUFFIX} COMPONENT libraries + PUBLIC_HEADER DESTINATION include/litehtml ) install(FILES cmake/litehtmlConfig.cmake DESTINATION lib${LIB_SUFFIX}/cmake/litehtml) install(EXPORT litehtmlTargets FILE litehtmlTargets.cmake DESTINATION lib${LIB_SUFFIX}/cmake/litehtml) # Tests -if (LITEHTML_BUILD_TESTING) - option(EXTERNAL_GTEST "Use external GoogleTest instead of fetching from GitHub" OFF) - - if (EXTERNAL_GTEST) - link_libraries("-Wl,--copy-dt-needed-entries") - else() - include(FetchContent) - FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip - ) - # For Windows: Prevent overriding the parent project's compiler/linker settings - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - FetchContent_GetProperties(googletest) - if(NOT googletest_POPULATED) - FetchContent_Populate(googletest) - add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) - endif() - endif() - - enable_testing() - - set(TEST_NAME ${PROJECT_NAME}_tests) - - add_executable( - ${TEST_NAME} - ${TEST_LITEHTML} - ) - - set_target_properties(${TEST_NAME} PROPERTIES - CXX_STANDARD 11 - C_STANDARD 99 - PUBLIC_HEADER "${HEADER_LITEHTML}" - ) - - target_include_directories( - ${TEST_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/containers - ) - - target_link_libraries( - ${TEST_NAME} - ${PROJECT_NAME} - gtest_main - ) - - include(GoogleTest) - gtest_discover_tests(${TEST_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test) +else () + include(ExternalProject) + ExternalProject_Add( + litehtml-tests + GIT_REPOSITORY https://github.com/litehtml/litehtml-tests.git + GIT_TAG 1893e7b9f4f7a3e316c79795b29248fc0e761ef2 + SOURCE_DIR "${CMAKE_BINARY_DIR}/litehtml-tests-src" + BINARY_DIR "${CMAKE_BINARY_DIR}/litehtml-tests-build" + CMAKE_ARGS -DLITEHTML_PATH=${CMAKE_CURRENT_SOURCE_DIR} + INSTALL_COMMAND "" + ) endif() diff --git a/libs/litehtml/README.md b/libs/litehtml/README.md index 0c1132102a..ab897f517d 100644 --- a/libs/litehtml/README.md +++ b/libs/litehtml/README.md @@ -1,42 +1,22 @@ # What is litehtml? -**litehtml** is the lightweight HTML rendering engine with CSS2/CSS3 support. -Note that **litehtml** itself does not draw any text, pictures or other graphics and that **litehtml** does not depend on any image/draw/font library. -You are free to use any library to draw images, fonts and any other graphics. -**litehtml** just parses HTML/CSS and places the HTML elements into the correct positions (renders HTML). -To draw the HTML elements you have to implement the simple callback interface [document_container](https://github.com/litehtml/litehtml/wiki/document_container). -This interface is really simple, check it out! The [document_container](https://github.com/litehtml/litehtml/wiki/document_container) implementation is required to render HTML correctly. +**litehtml** is the lightweight HTML rendering engine with CSS2/CSS3 support. Note that **litehtml** itself does not draw any text, pictures or other graphics and that **litehtml** does not depend on any image/draw/font library. You are free to use any library to draw images, fonts and any other graphics. **litehtml** just parses HTML/CSS and places the HTML elements into the correct positions (renders HTML). To draw the HTML elements you have to implement the simple callback interface [document_container](https://github.com/litehtml/litehtml/wiki/document_container). This interface is really simple, check it out! The [document_container](https://github.com/litehtml/litehtml/wiki/document_container) implementation is required to render HTML correctly. # Where litehtml can be used -The **litehtml** can be used when you need to show HTML formatted text or even to create a mini-browser, -but using it as a full-featured HTML engine is not recommended. -Usually you don't need something like WebKit to show simple HTML tooltips or HTML-formatted text, -**litehtml** is much better for these as it's more lightweight and easier to integrate into your application. +**litehtml** can be used when you need to show HTML formatted text or even to create a mini-browser, but using it as a full-featured HTML engine is not recommended. Usually you don't need something like WebKit to show simple HTML tooltips or HTML-formatted text, **litehtml** is much better for these as it's more lightweight and easier to integrate into your application. ## HTML Parser -The **litehtml** uses the [gumbo-parser](https://github.com/google/gumbo-parser) to parse HTML. -Gumbo is an implementation of the HTML5 parsing algorithm -implemented as a pure C99 library with no outside dependencies. -It's designed to serve as a building block for other tools and libraries such as linters, -validators, templating languages, and refactoring and analysis tools. +**litehtml** uses the [gumbo-parser](https://github.com/google/gumbo-parser) to parse HTML. Gumbo is an implementation of the HTML5 parsing algorithm implemented as a pure C99 library with no outside dependencies. It's designed to serve as a building block for other tools and libraries such as linters, validators, templating languages, and refactoring and analysis tools. ## Compatibility -The **litehtml** is compatible with any platform supported by C++ and STL. -For Windows MS Visual Studio 2013 is recommended. -The **litehtml** supports only UTF-8 strings. +**litehtml** is compatible with any platform supported by C++ and STL. For Windows MS Visual Studio 2013 is recommended. **litehtml** supports only UTF-8 strings. ## Support for HTML and CSS standards -Unfortunately, the **litehtml** is not fully compatible with HTML/CSS standards. -There is lots of work to do to make **litehtml** work as well as modern browsers. -But **litehtml** supports most HTML tags and CSS properties. -You can find the list of supported CSS properties in [this table](https://docs.google.com/spreadsheet/ccc?key=0AvHXl5n24PuhdHdELUdhaUl4OGlncXhDcDJuM1JpMnc&usp=sharing). -For most simple use cases the HTML/CSS features supported by **litehtml** are enough. -Right now **litehtml** supports even some pages with very complex HTML/CSS designs. -As an example, the pages created with [bootstrap framework](http://getbootstrap.com/) are usually well formatted by **litehtml**. +Unfortunately **litehtml** is not fully compatible with HTML/CSS standards. There is lots of work to do to make **litehtml** work as well as modern browsers. But **litehtml** supports most HTML tags and CSS properties. You can find the list of supported CSS properties in [this table](https://docs.google.com/spreadsheet/ccc?key=0AvHXl5n24PuhdHdELUdhaUl4OGlncXhDcDJuM1JpMnc&usp=sharing). For most simple usecases the HTML/CSS features supported by **litehtml** are enough. Right now **litehtml** supports even some pages with very complex HTML/CSS designs. As an example the pages created with [bootstrap framework](http://getbootstrap.com/) are usually well formatted by **litehtml**. ## Testing litehtml diff --git a/libs/litehtml/include/litehtml.h b/libs/litehtml/include/litehtml.h index 9953170b6b..2537aee839 100644 --- a/libs/litehtml/include/litehtml.h +++ b/libs/litehtml/include/litehtml.h @@ -1,11 +1,11 @@ #ifndef LITEHTML_H #define LITEHTML_H -#include "litehtml/html.h" -#include "litehtml/document.h" -#include "litehtml/html_tag.h" -#include "litehtml/stylesheet.h" -#include "litehtml/element.h" -#include "litehtml/utf8_strings.h" +#include <litehtml/html.h> +#include <litehtml/document.h> +#include <litehtml/html_tag.h> +#include <litehtml/stylesheet.h> +#include <litehtml/element.h> +#include <litehtml/utf8_strings.h> #endif // LITEHTML_H diff --git a/libs/litehtml/include/litehtml/background.h b/libs/litehtml/include/litehtml/background.h index 93e0502cad..07b07aa3c2 100644 --- a/libs/litehtml/include/litehtml/background.h +++ b/libs/litehtml/include/litehtml/background.h @@ -6,161 +6,10 @@ #include "css_position.h" #include "web_color.h" #include "borders.h" +#include "gradient.h" namespace litehtml { - class background_gradient - { - public: - enum gradient_type - { - no_gradient, - linear_gradient, - repeating_linear_gradient, - radial_gradient, - repeating_radial_gradient, - conic_gradient, - repeating_conic_gradient, - }; - enum gradient_side - { - gradient_side_none = 0, - gradient_side_left = 0x01, - gradient_side_right = 0x02, - gradient_side_top = 0x04, - gradient_side_bottom = 0x08, - gradient_side_x_center = 0x10, - gradient_side_y_center = 0x20, - gradient_side_x_length = 0x40, - gradient_side_y_length = 0x80, - }; - enum radial_shape_t - { - radial_shape_none, - radial_shape_circle, - radial_shape_ellipse, - }; - enum radial_extent_t - { - radial_extent_none, - radial_extent_closest_corner, - radial_extent_closest_side, - radial_extent_farthest_corner, - radial_extent_farthest_side, - }; - enum conic_color_space_t - { - conic_color_space_none, - // rectangular-color-space - conic_color_space_srgb, - conic_color_space_srgb_linear, - conic_color_space_display_p3, - conic_color_space_a98_rgb, - conic_color_space_prophoto_rgb, - conic_color_space_rec2020, - conic_color_space_lab, - conic_color_space_oklab, - conic_color_space_xyz, - conic_color_space_xyz_d50, - conic_color_space_xyz_d65, - - // polar-color-space - conic_color_space_polar_start, - conic_color_space_hsl, - conic_color_space_hwb, - conic_color_space_lch, - conic_color_space_oklch, - }; - enum interpolation_method_t - { - interpolation_method_none, - interpolation_method_hue, - interpolation_method_shorter_hue, - interpolation_method_longer_hue, - interpolation_method_increasing_hue, - interpolation_method_decreasing_hue - }; - - class gradient_color - { - public: - bool is_color_hint; - web_color color; - css_length length; - def_value<float> angle; - - gradient_color() : - is_color_hint(false), angle(0) - {} - }; - gradient_type m_type; - uint32_t m_side; - float angle; - std::vector<gradient_color> m_colors; - css_length radial_position_x; - css_length radial_position_y; - radial_extent_t radial_extent; - radial_shape_t radial_shape; - css_length radial_length_x; - css_length radial_length_y; - float conic_from_angle; - conic_color_space_t conic_color_space; - interpolation_method_t conic_interpolation; - - - explicit background_gradient(gradient_type type = no_gradient) - { - m_type = type; - m_side = gradient_side_none; - angle = 180; - radial_extent = radial_extent_none; - radial_shape = radial_shape_ellipse; - radial_length_x.predef(0); - radial_length_y.predef(0); - radial_position_x.predef(0); - radial_position_y.predef(0); - conic_from_angle = 0; - conic_color_space = conic_color_space_none; - conic_interpolation = interpolation_method_none; - } - - bool is_empty() const - { - return m_type == no_gradient || m_colors.empty(); - } - - static background_gradient transparent; - }; - - class background_image - { - public: - enum bg_image_type_t - { - bg_image_type_none, - bg_image_type_url, - bg_image_type_gradient, - }; - bg_image_type_t type; - std::string url; - background_gradient gradient; - - background_image() : type(bg_image_type_none) {} - bool is_empty() const - { - switch (type) - { - case bg_image_type_none: - return false; - case bg_image_type_url: - return url.empty(); - case bg_image_type_gradient: - return gradient.is_empty(); - } - return true; - } - }; - class background_layer { public: @@ -186,14 +35,15 @@ namespace litehtml class image { public: - std::string url; - std::string base_url; + string url; + string base_url; }; struct color_point { float offset; web_color color; + optional<float> hint; color_point() { offset = 0.0; } color_point(float _offset, web_color _color) : offset(_offset), color(_color) {} }; @@ -207,11 +57,12 @@ namespace litehtml class gradient_base { public: - std::vector<color_point> color_points; + vector<color_point> color_points; + color_space_t color_space = color_space_none; + hue_interpolation_t hue_interpolation = hue_interpolation_none; void color_points_transparent_fix(); - bool prepare_color_points(float len, background_gradient::gradient_type g_type, const std::vector<background_gradient::gradient_color>& colors); - bool prepare_angle_color_points(background_gradient::gradient_type g_type, const std::vector<background_gradient::gradient_color>& colors); + bool prepare_color_points(float len, string_id grad_type, const vector<gradient::color_stop>& colors); }; class linear_gradient : public gradient_base @@ -232,13 +83,7 @@ namespace litehtml { public: pointF position; - float angle; - background_gradient::conic_color_space_t color_space; - background_gradient::interpolation_method_t interpolation; - conic_gradient() : angle(0), - color_space(background_gradient::conic_color_space_none), - interpolation(background_gradient::interpolation_method_none) - {} + float angle = 0; }; }; @@ -255,7 +100,7 @@ namespace litehtml type_conic_gradient, }; - std::vector<background_image> m_image; + vector<image> m_image; string m_baseurl; web_color m_color; int_vector m_attachment; diff --git a/libs/litehtml/include/litehtml/borders.h b/libs/litehtml/include/litehtml/borders.h index 9c47abca07..7a14e869c5 100644 --- a/libs/litehtml/include/litehtml/borders.h +++ b/libs/litehtml/include/litehtml/borders.h @@ -25,13 +25,7 @@ namespace litehtml color = val.color; } - css_border& operator=(const css_border& val) - { - width = val.width; - style = val.style; - color = val.color; - return *this; - } + css_border& operator=(const css_border& val) = default; string to_string() const; }; @@ -45,6 +39,7 @@ namespace litehtml border() { width = 0; + style = border_style_none; } border(const border& val) { @@ -58,13 +53,7 @@ namespace litehtml style = val.style; color = val.color; } - border& operator=(const border& val) - { - width = val.width; - style = val.style; - color = val.color; - return *this; - } + border& operator=(const border& val) = default; border& operator=(const css_border& val) { width = (int) val.width.val(); @@ -110,18 +99,7 @@ namespace litehtml bottom_left_x = val.bottom_left_x; bottom_left_y = val.bottom_left_y; } - border_radiuses& operator = (const border_radiuses& val) - { - top_left_x = val.top_left_x; - top_left_y = val.top_left_y; - top_right_x = val.top_right_x; - top_right_y = val.top_right_y; - bottom_right_x = val.bottom_right_x; - bottom_right_y = val.bottom_right_y; - bottom_left_x = val.bottom_left_x; - bottom_left_y = val.bottom_left_y; - return *this; - } + border_radiuses& operator = (const border_radiuses& val) = default; void operator += (const margins& mg) { top_left_x += mg.left; @@ -202,10 +180,7 @@ namespace litehtml css_length bottom_left_x; css_length bottom_left_y; - css_border_radius() - { - - } + css_border_radius() = default; css_border_radius(const css_border_radius& val) { @@ -219,18 +194,8 @@ namespace litehtml bottom_right_y = val.bottom_right_y; } - css_border_radius& operator=(const css_border_radius& val) - { - top_left_x = val.top_left_x; - top_left_y = val.top_left_y; - top_right_x = val.top_right_x; - top_right_y = val.top_right_y; - bottom_left_x = val.bottom_left_x; - bottom_left_y = val.bottom_left_y; - bottom_right_x = val.bottom_right_x; - bottom_right_y = val.bottom_right_y; - return *this; - } + css_border_radius& operator=(const css_border_radius& val) = default; + border_radiuses calc_percents(int width, int height) const { border_radiuses ret; @@ -271,15 +236,7 @@ namespace litehtml radius = val.radius; } - css_borders& operator=(const css_borders& val) - { - left = val.left; - right = val.right; - top = val.top; - bottom = val.bottom; - radius = val.radius; - return *this; - } + css_borders& operator=(const css_borders& val) = default; string to_string() const { return "left: " + left.to_string() + @@ -321,15 +278,7 @@ namespace litehtml return left.width != 0 || right.width != 0 || top.width != 0 || bottom.width != 0; } - borders& operator=(const borders& val) - { - left = val.left; - right = val.right; - top = val.top; - bottom = val.bottom; - radius = val.radius; - return *this; - } + borders& operator=(const borders& val) = default; borders& operator=(const css_borders& val) { diff --git a/libs/litehtml/include/litehtml/css_length.h b/libs/litehtml/include/litehtml/css_length.h index ae787105af..235ff54580 100644 --- a/libs/litehtml/include/litehtml/css_length.h +++ b/libs/litehtml/include/litehtml/css_length.h @@ -2,9 +2,22 @@ #define LH_CSS_LENGTH_H #include "types.h" +#include "css_tokenizer.h" namespace litehtml { + // from_token options (flags) + enum { + f_length = 1, // <length> (includes unitless zero) + f_percentage = 2, // <percentage> + f_length_percentage = f_length | f_percentage, // <length-percentage> + f_number = 4, // <number> + f_integer = 8, // <integer> + f_positive = 16 // ⩾0 (non-negative) + }; + + // <length> | <percentage> | <number> | auto | none | normal + // https://developer.mozilla.org/en-US/docs/Web/CSS/length class css_length { union @@ -27,12 +40,11 @@ namespace litehtml float val() const; css_units units() const; int calc_percent(int width) const; - void fromString(const string& str, const string& predefs = "", int defValue = 0); - static css_length from_string(const string& str, const string& predefs = "", int defValue = 0); + bool from_token(const css_token& token, int options, const string& predefined_keywords = ""); string to_string() const; }; - using length_vector = std::vector<css_length>; + using length_vector = vector<css_length>; // css_length inlines diff --git a/libs/litehtml/include/litehtml/css_margins.h b/libs/litehtml/include/litehtml/css_margins.h index 17dc7698da..f23ea64abb 100644 --- a/libs/litehtml/include/litehtml/css_margins.h +++ b/libs/litehtml/include/litehtml/css_margins.h @@ -22,14 +22,7 @@ namespace litehtml bottom = val.bottom; } - css_margins& operator=(const css_margins& val) - { - left = val.left; - right = val.right; - top = val.top; - bottom = val.bottom; - return *this; - } + css_margins& operator=(const css_margins& val) = default; string to_string() const { diff --git a/libs/litehtml/include/litehtml/css_offsets.h b/libs/litehtml/include/litehtml/css_offsets.h index 5ab175ce26..bff3c8711d 100644 --- a/libs/litehtml/include/litehtml/css_offsets.h +++ b/libs/litehtml/include/litehtml/css_offsets.h @@ -22,14 +22,7 @@ namespace litehtml bottom = val.bottom; } - css_offsets& operator=(const css_offsets& val) - { - left = val.left; - top = val.top; - right = val.right; - bottom = val.bottom; - return *this; - } + css_offsets& operator=(const css_offsets& val) = default; string to_string() const { diff --git a/libs/litehtml/include/litehtml/css_properties.h b/libs/litehtml/include/litehtml/css_properties.h index d6acc4419a..b7f8e7e784 100644 --- a/libs/litehtml/include/litehtml/css_properties.h +++ b/libs/litehtml/include/litehtml/css_properties.h @@ -48,7 +48,7 @@ namespace litehtml uint_ptr m_font; css_length m_font_size; string m_font_family; - font_weight m_font_weight; + css_length m_font_weight; font_style m_font_style; string m_text_decoration; font_metrics m_font_metrics; @@ -78,6 +78,7 @@ namespace litehtml void compute_font(const html_tag* el, const std::shared_ptr<document>& doc); void compute_background(const html_tag* el, const std::shared_ptr<document>& doc); void compute_flex(const html_tag* el, const std::shared_ptr<document>& doc); + web_color get_color_property(const html_tag* el, string_id name, bool inherited, web_color default_value, uint_ptr member_offset) const; public: css_properties() : diff --git a/libs/litehtml/include/litehtml/css_selector.h b/libs/litehtml/include/litehtml/css_selector.h index 4fe7db9e50..042f1691e4 100644 --- a/libs/litehtml/include/litehtml/css_selector.h +++ b/libs/litehtml/include/litehtml/css_selector.h @@ -3,6 +3,7 @@ #include "style.h" #include "media_query.h" +#include "css_tokenizer.h" namespace litehtml { @@ -121,105 +122,92 @@ namespace litehtml { select_class, select_id, - - select_exists, - select_equal, - select_contain_str, - select_start_str, - select_end_str, - + select_attr, select_pseudo_class, select_pseudo_element, }; + // https://www.w3.org/TR/selectors-4/#attribute-selectors + enum attr_matcher : char + { + attribute_exists = 0, + attribute_equals = '=', + attribute_contains_string = '*', // *= + attribute_contains_word = '~', // ~= + attribute_starts_with_string = '^', // ^= + attribute_starts_with_string_hyphen = '|', // |= + attribute_ends_with_string = '$', // $= + }; + ////////////////////////////////////////////////////////////////////////// - class css_element_selector; + class css_selector; + class html_tag; + // <subclass-selector> | <pseudo-element-selector> + // = <id-selector> | <class-selector> | <attribute-selector> | <pseudo-class-selector> | <pseudo-element-selector> struct css_attribute_selector { - typedef std::vector<css_attribute_selector> vector; + using vector = std::vector<css_attribute_selector>; attr_select_type type; - string_id name; // .name, #name, [name], :name - string val; // [name=val], :lang(val) + string_id prefix; // [prefix|name] + string_id name; // .name, #name, [name], :name + string value; // [name=value], :lang(value) + + attr_matcher matcher; // <attr-matcher> = ~= |= ^= $= *= + bool caseless_match; // value is matched ASCII case-insensitively - std::shared_ptr<css_element_selector> sel; // :not(sel) - int a, b; // :nth-child(an+b) + std::vector<shared_ptr<css_selector>> selector_list; // :not(selector_list) + int a, b; // :nth-child(an+b of selector_list) - css_attribute_selector() - { - type = select_class; - name = empty_id; - a = b = 0; - } + css_attribute_selector(attr_select_type type = select_class, string name = "") + : type(type), prefix(empty_id), name(_id(name)), matcher(), caseless_match(0), a(0), b(0) {} + + operator bool() const { return name != empty_id; } }; ////////////////////////////////////////////////////////////////////////// - class css_element_selector + class css_element_selector // compound selector: div.class:hover { public: - string_id m_tag; - css_attribute_selector::vector m_attrs; + using ptr = shared_ptr<css_element_selector>; public: - - void parse(const string& txt); - static void parse_nth_child_params(const string& param, int& num, int& off); + string_id m_prefix; + string_id m_tag; + css_attribute_selector::vector m_attrs; // subclass and pseudo-element selectors }; ////////////////////////////////////////////////////////////////////////// enum css_combinator { - combinator_descendant, - combinator_child, - combinator_adjacent_sibling, - combinator_general_sibling + combinator_descendant = ' ', + combinator_child = '>', + combinator_adjacent_sibling = '+', + combinator_general_sibling = '~' }; ////////////////////////////////////////////////////////////////////////// - class css_selector + class css_selector // complex selector: div + p { public: - typedef std::shared_ptr<css_selector> ptr; - typedef std::vector<css_selector::ptr> vector; - public: - selector_specificity m_specificity; - css_element_selector m_right; - css_selector::ptr m_left; - css_combinator m_combinator; - style::ptr m_style; - int m_order; - media_query_list::ptr m_media_query; - public: - explicit css_selector(const media_query_list::ptr& media = nullptr) - { - m_media_query = media; - m_combinator = combinator_descendant; - m_order = 0; - } + using ptr = shared_ptr<css_selector>; + using vector = std::vector<css_selector::ptr>; - ~css_selector() = default; - - css_selector(const css_selector& val) - { - m_right = val.m_right; - if(val.m_left) - { - m_left = std::make_shared<css_selector>(*val.m_left); - } else - { - m_left = nullptr; - } - m_combinator = val.m_combinator; - m_specificity = val.m_specificity; - m_order = val.m_order; - m_media_query = val.m_media_query; - } + public: + selector_specificity m_specificity; + int m_order = 0; + css_selector::ptr m_left; + css_element_selector m_right; + css_combinator m_combinator = combinator_descendant; + media_query_list_list::ptr m_media_query; + style::ptr m_style; - bool parse(const string& text); + public: + bool parse(const string& text, document_mode mode); void calc_specificity(); bool is_media_valid() const; void add_media_to_doc(document* doc) const; @@ -281,20 +269,16 @@ namespace litehtml m_used = used; m_selector = selector; } + }; - used_selector(const used_selector& val) - { - m_used = val.m_used; - m_selector = val.m_selector; - } - used_selector& operator=(const used_selector& val) - { - m_used = val.m_used; - m_selector = val.m_selector; - return *this; - } + enum { + strict_mode = 0, + forgiving_mode = 1, + forbid_pseudo_elements = 1 << 1, }; + + css_selector::vector parse_selector_list(const css_token_vector& tokens, int options, document_mode mode); } #endif // LH_CSS_SELECTOR_H diff --git a/libs/litehtml/include/litehtml/document.h b/libs/litehtml/include/litehtml/document.h index 4871fc154f..3f94565655 100644 --- a/libs/litehtml/include/litehtml/document.h +++ b/libs/litehtml/include/litehtml/document.h @@ -25,28 +25,21 @@ namespace litehtml baseurl = url ? url : ""; media = media_str ? media_str : ""; } - - css_text(const css_text& val) - { - text = val.text; - baseurl = val.baseurl; - media = val.media; - } }; - class dumper - { - public: - virtual ~dumper() {} - virtual void begin_node(const string& descr) = 0; - virtual void end_node() = 0; - virtual void begin_attrs_group(const string& descr) = 0; - virtual void end_attrs_group() = 0; - virtual void add_attr(const string& name, const string& value) = 0; - }; + class dumper + { + public: + virtual ~dumper() {} + virtual void begin_node(const string& descr) = 0; + virtual void end_node() = 0; + virtual void begin_attrs_group(const string& descr) = 0; + virtual void end_attrs_group() = 0; + virtual void add_attr(const string& name, const string& value) = 0; + }; class html_tag; - class render_item; + class render_item; class document : public std::enable_shared_from_this<document> { @@ -61,30 +54,31 @@ namespace litehtml css_text::vector m_css; litehtml::css m_styles; litehtml::web_color m_def_color; - litehtml::css m_master_css; - litehtml::css m_user_css; + litehtml::css m_master_css; + litehtml::css m_user_css; litehtml::size m_size; litehtml::size m_content_size; position::vector m_fixed_boxes; - media_query_list::vector m_media_lists; element::ptr m_over_element; - std::list<std::shared_ptr<render_item>> m_tabular_elements; + std::list<shared_ptr<render_item>> m_tabular_elements; + media_query_list_list::vector m_media_lists; media_features m_media; string m_lang; string m_culture; string m_text; + document_mode m_mode = no_quirks_mode; public: document(document_container* objContainer); virtual ~document(); document_container* container() { return m_container; } + document_mode mode() const { return m_mode; } uint_ptr get_font(const char* name, int size, const char* weight, const char* style, const char* decoration, font_metrics* fm); int render(int max_width, render_type rt = render_all); void draw(uint_ptr hdc, int x, int y, const position* clip); web_color get_def_color() { return m_def_color; } - int to_pixels(const char* str, int fontSize, bool* is_percent = nullptr) const; - void cvt_units(css_length& val, int fontSize, int size = 0) const; - int to_pixels(const css_length& val, int fontSize, int size = 0) const; + void cvt_units(css_length& val, const font_metrics& metrics, int size) const; + int to_pixels(const css_length& val, const font_metrics& metrics, int size) const; int width() const; int height() const; int content_width() const; @@ -96,9 +90,10 @@ namespace litehtml bool on_mouse_leave(position::vector& redraw_boxes); element::ptr create_element(const char* tag_name, const string_map& attributes); element::ptr root(); + std::shared_ptr<render_item> root_render(); void get_fixed_boxes(position::vector& fixed_boxes); void add_fixed_box(const position& pos); - void add_media_list(const media_query_list::ptr& list); + void add_media_list(media_query_list_list::ptr list); bool media_changed(); bool lang_changed(); bool match_lang(const string& lang); @@ -130,6 +125,12 @@ namespace litehtml { return m_root; } + + inline std::shared_ptr<render_item> document::root_render() + { + return m_root_render; + } + inline void document::add_tabular(const std::shared_ptr<render_item>& el) { m_tabular_elements.push_back(el); diff --git a/libs/litehtml/include/litehtml/document_container.h b/libs/litehtml/include/litehtml/document_container.h index 1fc4713f9b..395c6cd1a7 100644 --- a/libs/litehtml/include/litehtml/document_container.h +++ b/libs/litehtml/include/litehtml/document_container.h @@ -22,6 +22,12 @@ namespace litehtml int index; uint_ptr font; }; + + enum mouse_event + { + mouse_event_enter, + mouse_event_leave, + }; // call back interface to draw text, images and other elements class document_container @@ -48,6 +54,7 @@ namespace litehtml virtual void set_base_url(const char* base_url) = 0; virtual void link(const std::shared_ptr<litehtml::document>& doc, const litehtml::element::ptr& el) = 0; virtual void on_anchor_click(const char* url, const litehtml::element::ptr& el) = 0; + virtual void on_mouse_event(const litehtml::element::ptr& el, litehtml::mouse_event event) = 0; virtual void set_cursor(const char* cursor) = 0; virtual void transform_text(litehtml::string& text, litehtml::text_transform tt) = 0; virtual void import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl) = 0; @@ -64,7 +71,7 @@ namespace litehtml virtual void split_text(const char* text, const std::function<void(const char*)>& on_word, const std::function<void(const char*)>& on_space); protected: - ~document_container() = default; + virtual ~document_container() = default; }; } diff --git a/libs/litehtml/include/litehtml/el_break.h b/libs/litehtml/include/litehtml/el_break.h index 2a4d8fe45b..81f38fec04 100644 --- a/libs/litehtml/include/litehtml/el_break.h +++ b/libs/litehtml/include/litehtml/el_break.h @@ -11,7 +11,7 @@ namespace litehtml explicit el_break(const std::shared_ptr<litehtml::document>& doc); bool is_break() const override; - }; + }; } #endif // LH_EL_BREAK_H diff --git a/libs/litehtml/include/litehtml/el_comment.h b/libs/litehtml/include/litehtml/el_comment.h index 746238e9aa..379318ffbe 100644 --- a/libs/litehtml/include/litehtml/el_comment.h +++ b/libs/litehtml/include/litehtml/el_comment.h @@ -14,11 +14,11 @@ namespace litehtml bool is_comment() const override; void get_text(string& text) override; void set_data(const char* data) override; - std::shared_ptr<render_item> create_render_item(const std::shared_ptr<render_item>& /*parent_ri*/) override - { - // Comments are not rendered - return nullptr; - } + std::shared_ptr<render_item> create_render_item(const std::shared_ptr<render_item>& /*parent_ri*/) override + { + // Comments are not rendered + return nullptr; + } }; } diff --git a/libs/litehtml/include/litehtml/el_space.h b/libs/litehtml/include/litehtml/el_space.h index 46a292ebcb..53c170f13a 100644 --- a/libs/litehtml/include/litehtml/el_space.h +++ b/libs/litehtml/include/litehtml/el_space.h @@ -13,8 +13,8 @@ namespace litehtml bool is_white_space() const override; bool is_break() const override; - bool is_space() const override; - string dump_get_name() override; + bool is_space() const override; + string dump_get_name() override; }; } diff --git a/libs/litehtml/include/litehtml/el_text.h b/libs/litehtml/include/litehtml/el_text.h index 4b8a442f38..ea54cc9c66 100644 --- a/libs/litehtml/include/litehtml/el_text.h +++ b/libs/litehtml/include/litehtml/el_text.h @@ -18,11 +18,11 @@ namespace litehtml void get_text(string& text) override; void compute_styles(bool recursive) override; - bool is_text() const override { return true; } + bool is_text() const override { return true; } - void draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr<render_item> &ri) override; - string dump_get_name() override; - std::vector<std::tuple<string, string>> dump_get_attrs() override; + void draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr<render_item> &ri) override; + string dump_get_name() override; + std::vector<std::tuple<string, string>> dump_get_attrs() override; protected: void get_content_size(size& sz, int max_width) override; }; diff --git a/libs/litehtml/include/litehtml/element.h b/libs/litehtml/include/litehtml/element.h index b1aa8e30ae..67dd1ae251 100644 --- a/libs/litehtml/include/litehtml/element.h +++ b/libs/litehtml/include/litehtml/element.h @@ -105,6 +105,7 @@ namespace litehtml virtual void get_text(string& text); virtual void parse_attributes(); + virtual int select(const css_selector::vector& selector_list, bool apply_pseudo = true); virtual int select(const string& selector); virtual int select(const css_selector& selector, bool apply_pseudo = true); virtual int select(const css_element_selector& selector, bool apply_pseudo = true); @@ -113,8 +114,8 @@ namespace litehtml virtual element::ptr find_adjacent_sibling(const element::ptr& el, const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr); virtual element::ptr find_sibling(const element::ptr& el, const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr); virtual void get_content_size(size& sz, int max_width); - virtual bool is_nth_child(const element::ptr& el, int num, int off, bool of_type) const; - virtual bool is_nth_last_child(const element::ptr& el, int num, int off, bool of_type) const; + virtual bool is_nth_child(const element::ptr& el, int num, int off, bool of_type, const css_selector::vector& selector_list = {}) const; + virtual bool is_nth_last_child(const element::ptr& el, int num, int off, bool of_type, const css_selector::vector& selector_list = {}) const; virtual bool is_only_child(const element::ptr& el, bool of_type) const; virtual void add_style(const style& style); virtual const background* get_background(bool own_only = false); diff --git a/libs/litehtml/include/litehtml/encodings.h b/libs/litehtml/include/litehtml/encodings.h index 82b0323b44..2dd9d5e0f5 100644 --- a/libs/litehtml/include/litehtml/encodings.h +++ b/libs/litehtml/include/litehtml/encodings.h @@ -84,6 +84,7 @@ encoding get_encoding(string label); encoding extract_encoding_from_meta_element(string str); void decode(string input, encoding coding, string& output); +string decode(string input, encoding coding); } // namespace litehtml diff --git a/libs/litehtml/include/litehtml/gradient.h b/libs/litehtml/include/litehtml/gradient.h index 782a764e28..6313eb75b2 100644 --- a/libs/litehtml/include/litehtml/gradient.h +++ b/libs/litehtml/include/litehtml/gradient.h @@ -1,13 +1,204 @@ #ifndef LITEHTML_GRADIENT_H #define LITEHTML_GRADIENT_H -#include "background.h" - namespace litehtml { - void parse_linear_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad); - void parse_radial_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad); - void parse_conic_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad); + enum gradient_side + { + gradient_side_none = 0, + gradient_side_left = 0x01, + gradient_side_right = 0x02, + gradient_side_top = 0x04, + gradient_side_bottom = 0x08, + gradient_side_x_center = 0x10, + gradient_side_y_center = 0x20, + gradient_side_x_length = 0x40, + gradient_side_y_length = 0x80, + }; + static_assert(gradient_side_left == (1 << background_position_left), "parse_bg_position is also used to parse radial gradient position"); + + enum radial_shape_t + { + radial_shape_none, + radial_shape_circle, + radial_shape_ellipse, + }; + +#define radial_extent_strings "closest-corner;closest-side;farthest-corner;farthest-side" + enum radial_extent_t + { + radial_extent_none, + radial_extent_closest_corner, + radial_extent_closest_side, + radial_extent_farthest_corner, + radial_extent_farthest_side, + }; + +#define color_space_strings \ + "srgb;"\ + "srgb-linear;"\ + "display-p3;"\ + "a98-rgb;"\ + "prophoto-rgb;"\ + "rec2020;"\ + "lab;"\ + "oklab;"\ + "xyz;"\ + "xyz-d50;"\ + "xyz-d65;"\ + "hsl;"\ + "hwb;"\ + "lch;"\ + "oklch" + + enum color_space_t + { + color_space_none, + + // rectangular-color-space + color_space_srgb, + color_space_srgb_linear, + color_space_display_p3, + color_space_a98_rgb, + color_space_prophoto_rgb, + color_space_rec2020, + color_space_lab, + color_space_oklab, + color_space_xyz, + color_space_xyz_d50, + color_space_xyz_d65, + + // polar-color-space + color_space_hsl, color_space_polar_start = color_space_hsl, + color_space_hwb, + color_space_lch, + color_space_oklch, + }; + +#define hue_interpolation_strings "shorter;longer;increasing;decreasing" + enum hue_interpolation_t + { + hue_interpolation_none, + hue_interpolation_shorter, + hue_interpolation_longer, + hue_interpolation_increasing, + hue_interpolation_decreasing + }; + + class gradient + { + public: + // https://drafts.csswg.org/css-images-4/#typedef-color-stop-list + // color_stop represents <linear-color-stop> | <linear-color-hint> | <angular-color-stop> | <angular-color-hint> + // <linear-color-stop> = <color> <length-percentage>{1,2}? + // <linear-color-hint> = <length-percentage> + // <angular-color-stop> = <color> <angle-percentage>{1,2}? + // <angular-color-hint> = <angle-percentage> + // If two lengths/angles are specified they create two color-stops with the same color. + class color_stop + { + public: + bool is_color_hint = false; + web_color color; + optional<css_length> length; + optional<float> angle; + + color_stop() {} + color_stop(web_color color) : color(color) {} + color_stop(web_color color, css_length length) : color(color), length(length) {} + color_stop(web_color color, float angle) : color(color), angle(angle) {} + color_stop(css_length length) : is_color_hint(true), length(length) {} + color_stop(float angle) : is_color_hint(true), angle(angle) {} + }; + + string_id m_type; + uint32_t m_side; + float angle; + vector<color_stop> m_colors; + css_length position_x; + css_length position_y; + radial_shape_t radial_shape; + radial_extent_t radial_extent; + css_length radial_radius_x; + css_length radial_radius_y; + float conic_from_angle; + color_space_t color_space; + hue_interpolation_t hue_interpolation; + + + explicit gradient(string_id type = empty_id) + { + m_type = type; + + // linear gradient: m_side default is gradient_side_none (use angle) + // radial,conic gradient: m_side default is gradient_side_x_center | gradient_side_y_center (see parse_gradient) + m_side = gradient_side_none; + // linear gradient angle, used when m_side == gradient_side_none + angle = 180; // to bottom + // radial,conic position (together with m_side) + position_x.predef(0); + position_y.predef(0); + + // actual default depends on the number of radii provided, see parse_radial_gradient_shape_size_position_interpolation + radial_shape = radial_shape_ellipse; + + // <radial-size> https://drafts.csswg.org/css-images-3/#valdef-radial-gradient-radial-size + // if radius is specified radial_extent will be set to none, see parse_radial_size + radial_extent = radial_extent_farthest_corner; + radial_radius_x.predef(0); + radial_radius_y.predef(0); + + conic_from_angle = 0; + + // If the host syntax does not define what color space interpolation should take place in, it defaults to Oklab. + color_space = color_space_oklab; + // Unless otherwise specified, if no specific hue interpolation algorithm is selected by the host syntax, the default is shorter. + hue_interpolation = hue_interpolation_shorter; + } + + bool is_empty() const + { + return m_type == empty_id || m_colors.empty(); + } + + bool is_linear() const { return m_type == _linear_gradient_ || m_type == _repeating_linear_gradient_; } + bool is_radial() const { return m_type == _radial_gradient_ || m_type == _repeating_radial_gradient_; } + bool is_conic() const { return m_type == _conic_gradient_ || m_type == _repeating_conic_gradient_; } + + static gradient transparent; + }; + + class image + { + public: + enum type + { + type_none, + type_url, + type_gradient, + }; + type type; + string url; + gradient m_gradient; + + image() : type(type_none) {} + + bool is_empty() const + { + switch (type) + { + case type_none: + return true; + case type_url: + return url.empty(); + case type_gradient: + return m_gradient.is_empty(); + } + return true; + } + }; + + bool parse_gradient(const css_token& token, gradient& gradient, document_container* container); } #endif //LITEHTML_GRADIENT_H diff --git a/libs/litehtml/include/litehtml/html.h b/libs/litehtml/include/litehtml/html.h index 0bde45d2bf..cd0500aabe 100644 --- a/libs/litehtml/include/litehtml/html.h +++ b/libs/litehtml/include/litehtml/html.h @@ -1,43 +1,127 @@ #ifndef LH_HTML_H #define LH_HTML_H -#include <stdlib.h> -#include <string> -#include <ctype.h> -#include <vector> -#include <map> +#include <cstdlib> +#include <cmath> +#include <cctype> #include <cstring> #include <algorithm> #include <functional> #include "os_types.h" -#include "string_id.h" #include "types.h" +#include "string_id.h" #include "utf8_strings.h" #include "background.h" #include "borders.h" #include "web_color.h" #include "media_query.h" +#include "html_microsyntaxes.h" #include "html_tag.h" #include "document_container.h" #include "document.h" namespace litehtml { - string& trim(string &s, const string& chars_to_trim = " \n\r\f\t"); - void lcase(string &s); + const string whitespace = " \n\r\t\f"; + string& trim(string& s, const string& chars_to_trim = whitespace); + string trim(const string& s, const string& chars_to_trim = whitespace); + string& lcase(string& s); int value_index(const string& val, const string& strings, int defValue = -1, char delim = ';'); - string index_value(int index, const string& strings, char delim = ';'); + 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, const string& delims_preserve = "", const string& quote = "\""); + 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 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); + double t_strtod(const char* string, char** endPtr = nullptr); + string get_escaped_string(const string& in_str); + + template<typename X, typename A> + bool is_one_of(X x, A a) + { + return x == a; + } + template<typename X, typename A, typename... AA> + bool is_one_of(X x, A a, AA... aa) + { + return x == a || is_one_of(x, aa...); + } + template<class T> + const T& at(const vector<T>& vec, int index /*may be negative*/) + { + static T invalid_item; // T's default constructor must create invalid item + if (index < 0) index += (int)vec.size(); + return index >= 0 && index < (int)vec.size() ? vec[index] : invalid_item; + } + template<class Map, class Key> + auto at(const Map& map, Key key) + { + static typename Map::mapped_type invalid_value; // mapped_type's default constructor must create invalid item + auto it = map.find(key); + return it != map.end() ? it->second : invalid_value; + } + template<typename T> + vector<T> slice(const vector<T>& vec, int index, int count = -1) + { + if (count == -1) count = (int)vec.size() - index; + return {vec.begin() + index, vec.begin() + index + count}; + } + template<class C> // C == vector or string + void remove(C& vec, int index /*may be negative*/, int count = 1) + { + if (index < 0) index += (int)vec.size(); + + if (!(index >= 0 && index < (int)vec.size())) + return; + + count = min(count, (int)vec.size() - index); + if (count <= 0) return; + + vec.erase(vec.begin() + index, vec.begin() + index + count); + } + template<class T> + void insert(vector<T>& vec, int index, const vector<T>& x) + { + vec.insert(vec.begin() + index, x.begin(), x.end()); + } + template<class T> + vector<T>& operator+=(vector<T>& vec, const vector<T>& x) + { + vec.insert(vec.end(), x.begin(), x.end()); + return vec; + } + template<class C, class T> + bool contains(const C& coll, const T& item) + { + return std::find(coll.begin(), coll.end(), item) != coll.end(); + } + inline bool contains(const string& str, const string& substr) + { + return str.find(substr) != string::npos; + } + template<class C> void sort(C& coll) { std::sort(coll.begin(), coll.end()); } int t_strcasecmp(const char *s1, const char *s2); int t_strncasecmp(const char *s1, const char *s2, size_t n); + inline bool equal_i(const string& s1, const string& s2) + { + if (s1.size() != s2.size()) return false; + return t_strncasecmp(s1.c_str(), s2.c_str(), s1.size()) == 0; + } + inline bool match(const string& str, int index /*may be negative*/, const string& substr) + { + if (index < 0) index += (int)str.size(); + if (index < 0) return false; + return str.substr(index, substr.size()) == substr; + } + inline bool match_i(const string& str, int index /*may be negative*/, const string& substr) + { + if (index < 0) index += (int)str.size(); + if (index < 0) return false; + return equal_i(str.substr(index, substr.size()), substr); + } - bool is_number(const string& string, const bool allow_dot = 1); + bool is_number(const string& string, bool allow_dot = true); // https://infra.spec.whatwg.org/#ascii-whitespace inline bool is_whitespace(int c) @@ -45,15 +129,11 @@ namespace litehtml return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; } - inline int t_isdigit(int c) - { - return (c >= '0' && c <= '9'); - } - inline int t_isalpha(int c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } + const auto is_letter = t_isalpha; inline int t_tolower(int c) { @@ -64,6 +144,29 @@ namespace litehtml { return t_tolower(c); } + inline string lowcase(string str) + { + for (char& c : str) c = (char)t_tolower(c); + return str; + } + + inline int t_isdigit(int c) + { + return (c >= '0' && c <= '9'); + } + const auto is_digit = t_isdigit; + + inline bool is_hex_digit(int ch) { + return is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + + inline int digit_value(int ch) { + return is_digit(ch) ? ch - '0' : lowcase(ch) - 'a' + 10; + } + + inline bool is_surrogate(int ch) { + return ch >= 0xD800 && ch < 0xE000; + } inline int round_f(float val) { diff --git a/libs/litehtml/include/litehtml/html_tag.h b/libs/litehtml/include/litehtml/html_tag.h index 4a7a9a78dc..63cd64498a 100644 --- a/libs/litehtml/include/litehtml/html_tag.h +++ b/libs/litehtml/include/litehtml/html_tag.h @@ -10,7 +10,6 @@ #include "stylesheet.h" #include "line_box.h" #include "table.h" -#include <assert.h> namespace litehtml { @@ -22,31 +21,33 @@ namespace litehtml friend class table_grid; friend class line_box; public: - typedef std::shared_ptr<html_tag> ptr; + typedef shared_ptr<html_tag> ptr; protected: string_id m_tag; string_id m_id; string_vector m_str_classes; - std::vector<string_id> m_classes; - litehtml::style m_style; + vector<string_id> m_classes; + style m_style; string_map m_attrs; - std::vector<string_id> m_pseudo_classes; + vector<string_id> m_pseudo_classes; void select_all(const css_selector& selector, elements_list& res) override; public: - explicit html_tag(const std::shared_ptr<document>& doc); + explicit html_tag(const shared_ptr<document>& doc); // constructor for anonymous wrapper boxes explicit html_tag(const element::ptr& parent, const string& style = "display: block"); - bool appendChild(const element::ptr &el) override; - bool removeChild(const element::ptr &el) override; + bool appendChild(const element::ptr& el) override; + bool removeChild(const element::ptr& el) override; void clearRecursive() override; string_id tag() const override; string_id id() const override; const char* get_tagName() const override; void set_tagName(const char* tag) override; void set_data(const char* data) override; + const vector<string_id>& classes() const { return m_classes; } + const string_vector& str_classes() const { return m_str_classes; } void set_attr(const char* name, const char* val) override; const char* get_attr(const char* name, const char* def = nullptr) const override; @@ -72,10 +73,11 @@ namespace litehtml template<class Type> const Type& get_property(string_id name, bool inherited, const Type& default_value, uint_ptr css_properties_member_offset) const; - string get_custom_property(string_id name, const string& default_value) const; + bool get_custom_property(string_id name, css_token_vector& result) const; elements_list& children(); + int select(const css_selector::vector& selector_list, bool apply_pseudo = true) override; int select(const string& selector) override; int select(const css_selector& selector, bool apply_pseudo = true) override; int select(const css_element_selector& selector, bool apply_pseudo = true) override; @@ -97,8 +99,8 @@ namespace litehtml void get_content_size(size& sz, int max_width) override; void add_style(const style& style) override; - bool is_nth_child(const element::ptr& el, int num, int off, bool of_type) const override; - bool is_nth_last_child(const element::ptr& el, int num, int off, bool of_type) const override; + bool is_nth_child(const element::ptr& el, int num, int off, bool of_type, const css_selector::vector& selector_list) const override; + bool is_nth_last_child(const element::ptr& el, int num, int off, bool of_type, const css_selector::vector& selector_list) const override; bool is_only_child(const element::ptr& el, bool of_type) const override; const background* get_background(bool own_only = false) override; @@ -110,6 +112,11 @@ namespace litehtml element::ptr get_element_before(const style& style, bool create); element::ptr get_element_after(const style& style, bool create); + void map_to_pixel_length_property(string_id prop_name, string attr_value); + void map_to_pixel_length_property_with_default_value(string_id prop_name, string attr_value, int default_value); + void map_to_dimension_property(string_id prop_name, string attr_value); + void map_to_dimension_property_ignoring_zero(string_id prop_name, string attr_value); + private: void handle_counter_properties(); @@ -119,7 +126,7 @@ namespace litehtml /* Inline Functions */ /************************************************************************/ - inline elements_list& litehtml::html_tag::children() + inline elements_list& html_tag::children() { return m_children; } @@ -141,7 +148,6 @@ namespace litehtml } return default_value; } - assert(value.is<invalid>()); return default_value; } diff --git a/libs/litehtml/include/litehtml/line_box.h b/libs/litehtml/include/litehtml/line_box.h index a1e4969c96..6e945c9fdc 100644 --- a/libs/litehtml/include/litehtml/line_box.h +++ b/libs/litehtml/include/litehtml/line_box.h @@ -46,6 +46,7 @@ namespace litehtml line_box_item() : m_element(), m_rendered_min_width(0) {}; line_box_item(const line_box_item& el) = default; line_box_item(line_box_item&&) = default; + virtual ~line_box_item(); int height() const { return right() - left(); } const std::shared_ptr<render_item>& get_el() const { return m_element; } @@ -67,6 +68,7 @@ namespace litehtml position m_pos; public: explicit lbi_start(const std::shared_ptr<render_item>& element); + virtual ~lbi_start() override; void place_to(int x, int y) override; int width() const override; @@ -83,6 +85,7 @@ namespace litehtml { public: explicit lbi_end(const std::shared_ptr<render_item>& element); + virtual ~lbi_end() override; void place_to(int x, int y) override; int right() const override; @@ -94,6 +97,7 @@ namespace litehtml { public: explicit lbi_continue(const std::shared_ptr<render_item>& element); + virtual ~lbi_continue() override; void place_to(int x, int y) override; int right() const override; diff --git a/libs/litehtml/include/litehtml/master_css.h b/libs/litehtml/include/litehtml/master_css.h index 0e7b0ca4e3..ea532922d7 100644 --- a/libs/litehtml/include/litehtml/master_css.h +++ b/libs/litehtml/include/litehtml/master_css.h @@ -4,32 +4,31 @@ namespace litehtml{ const char* const master_css = R"##( html { - display: block; - position: relative; + display: block; } head { - display: none + display: none } meta { - display: none + display: none } title { - display: none + display: none } link { - display: none + display: none } style { - display: none + display: none } script { - display: none + display: none } body { @@ -157,29 +156,29 @@ img[align="left"] } hr { - display: block; - margin-top: 0.5em; - margin-bottom: 0.5em; - margin-left: auto; - margin-right: auto; - border-style: inset; - border-width: 1px + display: block; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-left: auto; + margin-right: auto; + border-style: inset; + border-width: 1px } /***************** TABLES ********************/ table { - display: table; - border-collapse: separate; - border-spacing: 2px; - border-top-color:gray; - border-left-color:gray; - border-bottom-color:black; - border-right-color:black; - font-size: medium; - font-weight: normal; - font-style: normal; + display: table; + border-collapse: separate; + border-spacing: 2px; + border-top-color:gray; + border-left-color:gray; + border-bottom-color:black; + border-right-color:black; + font-size: medium; + font-weight: normal; + font-style: normal; } tbody, tfoot, thead { @@ -188,16 +187,16 @@ tbody, tfoot, thead { } tr { - display: table-row; - vertical-align: inherit; - border-color: inherit; + display: table-row; + vertical-align: inherit; + border-color: inherit; } td, th { - display: table-cell; - vertical-align: inherit; - border-width:1px; - padding:1px; + display: table-cell; + vertical-align: inherit; + border-width:1px; + padding:1px; } th { @@ -205,23 +204,23 @@ th { } table[border] { - border-style:solid; + border-style:solid; } -table[border|=0] { - border-style:none; +table[border^="0"] { + border-style:none; } table[border] td, table[border] th { - border-style:solid; - border-top-color:black; - border-left-color:black; - border-bottom-color:gray; - border-right-color:gray; + border-style:solid; + border-top-color:black; + border-left-color:black; + border-bottom-color:gray; + border-right-color:gray; } -table[border|=0] td, table[border|=0] th { - border-style:none; +table[border^="0"] td, table[border^="0"] th { + border-style:none; } table[align=left] { @@ -246,70 +245,70 @@ td[nowrap], th[nowrap] { } tt, code, kbd, samp { - font-family: monospace + font-family: monospace } pre, xmp, plaintext, listing { - display: block; - font-family: monospace; - white-space: pre; - margin: 1em 0 + display: block; + font-family: monospace; + white-space: pre; + margin: 1em 0 } /***************** LISTS ********************/ ul, menu, dir { - display: block; - list-style-type: disc; - margin-top: 1em; - margin-bottom: 1em; - margin-left: 0; - margin-right: 0; - padding-left: 40px + display: block; + list-style-type: disc; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; + padding-left: 40px } ol { - display: block; - list-style-type: decimal; - margin-top: 1em; - margin-bottom: 1em; - margin-left: 0; - margin-right: 0; - padding-left: 40px + display: block; + list-style-type: decimal; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; + padding-left: 40px } li { - display: list-item; + display: list-item; } ul ul, ol ul { - list-style-type: circle; + list-style-type: circle; } ol ol ul, ol ul ul, ul ol ul, ul ul ul { - list-style-type: square; + list-style-type: square; } dd { - display: block; - margin-left: 40px; + display: block; + margin-left: 40px; } dl { - display: block; - margin-top: 1em; - margin-bottom: 1em; - margin-left: 0; - margin-right: 0; + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; } dt { - display: block; + display: block; } ol ul, ul ol, ul ul, ol ol { - margin-top: 0; - margin-bottom: 0 + margin-top: 0; + margin-bottom: 0 } blockquote { diff --git a/libs/litehtml/include/litehtml/media_query.h b/libs/litehtml/include/litehtml/media_query.h index 4cb21fc2f6..94ed205e52 100644 --- a/libs/litehtml/include/litehtml/media_query.h +++ b/libs/litehtml/include/litehtml/media_query.h @@ -3,74 +3,113 @@ namespace litehtml { - struct media_query_expression + // https://drafts.csswg.org/mediaqueries/#evaluating + enum trilean { - typedef std::vector<media_query_expression> vector; - media_feature feature; - int val; - int val2; - bool check_as_bool; - - media_query_expression() - { - check_as_bool = false; - feature = media_feature_none; - val = 0; - val2 = 0; - } + False, + True, + Unknown + }; + static_assert(False == false && True == true); // for casting bool to trilean - bool check(const media_features& features) const; + inline trilean operator!(trilean x) + { + if (x == False) return True; + if (x == True) return False; + return Unknown; + } + + inline trilean operator&&(trilean a, trilean b) + { + if (a == False || b == False) return False; + if (a == True && b == True) return True; + return Unknown; + } + + inline trilean operator||(trilean a, trilean b) + { + if (a == True || b == True) return True; + if (a == False && b == False) return False; + return Unknown; + } + + + struct media_condition; + + // <media-query> = <media-condition> | [ not | only ]? <media-type> [ and <media-condition-without-or> ]? + struct media_query + { + bool m_not = true; + media_type m_media_type = media_type_all; + vector<media_condition> m_conditions; + + trilean check(const media_features& features) const; }; - class media_query + struct media_in_parens; + + // <media-condition> = not <media-in-parens> | <media-in-parens> [ [and <media-in-parens>]* | [or <media-in-parens>]* ] + struct media_condition { - public: - typedef std::shared_ptr<media_query> ptr; - typedef std::vector<media_query::ptr> vector; - private: - media_query_expression::vector m_expressions; - bool m_not; - media_type m_media_type; - public: - media_query(); - media_query(const media_query& val); + string_id op = _and_; // _not_ _and_ _or_ + vector<media_in_parens> m_conditions; + + trilean check(const media_features& features) const; + }; - static media_query::ptr create_from_string(const string& str, const std::shared_ptr<document>& doc); + // <media-feature> = ( [ <mf-plain> | <mf-boolean> | <mf-range> ] ) + // <mf-plain> = <mf-name> : <mf-value> + struct media_feature + { + string name; + float value = 0; + float value2 = 0; + short op = 0; + short op2 = 0; + + bool verify_and_convert_units(string_id syntax, css_token val[2] = 0, css_token val2[2] = 0, shared_ptr<document> doc = 0); + + bool compare(int x) const { return compare((float)x); } + bool compare(float x) const; bool check(const media_features& features) const; }; - class media_query_list + // <media-in-parens> = ( <media-condition> ) | <media-feature> | <general-enclosed> + struct unknown {}; // <general-enclosed> + struct media_in_parens : variant<media_condition, media_feature, unknown> { - public: - typedef std::shared_ptr<media_query_list> ptr; - typedef std::vector<media_query_list::ptr> vector; - private: - media_query::vector m_queries; - bool m_is_used; - public: - media_query_list(); - media_query_list(const media_query_list& val); + using base::base; // inherit ctors - static media_query_list::ptr create_from_string(const string& str, const std::shared_ptr<document>& doc); - bool is_used() const; - bool apply_media_features(const media_features& features); // returns true if the m_is_used changed + trilean check(const media_features& features) const; }; - inline media_query_list::media_query_list(const media_query_list& val) + struct media_query_list { - m_is_used = val.m_is_used; - m_queries = val.m_queries; - } + vector<media_query> m_queries; + bool empty() const { return m_queries.empty(); } + bool check(const media_features& features) const; + }; + media_query_list parse_media_query_list(const css_token_vector& tokens, shared_ptr<document> doc); + media_query_list parse_media_query_list(const string& str, shared_ptr<document> doc); - inline media_query_list::media_query_list() + + class media_query_list_list { - m_is_used = false; - } + public: + using ptr = shared_ptr<media_query_list_list>; + using vector = std::vector<ptr>; + private: + std::vector<media_query_list> m_media_query_lists; + bool m_is_used = false; + public: + void add(const media_query_list& mq_list) + { + m_media_query_lists.push_back(mq_list); + } + bool is_used() const { return m_is_used; } - inline bool media_query_list::is_used() const - { - return m_is_used; - } + bool apply_media_features(const media_features& features); // returns true if the m_is_used changed + }; } diff --git a/libs/litehtml/include/litehtml/num_cvt.h b/libs/litehtml/include/litehtml/num_cvt.h index 0eaaa68868..bdfad8ecdb 100644 --- a/libs/litehtml/include/litehtml/num_cvt.h +++ b/libs/litehtml/include/litehtml/num_cvt.h @@ -1,8 +1,7 @@ #ifndef NUM_CVT_H #define NUM_CVT_H -#include <string> -#include "os_types.h" +#include "types.h" namespace litehtml { diff --git a/libs/litehtml/include/litehtml/os_types.h b/libs/litehtml/include/litehtml/os_types.h index bf674b25b5..7300a49fae 100644 --- a/libs/litehtml/include/litehtml/os_types.h +++ b/libs/litehtml/include/litehtml/os_types.h @@ -1,24 +1,8 @@ #ifndef LH_OS_TYPES_H #define LH_OS_TYPES_H -#include <string> -#include <memory> -#include <cstdint> - -namespace litehtml -{ - using std::string; - using std::shared_ptr; - using std::make_shared; - using uint_ptr = std::uintptr_t; - #if defined( WIN32 ) || defined( _WIN32 ) || defined( WINCE ) -// noexcept appeared since Visual Studio 2015 -#if defined(_MSC_VER) && _MSC_VER < 1900 -#define noexcept -#endif - #define t_itoa(value, buffer, size, radix) _itoa_s(value, buffer, size, radix) #define t_snprintf(s, n, format, ...) _snprintf_s(s, _TRUNCATE, n, format, __VA_ARGS__) @@ -28,6 +12,5 @@ namespace litehtml #define t_snprintf(s, n, format, ...) snprintf(s, n, format, __VA_ARGS__) #endif -} #endif // LH_OS_TYPES_H diff --git a/libs/litehtml/include/litehtml/render_item.h b/libs/litehtml/include/litehtml/render_item.h index d3535429cb..d67fc42624 100644 --- a/libs/litehtml/include/litehtml/render_item.h +++ b/libs/litehtml/include/litehtml/render_item.h @@ -404,7 +404,9 @@ namespace litehtml std::shared_ptr<litehtml::render_item> > split_inlines(); bool fetch_positioned(); - void render_positioned(render_type rt = render_all); + void render_positioned(render_type width = render_all); + // returns element offset related to the containing block + std::tuple<int, int> element_static_offset(const std::shared_ptr<litehtml::render_item> &el); void add_positioned(const std::shared_ptr<litehtml::render_item> &el); void get_redraw_box(litehtml::position& pos, int x = 0, int y = 0); void calc_document_size( litehtml::size& sz, litehtml::size& content_size, int x = 0, int y = 0 ); diff --git a/libs/litehtml/include/litehtml/string_id.h b/libs/litehtml/include/litehtml/string_id.h index 46f8194727..a488b32792 100644 --- a/libs/litehtml/include/litehtml/string_id.h +++ b/libs/litehtml/include/litehtml/string_id.h @@ -153,6 +153,7 @@ STRING_ID( _nth_of_type_, _nth_last_child_, _nth_last_of_type_, + _is_, _not_, _lang_, @@ -160,6 +161,9 @@ STRING_ID( _hover_, // CSS property names + // Side properties must go in this order: top, right, bottom, left (clockwise starting from the top). + // Corner properties must go in this order: top-left, top-right, bottom-right, bottom-left (clockwise starting from the top-left). + // This is used in style::add_four_properties() for margin-*, padding-*, border-*-{width,style,color} and border-*-*-radius{,-x,-y}. _background_, _background_color_, _background_image_, @@ -183,45 +187,44 @@ STRING_ID( __litehtml_border_spacing_x_, __litehtml_border_spacing_y_, - _border_left_, - _border_right_, _border_top_, + _border_right_, _border_bottom_, + _border_left_, - _border_left_style_, - _border_right_style_, _border_top_style_, + _border_right_style_, _border_bottom_style_, + _border_left_style_, - _border_left_width_, - _border_right_width_, _border_top_width_, + _border_right_width_, _border_bottom_width_, + _border_left_width_, - _border_left_color_, - _border_right_color_, _border_top_color_, + _border_right_color_, _border_bottom_color_, + _border_left_color_, _border_radius_, _border_radius_x_, _border_radius_y_, + _border_top_left_radius_, + _border_top_right_radius_, + _border_bottom_right_radius_, _border_bottom_left_radius_, - _border_bottom_left_radius_x_, - _border_bottom_left_radius_y_, - _border_bottom_right_radius_, + _border_top_left_radius_x_, + _border_top_right_radius_x_, _border_bottom_right_radius_x_, - _border_bottom_right_radius_y_, + _border_bottom_left_radius_x_, - _border_top_left_radius_, - _border_top_left_radius_x_, _border_top_left_radius_y_, - - _border_top_right_radius_, - _border_top_right_radius_x_, _border_top_right_radius_y_, + _border_bottom_right_radius_y_, + _border_bottom_left_radius_y_, _list_style_, _list_style_type_, @@ -230,15 +233,16 @@ STRING_ID( _list_style_image_baseurl_, _margin_, - _margin_left_, - _margin_right_, _margin_top_, + _margin_right_, _margin_bottom_, + _margin_left_, + _padding_, - _padding_left_, - _padding_right_, _padding_top_, + _padding_right_, _padding_bottom_, + _padding_left_, _font_, _font_family_, @@ -268,10 +272,10 @@ STRING_ID( _float_, _clear_, _text_indent_, - _left_, - _right_, _top_, + _right_, _bottom_, + _left_, _cursor_, _content_, _border_collapse_, @@ -292,8 +296,51 @@ STRING_ID( _caption_side_, _order_, - _counter_reset_, + _counter_reset_, _counter_increment_, + + // some CSS dimensions + _deg_, + _grad_, + _rad_, + _turn_, + + // some CSS property values + _initial_, + _auto_, + _none_, + _linear_gradient_, + _repeating_linear_gradient_, + _radial_gradient_, + _repeating_radial_gradient_, + _conic_gradient_, + _repeating_conic_gradient_, + + // at-rules and their components + _charset_, + _layer_, + _import_, + _media_, + _and_, + _or_, + _boolean_, + _plain_, + _range_, + _discrete_, + _integer_, + _length_, + _resolution_, + _ratio_, + _keyword_, + _orientation_, + _portrait_, + _landscape_, + _device_width_, + _device_height_, + _aspect_ratio_, + _device_aspect_ratio_, + _color_index_, + _monochrome_, ) #undef STRING_ID extern const string_id empty_id; // _id("") diff --git a/libs/litehtml/include/litehtml/style.h b/libs/litehtml/include/litehtml/style.h index 6014f02dd0..256fd8f3ca 100644 --- a/libs/litehtml/include/litehtml/style.h +++ b/libs/litehtml/include/litehtml/style.h @@ -1,12 +1,14 @@ #ifndef LH_STYLE_H #define LH_STYLE_H -#include <variant> + +#include "css_tokenizer.h" namespace litehtml { struct invalid {}; // indicates "not found" condition in style::get_property struct inherit {}; // "inherit" was specified as the value of this property - using property_value_base = std::variant< + + struct property_value : variant< invalid, inherit, int, @@ -15,28 +17,25 @@ namespace litehtml length_vector, float, web_color, - std::vector<background_image>, + vector<image>, string, string_vector, - size_vector - >; - - struct property_value : property_value_base + size_vector, + css_token_vector + > { bool m_important = false; - bool m_has_var = false; // string; parsing is delayed because of var() + bool m_has_var = false; // css_token_vector, parsing is delayed because of var() property_value() {} template<class T> property_value(const T& val, bool important, bool has_var = false) - : property_value_base(val), m_important(important), m_has_var(has_var) {} - - template<class T> bool is() const { return std::holds_alternative<T>(*this); } - template<class T> const T& get() const { return std::get<T>(*this); } + : base(val), m_important(important), m_has_var(has_var) {} }; class html_tag; typedef std::map<string_id, property_value> props_map; + // represents a style block, eg. "color: black; display: inline" class style { public: @@ -46,12 +45,11 @@ namespace litehtml props_map m_properties; static std::map<string_id, string> m_valid_values; public: - void add(const string& txt, const string& baseurl = "", document_container* container = nullptr) - { - parse(txt, baseurl, container); - } + void add(const css_token_vector& tokens, const string& baseurl = "", document_container* container = nullptr); + void add(const string& txt, const string& baseurl = "", document_container* container = nullptr); - void add_property(string_id name, const string& val, const string& baseurl = "", bool important = false, document_container* container = nullptr); + void add_property(string_id name, const css_token_vector& tokens, const string& baseurl = "", bool important = false, document_container* container = nullptr); + void add_property(string_id name, const string& val, const string& baseurl = "", bool important = false, document_container* container = nullptr); const property_value& get_property(string_id name) const; @@ -65,28 +63,51 @@ namespace litehtml private: void inherit_property(string_id name, bool important); - void parse_property(const string& txt, const string& baseurl, document_container* container); - void parse(const string& txt, const string& baseurl, document_container* container); - void parse_background(const string& val, const string& baseurl, bool important, document_container* container); - bool parse_one_background(const string& val, document_container* container, background& bg); - void parse_background_image(const string& val, document_container* container, const string& baseurl, bool important); + + void parse_background(const css_token_vector& tokens, const string& baseurl, bool important, document_container* container); + bool parse_bg_layer(const css_token_vector& tokens, document_container* container, background& bg, bool final_layer); + // parse the value of background-image property, which is comma-separated list of <bg-image>s + void parse_background_image(const css_token_vector& tokens, const string& baseurl, bool important, document_container* container); + // parse comma-separated list of keywords - void parse_keyword_comma_list(string_id name, const string& val, bool important); - void parse_background_position(const string& val, bool important); - bool parse_one_background_position(const string& val, css_length& x, css_length& y); - void parse_background_size(const string& val, bool important); - bool parse_one_background_size(const string& val, css_size& size); - void parse_font(const string& val, bool important); - void parse_flex(const string& val, bool important); - void parse_align_self(string_id name, const string& val, bool important); - static css_length parse_border_width(const string& str); - static void parse_two_lengths(const string& str, css_length len[2]); - static int parse_four_lengths(const string& str, css_length len[4]); - static void subst_vars_(string& str, const html_tag* el); + void parse_keyword_comma_list(string_id name, const css_token_vector& tokens, bool important); + void parse_background_position(const css_token_vector& tokens, bool important); + void parse_background_size(const css_token_vector& tokens, bool important); + + void parse_border(const css_token_vector& tokens, bool important, document_container* container); + void parse_border_side(string_id name, const css_token_vector& tokens, bool important, document_container* container); + void parse_border_radius(const css_token_vector& tokens, bool important); + + bool parse_list_style_image(const css_token& tok, string& url); + void parse_list_style(const css_token_vector& tokens, string baseurl, bool important); + void parse_font(css_token_vector tokens, bool important); + + 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 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<class T> void add_four_properties(string_id top_name, T val[4], int n, bool important); void remove_property(string_id name, bool important); }; -} + + bool parse_url(const css_token& token, string& url); + bool parse_length(const css_token& tok, css_length& length, int options, string keywords = ""); + bool parse_angle(const css_token& tok, float& angle, bool percents_allowed = false); + bool parse_bg_position(const css_token_vector& tokens, int& index, css_length& x, css_length& y, bool convert_keywords_to_percents); + + template<typename Enum> + bool parse_keyword(const css_token& tok, Enum& val, string keywords, int first_keyword_value = 0) + { + int value_index(const string& val, const string& strings, int defValue = -1, char delim = ';'); + int idx = value_index(tok.ident(), keywords); + if (idx == -1) return false; + val = (Enum)(first_keyword_value + idx); + return true; + } + +} // namespace litehtml #endif // LH_STYLE_H diff --git a/libs/litehtml/include/litehtml/stylesheet.h b/libs/litehtml/include/litehtml/stylesheet.h index e614a28310..179f2de69b 100644 --- a/libs/litehtml/include/litehtml/stylesheet.h +++ b/libs/litehtml/include/litehtml/stylesheet.h @@ -3,46 +3,72 @@ #include "style.h" #include "css_selector.h" +#include "css_tokenizer.h" namespace litehtml { - class document_container; - class css - { - css_selector::vector m_selectors; - public: - css() = default; - ~css() = default; - - const css_selector::vector& selectors() const - { - return m_selectors; - } - - void clear() - { - m_selectors.clear(); - } - - void parse_stylesheet(const char* str, const char* baseurl, const std::shared_ptr<document>& doc, const media_query_list::ptr& media); - void sort_selectors(); - static void parse_css_url(const string& str, string& url); - static void parse_gradient(const string &token, document_container *container, background_gradient& grad); - - private: - void parse_atrule(const string& text, const char* baseurl, const std::shared_ptr<document>& doc, const media_query_list::ptr& media); - void add_selector(const css_selector::ptr& selector); - bool parse_selectors(const string& txt, const style::ptr& styles, const media_query_list::ptr& media); - - }; - - inline void litehtml::css::add_selector( const css_selector::ptr& selector ) +// https://www.w3.org/TR/cssom-1/#css-declarations +struct raw_declaration +{ + using vector = std::vector<raw_declaration>; + + string name; // property name + css_token_vector value = {}; // default value is specified here to get rid of gcc warning "missing initializer for member" + bool important = false; + + operator bool() const { return name != ""; } +}; + +// intermediate half-parsed rule that is used internally by the parser +class raw_rule +{ +public: + using ptr = shared_ptr<raw_rule>; + using vector = std::vector<ptr>; + + enum rule_type { qualified, at }; + + raw_rule(rule_type type, string name = "") : type(type), name(name) {} + + rule_type type; + // An at-rule has a name, a prelude consisting of a list of component values, and an optional block consisting of a simple {} block. + string name; + // https://www.w3.org/TR/css-syntax-3/#qualified-rule + // A qualified rule has a prelude consisting of a list of component values, and a block consisting of a simple {} block. + // Note: Most qualified rules will be style rules, where the prelude is a selector and the block a list of declarations. + css_token_vector prelude; + css_token block; +}; + +class css +{ + css_selector::vector m_selectors; +public: + + const css_selector::vector& selectors() const { - selector->m_order = (int) m_selectors.size(); - m_selectors.push_back(selector); + return m_selectors; } + template<class Input> + void parse_css_stylesheet(const Input& input, string baseurl, shared_ptr<document> doc, media_query_list_list::ptr media = nullptr, bool top_level = true); + + void sort_selectors(); + +private: + bool parse_style_rule(raw_rule::ptr rule, string baseurl, shared_ptr<document> doc, media_query_list_list::ptr media); + void parse_import_rule(raw_rule::ptr rule, string baseurl, shared_ptr<document> doc, media_query_list_list::ptr media); + void add_selector(const css_selector::ptr& selector); +}; + +inline void css::add_selector(const css_selector::ptr& selector) +{ + selector->m_order = (int)m_selectors.size(); + m_selectors.push_back(selector); } + +} // namespace litehtml + #endif // LH_STYLESHEET_H diff --git a/libs/litehtml/include/litehtml/table.h b/libs/litehtml/include/litehtml/table.h index d738b63ebf..2d251d6146 100644 --- a/libs/litehtml/include/litehtml/table.h +++ b/libs/litehtml/include/litehtml/table.h @@ -7,16 +7,16 @@ namespace litehtml { - class render_item; + class render_item; struct table_row { - typedef std::vector<table_row> vector; + using vector = std::vector<table_row>; int height; int border_top; int border_bottom; - std::shared_ptr<render_item> el_row; + std::shared_ptr<render_item> el_row; int top; int bottom; css_length css_height; @@ -63,7 +63,7 @@ namespace litehtml struct table_column { - typedef std::vector<table_column> vector; + using vector = std::vector<table_column>; int min_width; int max_width; @@ -140,7 +140,7 @@ namespace litehtml struct table_cell { - std::shared_ptr<render_item> el; + std::shared_ptr<render_item> el; int colspan; int rowspan; int min_width; @@ -179,7 +179,7 @@ namespace litehtml } table_cell(table_cell&& val) noexcept - { + { el = std::move(val.el); colspan = val.colspan; rowspan = val.rowspan; @@ -196,7 +196,7 @@ namespace litehtml class table_grid { public: - typedef std::vector< std::vector<table_cell> > rows; + using rows = std::vector<std::vector<table_cell>>; private: int m_rows_count; int m_cols_count; @@ -224,7 +224,7 @@ namespace litehtml table_cell* cell(int t_col, int t_row); table_column& column(int c) { return m_columns[c]; } table_row& row(int r) { return m_rows[r]; } - std::vector<std::shared_ptr<render_item>>& captions() { return m_captions; } + std::vector<std::shared_ptr<render_item>>& captions() { return m_captions; } int rows_count() const { return m_rows_count; } int cols_count() const { return m_cols_count; } diff --git a/libs/litehtml/include/litehtml/tstring_view.h b/libs/litehtml/include/litehtml/tstring_view.h index 384c8d7df5..74bf5d3241 100644 --- a/libs/litehtml/include/litehtml/tstring_view.h +++ b/libs/litehtml/include/litehtml/tstring_view.h @@ -33,7 +33,7 @@ #include <cstddef> #include <ostream> -#include "os_types.h" +#include "types.h" namespace litehtml { diff --git a/libs/litehtml/include/litehtml/types.h b/libs/litehtml/include/litehtml/types.h index 415092b28a..76427f7215 100644 --- a/libs/litehtml/include/litehtml/types.h +++ b/libs/litehtml/include/litehtml/types.h @@ -1,29 +1,61 @@ #ifndef LH_TYPES_H #define LH_TYPES_H -#include <cstdlib> +#include <cstdint> #include <memory> -#include <map> +#include <string> #include <vector> +#include <map> #include <list> +#include <set> +#include <variant> +#include <optional> namespace litehtml { + using uint_ptr = std::uintptr_t; + using std::string; + using std::vector; + using std::shared_ptr; + using std::make_shared; + using std::optional; + using std::min; + using std::max; + using std::swap; + using std::abs; + class document; class element; - typedef std::map<string, string> string_map; - typedef std::list< std::shared_ptr<element> > elements_list; - typedef std::vector<int> int_vector; - typedef std::vector<string> string_vector; + using string_map = std::map<string, string>; + using elements_list = std::list<std::shared_ptr<element>>; + using int_vector = std::vector<int>; + using string_vector = std::vector<string>; + + template <class... Types> + struct variant : std::variant<Types...> + { + using base = variant<Types...>; // for derived class ctors + using std::variant<Types...>::variant; // inherit ctors + template<class T> bool is() const { return std::holds_alternative<T>(*this); } + template<class T> const T& get() const { return std::get<T>(*this); } + template<class T> T& get() { return std::get<T>(*this); } + }; + + enum document_mode + { + no_quirks_mode, + quirks_mode, + limited_quirks_mode + }; const unsigned int font_decoration_none = 0x00; const unsigned int font_decoration_underline = 0x01; const unsigned int font_decoration_linethrough = 0x02; const unsigned int font_decoration_overline = 0x04; - typedef unsigned char byte; - typedef unsigned int ucode_t; + using byte = unsigned char; + using ucode_t = unsigned int; struct margins { @@ -37,8 +69,8 @@ namespace litehtml left = right = top = bottom = 0; } - int width() const { return left + right; } - int height() const { return top + bottom; } + int width() const { return left + right; } + int height() const { return top + bottom; } }; struct pointF @@ -68,7 +100,7 @@ namespace litehtml struct position { - typedef std::vector<position> vector; + using vector = std::vector<position>; int x; int y; @@ -130,14 +162,14 @@ namespace litehtml if(!val) return true; return ( - left() <= val->right() && - right() >= val->left() && - bottom() >= val->top() && + left() <= val->right() && + right() >= val->left() && + bottom() >= val->top() && top() <= val->bottom() ) || ( - val->left() <= right() && - val->right() >= left() && - val->bottom() >= top() && + val->left() <= right() && + val->right() >= left() && + val->bottom() >= top() && val->top() <= bottom() ); } @@ -152,7 +184,7 @@ namespace litehtml bool is_point_inside(int _x, int _y) const { - if(_x >= left() && _x <= right() && _y >= top() && _y <= bottom()) + if(_x >= left() && _x < right() && _y >= top() && _y < bottom()) { return true; } @@ -162,18 +194,22 @@ namespace litehtml struct font_metrics { + int font_size; int height; int ascent; int descent; int x_height; + int ch_width; bool draw_spaces; font_metrics() { + font_size = 0; height = 0; ascent = 0; descent = 0; x_height = 0; + ch_width = 0; draw_spaces = true; } int base_line() const { return descent; } @@ -185,7 +221,7 @@ namespace litehtml font_metrics metrics; }; - typedef std::map<string, font_item> fonts_map; + using fonts_map = std::map<string, font_item>; enum draw_flag { @@ -219,6 +255,8 @@ namespace litehtml int value; cbc_value_type type; + typed_int(const typed_int& v) = default; + typed_int(int val, cbc_value_type tp) { value = val; @@ -236,12 +274,7 @@ namespace litehtml return *this; } - typed_int& operator=(const typed_int& v) - { - value = v.value; - type = v.type; - return *this; - } + typed_int& operator=(const typed_int& v) = default; }; typed_int width; // width of the containing block @@ -342,6 +375,8 @@ namespace litehtml font_style_italic }; +#define font_system_family_name_strings "caption;icon;menu;message-box;small-caption;status-bar" + #define font_variant_strings "normal;small-caps" enum font_variant @@ -350,7 +385,7 @@ namespace litehtml font_variant_small_caps }; -#define font_weight_strings "normal;bold;bolder;lighter;100;200;300;400;500;600;700;800;900" +#define font_weight_strings "normal;bold;bolder;lighter" enum font_weight { @@ -358,15 +393,6 @@ namespace litehtml font_weight_bold, font_weight_bolder, font_weight_lighter, - font_weight_100, - font_weight_200, - font_weight_300, - font_weight_400, - font_weight_500, - font_weight_600, - font_weight_700, - font_weight_800, - font_weight_900 }; #define list_style_type_strings "none;circle;disc;square;armenian;cjk-ideographic;decimal;decimal-leading-zero;georgian;hebrew;hiragana;hiragana-iroha;katakana;katakana-iroha;lower-alpha;lower-greek;lower-latin;lower-roman;upper-alpha;upper-latin;upper-roman" @@ -467,9 +493,9 @@ namespace litehtml clear_both }; -#define css_units_strings "none;%;in;cm;mm;em;ex;pt;pc;px;dpi;dpcm;vw;vh;vmin;vmax;rem" +#define css_units_strings "none;%;in;cm;mm;em;ex;pt;pc;px;vw;vh;vmin;vmax;rem;ch" - enum css_units : byte + enum css_units : byte // see css_length { css_units_none, css_units_percentage, @@ -481,13 +507,12 @@ namespace litehtml css_units_pt, css_units_pc, css_units_px, - css_units_dpi, - css_units_dpcm, css_units_vw, css_units_vh, css_units_vmin, css_units_vmax, css_units_rem, + css_units_ch, }; #define background_attachment_strings "scroll;fixed" @@ -508,6 +533,7 @@ namespace litehtml background_repeat_no_repeat }; +// https://drafts.csswg.org/css-box-4/#typedef-visual-box #define background_box_strings "border-box;padding-box;content-box" enum background_box @@ -517,14 +543,15 @@ namespace litehtml background_box_content }; -#define background_position_strings "top;bottom;left;right;center" +#define background_position_strings "left;right;top;bottom;center" + const float background_position_percentages[] = {0, 100, 0, 100, 50}; enum background_position { - background_position_top, - background_position_bottom, background_position_left, background_position_right, + background_position_top, + background_position_bottom, background_position_center, }; @@ -585,7 +612,7 @@ namespace litehtml enum background_size { - background_size_auto, + background_size_auto, // must be first, see parse_bg_size background_size_cover, background_size_contain, }; @@ -640,16 +667,9 @@ namespace litehtml context = val.context; min_width = val.min_width; } - floated_box& operator=(const floated_box& val) - { - pos = val.pos; - float_side = val.float_side; - clear_floats = val.clear_floats; - el = val.el; - context = val.context; - min_width = val.min_width; - return *this; - } + + floated_box& operator=(const floated_box& val) = default; + floated_box(floated_box&& val) { pos = val.pos; @@ -810,63 +830,6 @@ namespace litehtml }; -#define media_orientation_strings "portrait;landscape" - - enum media_orientation - { - media_orientation_portrait, - media_orientation_landscape, - }; - -#define media_feature_strings "none;width;min-width;max-width;height;min-height;max-height;device-width;min-device-width;max-device-width;device-height;min-device-height;max-device-height;orientation;aspect-ratio;min-aspect-ratio;max-aspect-ratio;device-aspect-ratio;min-device-aspect-ratio;max-device-aspect-ratio;color;min-color;max-color;color-index;min-color-index;max-color-index;monochrome;min-monochrome;max-monochrome;resolution;min-resolution;max-resolution" - - enum media_feature - { - media_feature_none, - - media_feature_width, - media_feature_min_width, - media_feature_max_width, - - media_feature_height, - media_feature_min_height, - media_feature_max_height, - - media_feature_device_width, - media_feature_min_device_width, - media_feature_max_device_width, - - media_feature_device_height, - media_feature_min_device_height, - media_feature_max_device_height, - - media_feature_orientation, - - media_feature_aspect_ratio, - media_feature_min_aspect_ratio, - media_feature_max_aspect_ratio, - - media_feature_device_aspect_ratio, - media_feature_min_device_aspect_ratio, - media_feature_max_device_aspect_ratio, - - media_feature_color, - media_feature_min_color, - media_feature_max_color, - - media_feature_color_index, - media_feature_min_color_index, - media_feature_max_color_index, - - media_feature_monochrome, - media_feature_min_monochrome, - media_feature_max_monochrome, - - media_feature_resolution, - media_feature_min_resolution, - media_feature_max_resolution, - }; - #define box_sizing_strings "content-box;border-box" enum box_sizing @@ -875,22 +838,18 @@ namespace litehtml box_sizing_border_box, }; - -#define media_type_strings "none;all;screen;print;braille;embossed;handheld;projection;speech;tty;tv" +// https://drafts.csswg.org/mediaqueries/#media-types +// User agents must recognize the following media types as valid, but must make them match nothing. +#define deprecated_media_type_strings "tty;tv;projection;handheld;braille;embossed;aural;speech" +#define media_type_strings "all;print;screen;" deprecated_media_type_strings enum media_type { - media_type_none, + media_type_unknown, media_type_all, - media_type_screen, media_type_print, - media_type_braille, - media_type_embossed, - media_type_handheld, - media_type_projection, - media_type_speech, - media_type_tty, - media_type_tv, + media_type_screen, + media_type_first_deprecated }; struct media_features @@ -907,8 +866,8 @@ namespace litehtml media_features() { - type = media_type::media_type_none, - width =0; + type = media_type_unknown; + width = 0; height = 0; device_width = 0; device_height = 0; @@ -968,19 +927,24 @@ namespace litehtml flex_justify_content_stretch, }; -#define flex_align_items_strings "normal;flex-start;flex-end;center;start;end;baseline;stretch;auto" +#define self_position_strings "center;start;end;self-start;self-end;flex-start;flex-end" +#define flex_align_items_strings "auto;normal;stretch;baseline;" self_position_strings enum flex_align_items { - flex_align_items_flex_normal, - flex_align_items_flex_start, - flex_align_items_flex_end, + flex_align_items_auto, // used for align-self property only + flex_align_items_normal, + flex_align_items_stretch, + flex_align_items_baseline, + flex_align_items_center, flex_align_items_start, flex_align_items_end, - flex_align_items_baseline, - flex_align_items_stretch, - flex_align_items_auto, // used for align-self property only + flex_align_items_self_start, + flex_align_items_self_end, + flex_align_items_flex_start, + flex_align_items_flex_end, + flex_align_items_first = 0x100, flex_align_items_last = 0x200, flex_align_items_unsafe = 0x400, diff --git a/libs/litehtml/include/litehtml/url.h b/libs/litehtml/include/litehtml/url.h index bd0cdc63c9..83f98a752e 100644 --- a/libs/litehtml/include/litehtml/url.h +++ b/libs/litehtml/include/litehtml/url.h @@ -32,7 +32,7 @@ #include <ostream> -#include "os_types.h" +#include "types.h" // https://datatracker.ietf.org/doc/html/rfc3986 diff --git a/libs/litehtml/include/litehtml/url_path.h b/libs/litehtml/include/litehtml/url_path.h index 6b102139d4..6ba68b03b8 100644 --- a/libs/litehtml/include/litehtml/url_path.h +++ b/libs/litehtml/include/litehtml/url_path.h @@ -32,7 +32,7 @@ #include <ostream> -#include "os_types.h" +#include "types.h" namespace litehtml { diff --git a/libs/litehtml/include/litehtml/utf8_strings.h b/libs/litehtml/include/litehtml/utf8_strings.h index a4f1a78fa8..7930a437a7 100644 --- a/libs/litehtml/include/litehtml/utf8_strings.h +++ b/libs/litehtml/include/litehtml/utf8_strings.h @@ -1,47 +1,35 @@ #ifndef LH_UTF8_STRINGS_H #define LH_UTF8_STRINGS_H -#include "os_types.h" #include "types.h" namespace litehtml { // converts UTF-32 ch to UTF-8 and appends it to str void append_char(string& str, char32_t ch); + char32_t read_utf8_char(const string& str, int& index); + void prev_utf8_char(const string& str, int& index); class utf8_to_utf32 { - const byte* m_utf8; std::u32string m_str; public: - utf8_to_utf32(const char* val); + utf8_to_utf32(const string& val); operator const char32_t*() const { return m_str.c_str(); } - private: - char32_t getb() - { - if (!(*m_utf8)) return 0; - return *m_utf8++; - } - char32_t get_next_utf8(char32_t val) - { - return (val & 0x3f); - } - char32_t get_char(); }; class utf32_to_utf8 { - std::string m_str; + string m_str; public: utf32_to_utf8(const std::u32string& val); operator const char*() const { return m_str.c_str(); } - const char* c_str() const { return m_str.c_str(); diff --git a/libs/litehtml/include/litehtml/web_color.h b/libs/litehtml/include/litehtml/web_color.h index 145a4157c3..9bc198dbcd 100644 --- a/libs/litehtml/include/litehtml/web_color.h +++ b/libs/litehtml/include/litehtml/web_color.h @@ -3,63 +3,33 @@ namespace litehtml { - struct def_color - { - const char* name; - const char* rgb; - }; - - extern def_color g_def_colors[]; - class document_container; struct web_color { - byte red; - byte green; - byte blue; - byte alpha; + byte red = 0; + byte green = 0; + byte blue = 0; + byte alpha = 255; + bool is_current_color = false; static const web_color transparent; static const web_color black; static const web_color white; + static const web_color current_color; - web_color(byte r, byte g, byte b, byte a = 255) - { - red = r; - green = g; - blue = b; - alpha = a; - } - - web_color() - { - red = 0; - green = 0; - blue = 0; - alpha = 0xFF; - } + web_color() {} + web_color(byte r, byte g, byte b, byte a = 255) : red(r), green(g), blue(b), alpha(a) {} + web_color(bool is_current_color) : is_current_color(is_current_color) {} bool operator==(web_color color) const { return red == color.red && green == color.green && blue == color.blue && alpha == color.alpha; } bool operator!=(web_color color) const { return !(*this == color); } + web_color darken(double fraction) const; string to_string() const; - static web_color from_string(const string& str, document_container* callback); - static string resolve_name(const string& name, document_container* callback); - static bool is_color(const string& str, document_container* callback); - web_color darken(double fraction) const - { - int v_red = (int) red; - int v_blue = (int) blue; - int v_green = (int) green; - v_red = (int) std::max(v_red - (v_red * fraction), 0.0); - v_blue = (int) std::max(v_blue - (v_blue * fraction), 0.0); - v_green = (int) std::max(v_green - (v_green * fraction), 0.0); - return {(byte) v_red, (byte) v_green, (byte) v_blue, alpha}; - } }; - typedef std::vector<web_color> web_color_vector; + bool parse_color(const css_token& token, web_color& color, document_container* container); } #endif // LH_WEB_COLOR_H diff --git a/libs/litehtml/litehtml.vcxproj b/libs/litehtml/litehtml.vcxproj index 7fc6f311f3..3f9ad89b91 100644 --- a/libs/litehtml/litehtml.vcxproj +++ b/libs/litehtml/litehtml.vcxproj @@ -48,12 +48,18 @@ <ClCompile Include="src\css_length.cpp"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> + <ClCompile Include="src\css_parser.cpp"> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + </ClCompile> <ClCompile Include="src\css_properties.cpp"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> <ClCompile Include="src\css_selector.cpp"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> + <ClCompile Include="src\css_tokenizer.cpp"> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + </ClCompile> <ClCompile Include="src\document.cpp"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> @@ -174,6 +180,9 @@ <ClCompile Include="src\html.cpp"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> + <ClCompile Include="src\html_microsyntaxes.cpp"> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + </ClCompile> <ClCompile Include="src\html_tag.cpp"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> @@ -308,5 +317,6 @@ <ClInclude Include="include\litehtml\types.h" /> <ClInclude Include="include\litehtml\utf8_strings.h" /> <ClInclude Include="include\litehtml\web_color.h" /> + <ClInclude Include="src\internal.h" /> </ItemGroup> </Project>
\ No newline at end of file diff --git a/libs/litehtml/litehtml.vcxproj.filters b/libs/litehtml/litehtml.vcxproj.filters index ea05d9a68e..428bf16aa9 100644 --- a/libs/litehtml/litehtml.vcxproj.filters +++ b/libs/litehtml/litehtml.vcxproj.filters @@ -225,6 +225,15 @@ <ClCompile Include="src\gradient.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\css_parser.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\css_tokenizer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\html_microsyntaxes.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="include\litehtml\background.h"> @@ -425,6 +434,9 @@ <ClInclude Include="include\litehtml\flex_line.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="src\internal.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" /> </Project>
\ No newline at end of file diff --git a/libs/litehtml/src/background.cpp b/libs/litehtml/src/background.cpp index b7ae75c6b6..3ef69b862d 100644 --- a/libs/litehtml/src/background.cpp +++ b/libs/litehtml/src/background.cpp @@ -1,4 +1,5 @@ #include <cmath> + #include "html.h" #include "background.h" #include "render_item.h" @@ -7,6 +8,9 @@ # define M_PI 3.14159265358979323846 #endif +namespace litehtml +{ + bool litehtml::background::get_layer(int idx, position pos, const element* el, const std::shared_ptr<render_item>& ri, background_layer& layer) const { if(idx < 0 || idx >= get_layers_number()) @@ -175,9 +179,9 @@ std::unique_ptr<litehtml::background_layer::image> litehtml::background::get_ima { if(idx >= 0 && idx < (int) m_image.size()) { - if(m_image[idx].type == background_image::bg_image_type_url) + if(m_image[idx].type == image::type_url) { - auto ret = std::unique_ptr<background_layer::image>(new background_layer::image()); + auto ret = std::make_unique<background_layer::image>(); ret->url = m_image[idx].url; ret->base_url = m_baseurl; return ret; @@ -190,7 +194,7 @@ std::unique_ptr<litehtml::background_layer::color> litehtml::background::get_col { if(idx == (int) m_image.size()) { - auto ret = std::unique_ptr<background_layer::color>(new background_layer::color()); + auto ret = std::make_unique<background_layer::color>(); ret->color = m_color; return ret; } @@ -280,24 +284,24 @@ static float distance(const litehtml::pointF& p1, const litehtml::pointF& p2) std::unique_ptr<litehtml::background_layer::linear_gradient> litehtml::background::get_linear_gradient_layer(int idx, const background_layer& layer) const { if(idx < 0 || idx >= (int) m_image.size()) return {}; - if(m_image[idx].type != background_image::bg_image_type_gradient) return {}; - if(m_image[idx].gradient.m_type != background_gradient::linear_gradient && - m_image[idx].gradient.m_type != background_gradient::repeating_linear_gradient) return {}; + if(m_image[idx].type != image::type_gradient) return {}; + if(m_image[idx].m_gradient.m_type != _linear_gradient_ && + m_image[idx].m_gradient.m_type != _repeating_linear_gradient_) return {}; - auto ret = std::unique_ptr<background_layer::linear_gradient>(new background_layer::linear_gradient()); + auto ret = std::make_unique<background_layer::linear_gradient>(); float angle; - if(m_image[idx].gradient.m_side == 0) + if(m_image[idx].m_gradient.m_side == 0) { - angle = m_image[idx].gradient.angle; + angle = m_image[idx].m_gradient.angle; } else { auto rise = (float) layer.origin_box.width; auto run = (float) layer.origin_box.height; - if(m_image[idx].gradient.m_side & background_gradient::gradient_side_left) + if(m_image[idx].m_gradient.m_side & gradient_side_left) { run *= -1; } - if(m_image[idx].gradient.m_side & background_gradient::gradient_side_bottom) + if(m_image[idx].m_gradient.m_side & gradient_side_bottom) { rise *= -1; } @@ -312,7 +316,7 @@ std::unique_ptr<litehtml::background_layer::linear_gradient> litehtml::backgroun auto line_len = distance(ret->start, ret->end); - if(!ret->prepare_color_points(line_len, m_image[idx].gradient.m_type, m_image[idx].gradient.m_colors)) + if(!ret->prepare_color_points(line_len, m_image[idx].m_gradient.m_type, m_image[idx].m_gradient.m_colors)) { return {}; } @@ -392,51 +396,51 @@ static inline litehtml::pointF find_corner(const litehtml::pointF& center, const std::unique_ptr<litehtml::background_layer::radial_gradient> litehtml::background::get_radial_gradient_layer(int idx, const background_layer& layer) const { if(idx < 0 || idx >= (int) m_image.size()) return {}; - if(m_image[idx].type != background_image::bg_image_type_gradient) return {}; - if(m_image[idx].gradient.m_type != background_gradient::radial_gradient && - m_image[idx].gradient.m_type != background_gradient::repeating_radial_gradient) return {}; + if(m_image[idx].type != image::type_gradient) return {}; + if(m_image[idx].m_gradient.m_type != _radial_gradient_ && + m_image[idx].m_gradient.m_type != _repeating_radial_gradient_) return {}; - auto ret = std::unique_ptr<background_layer::radial_gradient>(new background_layer::radial_gradient()); + auto ret = std::make_unique<background_layer::radial_gradient>(); ret->position.x = (float) layer.origin_box.x + (float) layer.origin_box.width / 2.0f; ret->position.y = (float) layer.origin_box.y + (float) layer.origin_box.height / 2.0f; - if(m_image[idx].gradient.m_side & background_gradient::gradient_side_left) + if(m_image[idx].m_gradient.m_side & gradient_side_left) { ret->position.x = (float) layer.origin_box.left(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_right) + } else if(m_image[idx].m_gradient.m_side & gradient_side_right) { ret->position.x = (float) layer.origin_box.right(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_x_center) + } else if(m_image[idx].m_gradient.m_side & gradient_side_x_center) { ret->position.x = (float) layer.origin_box.left() + (float) layer.origin_box.width / 2.0f; - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_x_length) + } else if(m_image[idx].m_gradient.m_side & gradient_side_x_length) { - ret->position.x = (float) layer.origin_box.left() + (float) m_image[idx].gradient.radial_position_x.calc_percent(layer.origin_box.width); + ret->position.x = (float) layer.origin_box.left() + (float) m_image[idx].m_gradient.position_x.calc_percent(layer.origin_box.width); } - if(m_image[idx].gradient.m_side & background_gradient::gradient_side_top) + if(m_image[idx].m_gradient.m_side & gradient_side_top) { ret->position.y = (float) layer.origin_box.top(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_bottom) + } else if(m_image[idx].m_gradient.m_side & gradient_side_bottom) { ret->position.y = (float) layer.origin_box.bottom(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_y_center) + } else if(m_image[idx].m_gradient.m_side & gradient_side_y_center) { ret->position.y = (float) layer.origin_box.top() + (float) layer.origin_box.height / 2.0f; - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_y_length) + } else if(m_image[idx].m_gradient.m_side & gradient_side_y_length) { - ret->position.y = (float) layer.origin_box.top() + (float) m_image[idx].gradient.radial_position_y.calc_percent(layer.origin_box.height); + ret->position.y = (float) layer.origin_box.top() + (float) m_image[idx].m_gradient.position_y.calc_percent(layer.origin_box.height); } - if(m_image[idx].gradient.radial_extent) + if(m_image[idx].m_gradient.radial_extent) { - switch (m_image[idx].gradient.radial_extent) + switch (m_image[idx].m_gradient.radial_extent) { - case background_gradient::radial_extent_closest_corner: + case radial_extent_closest_corner: { - if (m_image[idx].gradient.radial_shape == background_gradient::radial_shape_circle) + if (m_image[idx].m_gradient.radial_shape == radial_shape_circle) { float corner1 = distance(ret->position, {(float) layer.origin_box.left(), (float) layer.origin_box.top()}); float corner2 = distance(ret->position, {(float) layer.origin_box.right(), (float) layer.origin_box.top()}); @@ -461,8 +465,8 @@ std::unique_ptr<litehtml::background_layer::radial_gradient> litehtml::backgroun } } break; - case background_gradient::radial_extent_closest_side: - if (m_image[idx].gradient.radial_shape == background_gradient::radial_shape_circle) + case radial_extent_closest_side: + if (m_image[idx].m_gradient.radial_shape == radial_shape_circle) { ret->radius.x = ret->radius.y = std::min( { @@ -483,9 +487,9 @@ std::unique_ptr<litehtml::background_layer::radial_gradient> litehtml::backgroun ); } break; - case background_gradient::radial_extent_farthest_corner: + case radial_extent_farthest_corner: { - if (m_image[idx].gradient.radial_shape == background_gradient::radial_shape_circle) + if (m_image[idx].m_gradient.radial_shape == radial_shape_circle) { float corner1 = distance(ret->position, {(float) layer.origin_box.left(), (float) layer.origin_box.top()}); float corner2 = distance(ret->position, {(float) layer.origin_box.right(), (float) layer.origin_box.top()}); @@ -510,8 +514,8 @@ std::unique_ptr<litehtml::background_layer::radial_gradient> litehtml::backgroun } } break; - case background_gradient::radial_extent_farthest_side: - if (m_image[idx].gradient.radial_shape == background_gradient::radial_shape_circle) + case radial_extent_farthest_side: + if (m_image[idx].m_gradient.radial_shape == radial_shape_circle) { ret->radius.x = ret->radius.y = std::max( { @@ -536,16 +540,16 @@ std::unique_ptr<litehtml::background_layer::radial_gradient> litehtml::backgroun break; } } - if(!m_image[idx].gradient.radial_length_x.is_predefined()) + if(!m_image[idx].m_gradient.radial_radius_x.is_predefined()) { - ret->radius.x = (float) m_image[idx].gradient.radial_length_x.calc_percent(layer.origin_box.width); + ret->radius.x = (float) m_image[idx].m_gradient.radial_radius_x.calc_percent(layer.origin_box.width); } - if(!m_image[idx].gradient.radial_length_y.is_predefined()) + if(!m_image[idx].m_gradient.radial_radius_y.is_predefined()) { - ret->radius.y = (float) m_image[idx].gradient.radial_length_y.calc_percent(layer.origin_box.height); + ret->radius.y = (float) m_image[idx].m_gradient.radial_radius_y.calc_percent(layer.origin_box.height); } - if(ret->prepare_color_points(ret->radius.x, m_image[idx].gradient.m_type, m_image[idx].gradient.m_colors)) + if(ret->prepare_color_points(ret->radius.x, m_image[idx].m_gradient.m_type, m_image[idx].m_gradient.m_colors)) { return ret; } @@ -556,47 +560,48 @@ std::unique_ptr<litehtml::background_layer::radial_gradient> litehtml::backgroun std::unique_ptr<litehtml::background_layer::conic_gradient> litehtml::background::get_conic_gradient_layer(int idx, const background_layer& layer) const { if(idx < 0 || idx >= (int) m_image.size()) return {}; - if(m_image[idx].type != background_image::bg_image_type_gradient) return {}; - if(m_image[idx].gradient.m_type != background_gradient::conic_gradient) return {}; + if(m_image[idx].type != image::type_gradient) return {}; + if (m_image[idx].m_gradient.m_type != _conic_gradient_ && + m_image[idx].m_gradient.m_type != _repeating_conic_gradient_) return {}; - auto ret = std::unique_ptr<background_layer::conic_gradient>(new background_layer::conic_gradient()); + auto ret = std::make_unique<background_layer::conic_gradient>(); ret->position.x = (float) layer.origin_box.x + (float) layer.origin_box.width / 2.0f; ret->position.y = (float) layer.origin_box.y + (float) layer.origin_box.height / 2.0f; - if(m_image[idx].gradient.m_side & background_gradient::gradient_side_left) + if(m_image[idx].m_gradient.m_side & gradient_side_left) { ret->position.x = (float) layer.origin_box.left(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_right) + } else if(m_image[idx].m_gradient.m_side & gradient_side_right) { ret->position.x = (float) layer.origin_box.right(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_x_center) + } else if(m_image[idx].m_gradient.m_side & gradient_side_x_center) { ret->position.x = (float) layer.origin_box.left() + (float) layer.origin_box.width / 2.0f; - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_x_length) + } else if(m_image[idx].m_gradient.m_side & gradient_side_x_length) { - ret->position.x = (float) layer.origin_box.left() + (float) m_image[idx].gradient.radial_position_x.calc_percent(layer.origin_box.width); + ret->position.x = (float) layer.origin_box.left() + (float) m_image[idx].m_gradient.position_x.calc_percent(layer.origin_box.width); } - if(m_image[idx].gradient.m_side & background_gradient::gradient_side_top) + if(m_image[idx].m_gradient.m_side & gradient_side_top) { ret->position.y = (float) layer.origin_box.top(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_bottom) + } else if(m_image[idx].m_gradient.m_side & gradient_side_bottom) { ret->position.y = (float) layer.origin_box.bottom(); - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_y_center) + } else if(m_image[idx].m_gradient.m_side & gradient_side_y_center) { ret->position.y = (float) layer.origin_box.top() + (float) layer.origin_box.height / 2.0f; - } else if(m_image[idx].gradient.m_side & background_gradient::gradient_side_y_length) + } else if(m_image[idx].m_gradient.m_side & gradient_side_y_length) { - ret->position.y = (float) layer.origin_box.top() + (float) m_image[idx].gradient.radial_position_y.calc_percent(layer.origin_box.height); + ret->position.y = (float) layer.origin_box.top() + (float) m_image[idx].m_gradient.position_y.calc_percent(layer.origin_box.height); } - ret->angle = m_image[idx].gradient.conic_from_angle; - ret->color_space = m_image[idx].gradient.conic_color_space; - ret->interpolation = m_image[idx].gradient.conic_interpolation; + ret->angle = m_image[idx].m_gradient.conic_from_angle; + ret->color_space = m_image[idx].m_gradient.color_space; + ret->hue_interpolation = m_image[idx].m_gradient.hue_interpolation; - if(ret->prepare_angle_color_points(m_image[idx].gradient.m_type, m_image[idx].gradient.m_colors)) + if(ret->prepare_color_points(0, m_image[idx].m_gradient.m_type, m_image[idx].m_gradient.m_colors)) { return ret; } @@ -611,19 +616,19 @@ litehtml::background::layer_type litehtml::background::get_layer_type(int idx) c switch (m_image[idx].type) { - case background_image::bg_image_type_url: + case image::type_url: return type_image; - case background_image::bg_image_type_gradient: - switch (m_image[idx].gradient.m_type) + case image::type_gradient: + switch (m_image[idx].m_gradient.m_type) { - case background_gradient::linear_gradient: - case background_gradient::repeating_linear_gradient: + case _linear_gradient_: + case _repeating_linear_gradient_: return type_linear_gradient; - case background_gradient::radial_gradient: - case background_gradient::repeating_radial_gradient: + case _radial_gradient_: + case _repeating_radial_gradient_: return type_radial_gradient; - case background_gradient::conic_gradient: - case background_gradient::repeating_conic_gradient: + case _conic_gradient_: + case _repeating_conic_gradient_: return type_conic_gradient; default: break; @@ -653,6 +658,7 @@ void litehtml::background::draw_layer(uint_ptr hdc, int idx, const background_la } break; case background::type_image: + if(layer.origin_box.width != 0 && layer.origin_box.height != 0) { auto image_layer = get_image_layer(idx); if(image_layer) @@ -662,6 +668,7 @@ void litehtml::background::draw_layer(uint_ptr hdc, int idx, const background_la } break; case background::type_linear_gradient: + if(layer.origin_box.width != 0 && layer.origin_box.height != 0) { auto gradient_layer = get_linear_gradient_layer(idx, layer); if(gradient_layer) @@ -671,6 +678,7 @@ void litehtml::background::draw_layer(uint_ptr hdc, int idx, const background_la } break; case background::type_radial_gradient: + if(layer.origin_box.width != 0 && layer.origin_box.height != 0) { auto gradient_layer = get_radial_gradient_layer(idx, layer); if(gradient_layer) @@ -680,6 +688,7 @@ void litehtml::background::draw_layer(uint_ptr hdc, int idx, const background_la } break; case background::type_conic_gradient: + if(layer.origin_box.width != 0 && layer.origin_box.height != 0) { auto gradient_layer = get_conic_gradient_layer(idx, layer); if(gradient_layer) @@ -693,14 +702,14 @@ void litehtml::background::draw_layer(uint_ptr hdc, int idx, const background_la } } -static void repeat_color_points(std::vector<litehtml::background_layer::color_point>& color_points, float max_val) +static void repeat_color_points(std::vector<litehtml::background_layer::color_point>& color_points) { auto old_points = color_points; - if(color_points.back().offset < max_val) + if(color_points.back().offset < 1) { float gd_size = color_points.back().offset - old_points.front().offset; auto iter = old_points.begin(); - while (color_points.back().offset < max_val) + while (color_points.back().offset < 1) { color_points.emplace_back(iter->offset + gd_size, iter->color); std::advance(iter, 1); @@ -763,13 +772,27 @@ void litehtml::background_layer::gradient_base::color_points_transparent_fix() } } -bool litehtml::background_layer::gradient_base::prepare_color_points(float line_len, background_gradient::gradient_type g_type, const std::vector<background_gradient::gradient_color> &colors) +// normalize length into value between 0 and 1 +float normalize_length(css_length length, float line_len) +{ + if (length.units() == css_units_percentage) + { + return length.val() / 100.0f; + } + else if (line_len != 0) + { + return length.val() / line_len; + } + return length.val(); +} + +bool litehtml::background_layer::gradient_base::prepare_color_points(float line_len, string_id g_type, const std::vector<gradient::color_stop> &colors) { bool repeating; - if(g_type == background_gradient::linear_gradient || g_type == background_gradient::radial_gradient) + if(g_type == _linear_gradient_ || g_type == _radial_gradient_ || g_type == _conic_gradient_) { repeating = false; - } else if(g_type == background_gradient::repeating_linear_gradient || g_type == background_gradient::repeating_radial_gradient) + } else if(g_type == _repeating_linear_gradient_ || g_type == _repeating_radial_gradient_ || g_type == _repeating_conic_gradient_) { repeating = true; } else @@ -780,22 +803,31 @@ bool litehtml::background_layer::gradient_base::prepare_color_points(float line_ bool has_transparent = false; for(const auto& item : colors) { - if(item.color.alpha == 0) + if (item.is_color_hint) + { + if (!color_points.empty()) + { + color_points.back().hint = item.length ? + normalize_length(*item.length, line_len) : + *item.angle / 360; + } + continue; + } + if (item.color.alpha == 0) { has_transparent = true; } - if(item.length.units() == css_units_percentage) + if (item.length) { - color_points.emplace_back(item.length.val() / 100.0f, item.color); - } else if(item.length.units() != css_units_none) + color_points.emplace_back(normalize_length(*item.length, line_len), item.color); + } + else if (item.angle) { - if(line_len != 0) - { - color_points.emplace_back(item.length.val() / line_len, item.color); - } - } else + color_points.emplace_back(*item.angle / 360, item.color); + } + else { - if(!color_points.empty()) + if (!color_points.empty()) { none_units++; } @@ -868,112 +900,10 @@ bool litehtml::background_layer::gradient_base::prepare_color_points(float line_ if(repeating) { - repeat_color_points(color_points, 1.0f); + repeat_color_points(color_points); } return true; } -bool litehtml::background_layer::gradient_base::prepare_angle_color_points(background_gradient::gradient_type g_type, const std::vector<background_gradient::gradient_color> &colors) -{ - bool repeating; - if(g_type != background_gradient::conic_gradient) - { - repeating = false; - } else if(g_type != background_gradient::repeating_conic_gradient) - { - repeating = true; - } else - { - return false; - } - int none_units = 0; - bool has_transparent = false; - for(const auto& item : colors) - { - if(item.color.alpha == 0) - { - has_transparent = true; - } - if(item.angle.is_default()) - { - if(!color_points.empty()) - { - none_units++; - } - color_points.emplace_back(0.0f, item.color); - } else - { - color_points.emplace_back(item.angle, item.color); - } - } - if(color_points.empty()) - { - return false; - } - - if(!repeating) - { - // Add color point with offset 0 if not exists - if (color_points[0].offset != 0) - { - color_points.emplace(color_points.begin(), 0.0f, color_points[0].color); - } - // Add color point with offset 1.0 if not exists - if (color_points.back().offset < 360.0f) - { - if (color_points.back().offset == 0) - { - color_points.back().offset = 360.0f; - none_units--; - } else - { - color_points.emplace_back(360.0f, color_points.back().color); - } - } - } else - { - if (color_points.back().offset == 0) - { - color_points.back().offset = 360.0f; - none_units--; - } - } - - if(none_units > 0) - { - size_t i = 1; - while(i < color_points.size()) - { - if(color_points[i].offset != 0) - { - i++; - continue; - } - // Find next defined offset - size_t j = i + 1; - while (color_points[j].offset == 0) j++; - size_t num = j - i; - float sum = color_points[i - 1].offset + color_points[j].offset; - float offset = sum / (float) (num + 1); - while(i < j) - { - color_points[i].offset = color_points[i - 1].offset + offset; - i++; - } - } - } - - // process transparent - if(has_transparent) - { - color_points_transparent_fix(); - } - - if(repeating) - { - repeat_color_points(color_points, 1.0f); - } - - return true; -} +} // namespace litehtml
\ No newline at end of file diff --git a/libs/litehtml/src/codepoint.cpp b/libs/litehtml/src/codepoint.cpp index 61ff682ada..d93230e41b 100644 --- a/libs/litehtml/src/codepoint.cpp +++ b/libs/litehtml/src/codepoint.cpp @@ -29,7 +29,7 @@ #include "codepoint.h" -#include <iostream> +#include <cstdint> namespace { diff --git a/libs/litehtml/src/css_borders.cpp b/libs/litehtml/src/css_borders.cpp index 478e9df5bf..2b0fe6d845 100644 --- a/libs/litehtml/src/css_borders.cpp +++ b/libs/litehtml/src/css_borders.cpp @@ -3,5 +3,5 @@ litehtml::string litehtml::css_border::to_string() const { - return width.to_string() + "/" + index_value(style, border_style_strings) + "/" + color.to_string(); + return width.to_string() + "/" + index_value(style, border_style_strings) + "/" + color.to_string(); } diff --git a/libs/litehtml/src/css_length.cpp b/libs/litehtml/src/css_length.cpp index d1c69d2612..798eb0bd37 100644 --- a/libs/litehtml/src/css_length.cpp +++ b/libs/litehtml/src/css_length.cpp @@ -1,77 +1,78 @@ #include "html.h" #include "css_length.h" -void litehtml::css_length::fromString( const string& str, const string& predefs, int defValue ) +namespace litehtml { - // TODO: Make support for calc - if(str.substr(0, 4) == "calc") + +bool css_length::from_token(const css_token& token, int options, const string& keywords) +{ + if ((options & f_positive) && is_one_of(token.type, NUMBER, DIMENSION, PERCENTAGE) && token.n.number < 0) + return false; + + if (token.type == IDENT) { + int idx = value_index(lowcase(token.name), keywords); + if (idx == -1) return false; + m_predef = idx; m_is_predefined = true; - m_predef = defValue; - return; + return true; } + else if (token.type == DIMENSION) + { + if (!(options & f_length)) return false; - int predef = value_index(str, predefs, -1); - if(predef >= 0) + int idx = value_index(lowcase(token.unit), css_units_strings); + // note: 1none and 1\% are invalid + if (idx == -1 || idx == css_units_none || idx == css_units_percentage) + return false; + + m_value = token.n.number; + m_units = (css_units)idx; + m_is_predefined = false; + return true; + } + else if (token.type == PERCENTAGE) { - m_is_predefined = true; - m_predef = predef; - } else + if (!(options & f_percentage)) return false; + + m_value = token.n.number; + m_units = css_units_percentage; + m_is_predefined = false; + return true; + } + else if (token.type == NUMBER) { + // if token is a nonzero number and neither f_number nor f_integer is specified in the options + if (!(options & (f_number | f_integer)) && token.n.number != 0) + return false; + // if token is a zero number and neither of f_number, f_integer or f_length are specified in the options + if (!(options & (f_number | f_integer | f_length)) && token.n.number == 0) + return false; + if ((options & f_integer) && token.n.number_type != css_number_integer) + return false; + + m_value = token.n.number; + m_units = css_units_none; m_is_predefined = false; - - string num; - string un; - bool is_unit = false; - for(char chr : str) - { - if(!is_unit) - { - if(t_isdigit(chr) || chr == '.' || chr == '+' || chr == '-') - { - num += chr; - } else - { - is_unit = true; - } - } - if(is_unit) - { - un += chr; - } - } - if(!num.empty()) - { - m_value = t_strtof(num); - m_units = (css_units) value_index(un, css_units_strings, css_units_none); - } else - { - // not a number so it is predefined - m_is_predefined = true; - m_predef = defValue; - } + return true; } + return false; } -litehtml::css_length litehtml::css_length::from_string(const string& str, const string& predefs, int defValue) +string css_length::to_string() const { - css_length len; - len.fromString(str, predefs, defValue); - return len; -} - -litehtml::string litehtml::css_length::to_string() const -{ - if(m_is_predefined) - { - return "def(" + std::to_string(m_predef) + ")"; - } - return std::to_string(m_value) + "{" + index_value(m_units, css_units_strings) + "}"; + if(m_is_predefined) + { + return "def(" + std::to_string(m_predef) + ")"; + } + return std::to_string(m_value) + "{" + index_value(m_units, css_units_strings) + "}"; } -litehtml::css_length litehtml::css_length::predef_value(int val) +css_length css_length::predef_value(int val) { css_length len; len.predef(val); return len; } + +} // namespace litehtml
\ No newline at end of file diff --git a/libs/litehtml/src/css_properties.cpp b/libs/litehtml/src/css_properties.cpp index 4fe8a6a526..9100a998e0 100644 --- a/libs/litehtml/src/css_properties.cpp +++ b/libs/litehtml/src/css_properties.cpp @@ -113,39 +113,39 @@ void litehtml::css_properties::compute(const html_tag* el, const document::ptr& m_css_max_width = el->get_property<css_length>(_max_width_, false, none, offset(m_css_max_width)); m_css_max_height = el->get_property<css_length>(_max_height_, false, none, offset(m_css_max_height)); - doc->cvt_units(m_css_width, font_size); - doc->cvt_units(m_css_height, font_size); + doc->cvt_units(m_css_width, m_font_metrics, 0); + doc->cvt_units(m_css_height, m_font_metrics, 0); - doc->cvt_units(m_css_min_width, font_size); - doc->cvt_units(m_css_min_height, font_size); + doc->cvt_units(m_css_min_width, m_font_metrics, 0); + doc->cvt_units(m_css_min_height, m_font_metrics, 0); - doc->cvt_units(m_css_max_width, font_size); - doc->cvt_units(m_css_max_height, font_size); + doc->cvt_units(m_css_max_width, m_font_metrics, 0); + doc->cvt_units(m_css_max_height, m_font_metrics, 0); m_css_margins.left = el->get_property<css_length>(_margin_left_, false, 0, offset(m_css_margins.left)); m_css_margins.right = el->get_property<css_length>(_margin_right_, false, 0, offset(m_css_margins.right)); m_css_margins.top = el->get_property<css_length>(_margin_top_, false, 0, offset(m_css_margins.top)); m_css_margins.bottom = el->get_property<css_length>(_margin_bottom_, false, 0, offset(m_css_margins.bottom)); - doc->cvt_units(m_css_margins.left, font_size); - doc->cvt_units(m_css_margins.right, font_size); - doc->cvt_units(m_css_margins.top, font_size); - doc->cvt_units(m_css_margins.bottom, font_size); + doc->cvt_units(m_css_margins.left, m_font_metrics, 0); + doc->cvt_units(m_css_margins.right, m_font_metrics, 0); + doc->cvt_units(m_css_margins.top, m_font_metrics, 0); + doc->cvt_units(m_css_margins.bottom, m_font_metrics, 0); m_css_padding.left = el->get_property<css_length>(_padding_left_, false, 0, offset(m_css_padding.left)); m_css_padding.right = el->get_property<css_length>(_padding_right_, false, 0, offset(m_css_padding.right)); m_css_padding.top = el->get_property<css_length>(_padding_top_, false, 0, offset(m_css_padding.top)); m_css_padding.bottom = el->get_property<css_length>(_padding_bottom_, false, 0, offset(m_css_padding.bottom)); - doc->cvt_units(m_css_padding.left, font_size); - doc->cvt_units(m_css_padding.right, font_size); - doc->cvt_units(m_css_padding.top, font_size); - doc->cvt_units(m_css_padding.bottom, font_size); + doc->cvt_units(m_css_padding.left, m_font_metrics, 0); + doc->cvt_units(m_css_padding.right, m_font_metrics, 0); + doc->cvt_units(m_css_padding.top, m_font_metrics, 0); + doc->cvt_units(m_css_padding.bottom, m_font_metrics, 0); - m_css_borders.left.color = el->get_property<web_color>(_border_left_color_, false, m_color, offset(m_css_borders.left.color)); - m_css_borders.right.color = el->get_property<web_color>(_border_right_color_, false, m_color, offset(m_css_borders.right.color)); - m_css_borders.top.color = el->get_property<web_color>(_border_top_color_, false, m_color, offset(m_css_borders.top.color)); - m_css_borders.bottom.color = el->get_property<web_color>(_border_bottom_color_, false, m_color, offset(m_css_borders.bottom.color)); + m_css_borders.left.color = get_color_property(el, _border_left_color_, false, m_color, offset(m_css_borders.left.color)); + m_css_borders.right.color = get_color_property(el, _border_right_color_, false, m_color, offset(m_css_borders.right.color)); + m_css_borders.top.color = get_color_property(el, _border_top_color_, false, m_color, offset(m_css_borders.top.color)); + m_css_borders.bottom.color = get_color_property(el, _border_bottom_color_, false, m_color, offset(m_css_borders.bottom.color)); m_css_borders.left.style = (border_style) el->get_property<int>(_border_left_style_, false, border_style_none, offset(m_css_borders.left.style)); m_css_borders.right.style = (border_style) el->get_property<int>(_border_right_style_, false, border_style_none, offset(m_css_borders.right.style)); @@ -166,10 +166,10 @@ void litehtml::css_properties::compute(const html_tag* el, const document::ptr& if (m_css_borders.bottom.style == border_style_none || m_css_borders.bottom.style == border_style_hidden) m_css_borders.bottom.width = 0; - doc->cvt_units(m_css_borders.left.width, font_size); - doc->cvt_units(m_css_borders.right.width, font_size); - doc->cvt_units(m_css_borders.top.width, font_size); - doc->cvt_units(m_css_borders.bottom.width, font_size); + doc->cvt_units(m_css_borders.left.width, m_font_metrics, 0); + doc->cvt_units(m_css_borders.right.width, m_font_metrics, 0); + doc->cvt_units(m_css_borders.top.width, m_font_metrics, 0); + doc->cvt_units(m_css_borders.bottom.width, m_font_metrics, 0); m_css_borders.radius.top_left_x = el->get_property<css_length>(_border_top_left_radius_x_, false, 0, offset(m_css_borders.radius.top_left_x)); m_css_borders.radius.top_left_y = el->get_property<css_length>(_border_top_left_radius_y_, false, 0, offset(m_css_borders.radius.top_left_y)); @@ -183,39 +183,39 @@ void litehtml::css_properties::compute(const html_tag* el, const document::ptr& m_css_borders.radius.bottom_right_x = el->get_property<css_length>(_border_bottom_right_radius_x_, false, 0, offset(m_css_borders.radius.bottom_right_x)); m_css_borders.radius.bottom_right_y = el->get_property<css_length>(_border_bottom_right_radius_y_, false, 0, offset(m_css_borders.radius.bottom_right_y)); - doc->cvt_units( m_css_borders.radius.top_left_x, font_size); - doc->cvt_units( m_css_borders.radius.top_left_y, font_size); - doc->cvt_units( m_css_borders.radius.top_right_x, font_size); - doc->cvt_units( m_css_borders.radius.top_right_y, font_size); - doc->cvt_units( m_css_borders.radius.bottom_left_x, font_size); - doc->cvt_units( m_css_borders.radius.bottom_left_y, font_size); - doc->cvt_units( m_css_borders.radius.bottom_right_x, font_size); - doc->cvt_units( m_css_borders.radius.bottom_right_y, font_size); + doc->cvt_units( m_css_borders.radius.top_left_x, m_font_metrics, 0); + doc->cvt_units( m_css_borders.radius.top_left_y, m_font_metrics, 0); + doc->cvt_units( m_css_borders.radius.top_right_x, m_font_metrics, 0); + doc->cvt_units( m_css_borders.radius.top_right_y, m_font_metrics, 0); + doc->cvt_units( m_css_borders.radius.bottom_left_x, m_font_metrics, 0); + doc->cvt_units( m_css_borders.radius.bottom_left_y, m_font_metrics, 0); + doc->cvt_units( m_css_borders.radius.bottom_right_x, m_font_metrics, 0); + doc->cvt_units( m_css_borders.radius.bottom_right_y, m_font_metrics, 0); m_border_collapse = (border_collapse) el->get_property<int>(_border_collapse_, true, border_collapse_separate, offset(m_border_collapse)); m_css_border_spacing_x = el->get_property<css_length>(__litehtml_border_spacing_x_, true, 0, offset(m_css_border_spacing_x)); m_css_border_spacing_y = el->get_property<css_length>(__litehtml_border_spacing_y_, true, 0, offset(m_css_border_spacing_y)); - doc->cvt_units(m_css_border_spacing_x, font_size); - doc->cvt_units(m_css_border_spacing_y, font_size); + doc->cvt_units(m_css_border_spacing_x, m_font_metrics, 0); + doc->cvt_units(m_css_border_spacing_y, m_font_metrics, 0); m_css_offsets.left = el->get_property<css_length>(_left_, false, _auto, offset(m_css_offsets.left)); m_css_offsets.right = el->get_property<css_length>(_right_, false, _auto, offset(m_css_offsets.right)); m_css_offsets.top = el->get_property<css_length>(_top_, false, _auto, offset(m_css_offsets.top)); m_css_offsets.bottom = el->get_property<css_length>(_bottom_,false, _auto, offset(m_css_offsets.bottom)); - doc->cvt_units(m_css_offsets.left, font_size); - doc->cvt_units(m_css_offsets.right, font_size); - doc->cvt_units(m_css_offsets.top, font_size); - doc->cvt_units(m_css_offsets.bottom, font_size); + doc->cvt_units(m_css_offsets.left, m_font_metrics, 0); + doc->cvt_units(m_css_offsets.right, m_font_metrics, 0); + doc->cvt_units(m_css_offsets.top, m_font_metrics, 0); + doc->cvt_units(m_css_offsets.bottom, m_font_metrics, 0); m_z_index = el->get_property<css_length>(_z_index_, false, _auto, offset(m_z_index)); m_content = el->get_property<string>(_content_, false, "", offset(m_content)); m_cursor = el->get_property<string>(_cursor_, true, "auto", offset(m_cursor)); m_css_text_indent = el->get_property<css_length>(_text_indent_, true, 0, offset(m_css_text_indent)); - doc->cvt_units(m_css_text_indent, font_size); + doc->cvt_units(m_css_text_indent, m_font_metrics, 0); m_css_line_height = el->get_property<css_length>(_line_height_, true, normal, offset(m_css_line_height)); if(m_css_line_height.is_predefined()) @@ -226,7 +226,7 @@ void litehtml::css_properties::compute(const html_tag* el, const document::ptr& m_line_height = (int) std::nearbyint(m_css_line_height.val() * font_size); } else { - m_line_height = doc->to_pixels(m_css_line_height, font_size, font_size); + m_line_height = doc->to_pixels(m_css_line_height, m_font_metrics, m_font_metrics.font_size); m_css_line_height = (float) m_line_height; } @@ -246,6 +246,14 @@ void litehtml::css_properties::compute(const html_tag* el, const document::ptr& compute_flex(el, doc); } +// used for all color properties except `color` (color:currentcolor is converted to color:inherit during parsing) +litehtml::web_color litehtml::css_properties::get_color_property(const html_tag* el, string_id name, bool inherited, web_color default_value, uint_ptr member_offset) const +{ + web_color color = el->get_property<web_color>(name, inherited, default_value, member_offset); + if (color.is_current_color) color = m_color; + return color; +} + static const int font_size_table[8][7] = { { 9, 9, 9, 9, 11, 14, 18}, @@ -334,22 +342,24 @@ void litehtml::css_properties::compute_font(const html_tag* el, const document:: font_size = sz.calc_percent(parent_sz); } else { - font_size = doc->to_pixels(sz, parent_sz); + font_metrics fm; + fm.x_height = fm.font_size = parent_sz; + font_size = doc->to_pixels(sz, fm, 0); } } m_font_size = (float)font_size; // initialize font - m_font_family = el->get_property<string>(_font_family_, true, doc->container()->get_default_font_name(), offset(m_font_family)); - m_font_weight = (font_weight) el->get_property<int>( _font_weight_, true, font_weight_normal, offset(m_font_weight)); - m_font_style = (font_style) el->get_property<int>( _font_style_, true, font_style_normal, offset(m_font_style)); - m_text_decoration = el->get_property<string>(_text_decoration_, true, "none", offset(m_text_decoration)); + m_font_family = el->get_property<string>( _font_family_, true, doc->container()->get_default_font_name(), offset(m_font_family)); + m_font_weight = el->get_property<css_length>(_font_weight_, true, css_length::predef_value(font_weight_normal), offset(m_font_weight)); + m_font_style = (font_style) el->get_property<int>( _font_style_, true, font_style_normal, offset(m_font_style)); + m_text_decoration = el->get_property<string>( _text_decoration_, true, "none", offset(m_text_decoration)); m_font = doc->get_font( m_font_family.c_str(), font_size, - index_value(m_font_weight, font_weight_strings).c_str(), + m_font_weight.is_predefined() ? index_value(m_font_weight.predef(), font_weight_strings).c_str() : std::to_string(m_font_weight.val()).c_str(), index_value(m_font_style, font_style_strings).c_str(), m_text_decoration.c_str(), &m_font_metrics); @@ -357,21 +367,19 @@ void litehtml::css_properties::compute_font(const html_tag* el, const document:: void litehtml::css_properties::compute_background(const html_tag* el, const document::ptr& doc) { - int font_size = get_font_size(); - - m_bg.m_color = el->get_property<web_color>(_background_color_, false, web_color::transparent, offset(m_bg.m_color)); + m_bg.m_color = get_color_property(el, _background_color_, false, web_color::transparent, offset(m_bg.m_color)); const css_size auto_auto(css_length::predef_value(background_size_auto), css_length::predef_value(background_size_auto)); m_bg.m_position_x = el->get_property<length_vector>(_background_position_x_, false, { css_length(0, css_units_percentage) }, offset(m_bg.m_position_x)); m_bg.m_position_y = el->get_property<length_vector>(_background_position_y_, false, { css_length(0, css_units_percentage) }, offset(m_bg.m_position_y)); m_bg.m_size = el->get_property<size_vector> (_background_size_, false, { auto_auto }, offset(m_bg.m_size)); - for (auto& x : m_bg.m_position_x) doc->cvt_units(x, font_size); - for (auto& y : m_bg.m_position_y) doc->cvt_units(y, font_size); + for (auto& x : m_bg.m_position_x) doc->cvt_units(x, m_font_metrics, 0); + for (auto& y : m_bg.m_position_y) doc->cvt_units(y, m_font_metrics, 0); for (auto& size : m_bg.m_size) { - doc->cvt_units(size.width, font_size); - doc->cvt_units(size.height, font_size); + doc->cvt_units(size.width, m_font_metrics, 0); + doc->cvt_units(size.height, m_font_metrics, 0); } m_bg.m_attachment = el->get_property<int_vector>(_background_attachment_, false, { background_attachment_scroll }, offset(m_bg.m_attachment)); @@ -379,7 +387,7 @@ void litehtml::css_properties::compute_background(const html_tag* el, const docu m_bg.m_clip = el->get_property<int_vector>(_background_clip_, false, { background_box_border }, offset(m_bg.m_clip)); m_bg.m_origin = el->get_property<int_vector>(_background_origin_, false, { background_box_padding }, offset(m_bg.m_origin)); - m_bg.m_image = el->get_property<std::vector<background_image>>(_background_image_, false, {{}}, offset(m_bg.m_image)); + m_bg.m_image = el->get_property<vector<image>>(_background_image_, false, {{}}, offset(m_bg.m_image)); m_bg.m_baseurl = el->get_property<string>(_background_image_baseurl_, false, "", offset(m_bg.m_baseurl)); for (auto& image : m_bg.m_image) @@ -387,18 +395,19 @@ void litehtml::css_properties::compute_background(const html_tag* el, const docu switch (image.type) { - case background_image::bg_image_type_none: + case image::type_none: break; - case background_image::bg_image_type_url: + case image::type_url: if (!image.url.empty()) { doc->container()->load_image(image.url.c_str(), m_bg.m_baseurl.c_str(), true); } break; - case background_image::bg_image_type_gradient: - for(auto& item : image.gradient.m_colors) + case image::type_gradient: + for(auto& item : image.m_gradient.m_colors) { - doc->cvt_units(item.length, font_size); + if (item.length) + doc->cvt_units(*item.length, m_font_metrics, 0); } break; } @@ -413,7 +422,7 @@ void litehtml::css_properties::compute_flex(const html_tag* el, const document:: m_flex_wrap = (flex_wrap) el->get_property<int>(_flex_wrap_, false, flex_wrap_nowrap, offset(m_flex_wrap)); m_flex_justify_content = (flex_justify_content) el->get_property<int>(_justify_content_, false, flex_justify_content_flex_start, offset(m_flex_justify_content)); - m_flex_align_items = (flex_align_items) el->get_property<int>(_align_items_, false, flex_align_items_flex_normal, offset(m_flex_align_items)); + m_flex_align_items = (flex_align_items) el->get_property<int>(_align_items_, false, flex_align_items_normal, offset(m_flex_align_items)); m_flex_align_content = (flex_align_content) el->get_property<int>(_align_content_, false, flex_align_content_stretch, offset(m_flex_align_content)); } m_flex_align_self = (flex_align_items) el->get_property<int>(_align_self_, false, flex_align_items_auto, offset(m_flex_align_self)); @@ -428,7 +437,7 @@ void litehtml::css_properties::compute_flex(const html_tag* el, const document:: // flex-basis property must contain units m_flex_basis.predef(flex_basis_auto); } - doc->cvt_units(m_flex_basis, get_font_size()); + doc->cvt_units(m_flex_basis, m_font_metrics, 0); if(m_display == display_inline || m_display == display_inline_block) { m_display = display_block; @@ -446,34 +455,34 @@ std::vector<std::tuple<litehtml::string, litehtml::string>> litehtml::css_proper { std::vector<std::tuple<string, string>> ret; - ret.emplace_back(std::make_tuple("display", index_value(m_display, style_display_strings))); - ret.emplace_back(std::make_tuple("el_position", index_value(m_el_position, element_position_strings))); - ret.emplace_back(std::make_tuple("text_align", index_value(m_text_align, text_align_strings))); - ret.emplace_back(std::make_tuple("font_size", m_font_size.to_string())); - ret.emplace_back(std::make_tuple("overflow", index_value(m_overflow, overflow_strings))); - ret.emplace_back(std::make_tuple("white_space", index_value(m_white_space, white_space_strings))); - ret.emplace_back(std::make_tuple("visibility", index_value(m_visibility, visibility_strings))); - ret.emplace_back(std::make_tuple("box_sizing", index_value(m_box_sizing, box_sizing_strings))); - ret.emplace_back(std::make_tuple("z_index", m_z_index.to_string())); - ret.emplace_back(std::make_tuple("vertical_align", index_value(m_vertical_align, vertical_align_strings))); - ret.emplace_back(std::make_tuple("float", index_value(m_float, element_float_strings))); - ret.emplace_back(std::make_tuple("clear", index_value(m_clear, element_clear_strings))); - ret.emplace_back(std::make_tuple("margins", m_css_margins.to_string())); - ret.emplace_back(std::make_tuple("padding", m_css_padding.to_string())); - ret.emplace_back(std::make_tuple("borders", m_css_borders.to_string())); - ret.emplace_back(std::make_tuple("width", m_css_width.to_string())); - ret.emplace_back(std::make_tuple("height", m_css_height.to_string())); - ret.emplace_back(std::make_tuple("min_width", m_css_min_width.to_string())); - ret.emplace_back(std::make_tuple("min_height", m_css_min_width.to_string())); - ret.emplace_back(std::make_tuple("max_width", m_css_max_width.to_string())); - ret.emplace_back(std::make_tuple("max_height", m_css_max_width.to_string())); - ret.emplace_back(std::make_tuple("offsets", m_css_offsets.to_string())); - ret.emplace_back(std::make_tuple("text_indent", m_css_text_indent.to_string())); - ret.emplace_back(std::make_tuple("line_height", std::to_string(m_line_height))); - ret.emplace_back(std::make_tuple("list_style_type", index_value(m_list_style_type, list_style_type_strings))); - ret.emplace_back(std::make_tuple("list_style_position", index_value(m_list_style_position, list_style_position_strings))); - ret.emplace_back(std::make_tuple("border_spacing_x", m_css_border_spacing_x.to_string())); - ret.emplace_back(std::make_tuple("border_spacing_y", m_css_border_spacing_y.to_string())); + ret.emplace_back("display", index_value(m_display, style_display_strings)); + ret.emplace_back("el_position", index_value(m_el_position, element_position_strings)); + ret.emplace_back("text_align", index_value(m_text_align, text_align_strings)); + ret.emplace_back("font_size", m_font_size.to_string()); + ret.emplace_back("overflow", index_value(m_overflow, overflow_strings)); + ret.emplace_back("white_space", index_value(m_white_space, white_space_strings)); + ret.emplace_back("visibility", index_value(m_visibility, visibility_strings)); + ret.emplace_back("box_sizing", index_value(m_box_sizing, box_sizing_strings)); + ret.emplace_back("z_index", m_z_index.to_string()); + ret.emplace_back("vertical_align", index_value(m_vertical_align, vertical_align_strings)); + ret.emplace_back("float", index_value(m_float, element_float_strings)); + ret.emplace_back("clear", index_value(m_clear, element_clear_strings)); + ret.emplace_back("margins", m_css_margins.to_string()); + ret.emplace_back("padding", m_css_padding.to_string()); + ret.emplace_back("borders", m_css_borders.to_string()); + ret.emplace_back("width", m_css_width.to_string()); + ret.emplace_back("height", m_css_height.to_string()); + ret.emplace_back("min_width", m_css_min_width.to_string()); + ret.emplace_back("min_height", m_css_min_width.to_string()); + ret.emplace_back("max_width", m_css_max_width.to_string()); + ret.emplace_back("max_height", m_css_max_width.to_string()); + ret.emplace_back("offsets", m_css_offsets.to_string()); + ret.emplace_back("text_indent", m_css_text_indent.to_string()); + ret.emplace_back("line_height", std::to_string(m_line_height)); + ret.emplace_back("list_style_type", index_value(m_list_style_type, list_style_type_strings)); + ret.emplace_back("list_style_position", index_value(m_list_style_position, list_style_position_strings)); + ret.emplace_back("border_spacing_x", m_css_border_spacing_x.to_string()); + ret.emplace_back("border_spacing_y", m_css_border_spacing_y.to_string()); return ret; } diff --git a/libs/litehtml/src/css_selector.cpp b/libs/litehtml/src/css_selector.cpp index af46892397..43e5a5e33d 100644 --- a/libs/litehtml/src/css_selector.cpp +++ b/libs/litehtml/src/css_selector.cpp @@ -1,323 +1,714 @@ #include "html.h" #include "css_selector.h" -#include "document.h" +#include "css_parser.h" +#include "internal.h" -void litehtml::css_element_selector::parse_nth_child_params(const string& param, int& num, int& off) +namespace litehtml { - if (param == "odd") + +void css_selector::calc_specificity() +{ + if(m_right.m_tag != star_id) { - num = 2; - off = 1; + m_specificity.d = 1; } - else if (param == "even") + for(const auto& attr : m_right.m_attrs) { - num = 2; - off = 0; + if(attr.type == select_id) + { + m_specificity.b++; + } else + { + m_specificity.c++; + } } - else + if(m_left) { - string_vector tokens; - split_string(param, tokens, " n", "n"); + m_left->calc_specificity(); + m_specificity += m_left->m_specificity; + } +} - string s_num; - string s_off; +void css_selector::add_media_to_doc( document* doc ) const +{ + if(m_media_query && doc) + { + doc->add_media_list(m_media_query); + } +} - string s_int; - for (const auto& token : tokens) - { - if (token == "n") - { - s_num = s_int; - s_int.clear(); - } - else - { - s_int += token; - } - } - s_off = s_int; +// https://www.w3.org/TR/selectors-4/#type-nmsp +// <ns-prefix> = [ <ident-token> | '*' ]? '|' https://www.w3.org/TR/selectors-4/#typedef-ns-prefix +string parse_ns_prefix(const css_token_vector& tokens, int& index) +{ + const auto& a = at(tokens, index); + const auto& b = at(tokens, index + 1); - num = atoi(s_num.c_str()); - off = atoi(s_off.c_str()); + if (a.ch == '|') + { + index++; + return ""; } + + if ((a.type == IDENT || a.ch == '*') && b.ch == '|') + { + index += 2; + return a.type == IDENT ? a.name : "*"; + } + + return ""; } -void litehtml::css_element_selector::parse( const string& txt ) +struct wq_name { - string::size_type el_end = txt.find_first_of(".#[:"); - string tag = txt.substr(0, el_end); - litehtml::lcase(tag); - if (tag == "") tag = "*"; - m_tag = _id(tag); + string prefix; + string name; +}; - m_attrs.clear(); - while(el_end != string::npos) +// <wq-name> = <ns-prefix>? <ident-token> +// Whitespace is forbidden between any of the components of a <wq-name>. +wq_name parse_wq_name(const css_token_vector& tokens, int& index) +{ + int start = index; + string prefix = parse_ns_prefix(tokens, index); + + auto tok = at(tokens, index); + if (tok.type == IDENT) { - if(txt[el_end] == '.') - { - css_attribute_selector attribute; - - attribute.type = select_class; - string::size_type pos = txt.find_first_of(".#[:", el_end + 1); - string name = txt.substr(el_end + 1, pos - el_end - 1); - litehtml::lcase(name); - attribute.name = _id(name); - m_attrs.push_back(attribute); - el_end = pos; - } else if(txt[el_end] == '#') - { - css_attribute_selector attribute; - - attribute.type = select_id; - string::size_type pos = txt.find_first_of(".#[:", el_end + 1); - string name = txt.substr(el_end + 1, pos - el_end - 1); - litehtml::lcase(name); - attribute.name = _id(name); - m_attrs.push_back(attribute); - el_end = pos; - } else if(txt[el_end] == ':') - { - css_attribute_selector attribute; - - if(txt[el_end + 1] == ':') - { - attribute.type = select_pseudo_element; - string::size_type pos = txt.find_first_of(".#[:", el_end + 2); - string name = txt.substr(el_end + 2, pos - el_end - 2); - litehtml::lcase(name); - attribute.name = _id(name); - m_attrs.push_back(attribute); - el_end = pos; - } else - { - string::size_type pos = txt.find_first_of(".#[:(", el_end + 1); - string name = txt.substr(el_end + 1, pos - el_end - 1); - lcase(name); - attribute.name = _id(name); - if(attribute.name == _after_ || attribute.name == _before_) - { - attribute.type = select_pseudo_element; - } else - { - attribute.type = select_pseudo_class; - } - - string val; - if(pos != string::npos && txt.at(pos) == '(') - { - auto end = find_close_bracket(txt, pos); - val = txt.substr(pos + 1, end - pos - 1); - if (end != string::npos) pos = end + 1; - } - - switch (attribute.name) - { - case _nth_child_: - case _nth_of_type_: - case _nth_last_child_: - case _nth_last_of_type_: - lcase(val); - parse_nth_child_params(val, attribute.a, attribute.b); - break; - case _not_: - attribute.sel = std::make_shared<css_element_selector>(); - attribute.sel->parse(val); - break; - case _lang_: - trim(val); - lcase(val); - attribute.val = val; - break; - default: - break; - } - - m_attrs.push_back(attribute); - el_end = pos; - } - } else if(txt[el_end] == '[') - { - css_attribute_selector attribute; - - string::size_type pos = txt.find_first_of("]~=|$*^", el_end + 1); - string attr = txt.substr(el_end + 1, pos - el_end - 1); - trim(attr); - litehtml::lcase(attr); - if(pos != string::npos) - { - if(txt[pos] == ']') - { - attribute.type = select_exists; - } else if(txt[pos] == '=') - { - attribute.type = select_equal; - pos++; - } else if(txt.substr(pos, 2) == "~=") - { - attribute.type = select_contain_str; - pos += 2; - } else if(txt.substr(pos, 2) == "|=") - { - attribute.type = select_start_str; - pos += 2; - } else if(txt.substr(pos, 2) == "^=") - { - attribute.type = select_start_str; - pos += 2; - } else if(txt.substr(pos, 2) == "$=") - { - attribute.type = select_end_str; - pos += 2; - } else if(txt.substr(pos, 2) == "*=") - { - attribute.type = select_contain_str; - pos += 2; - } else - { - attribute.type = select_exists; - pos += 1; - } - pos = txt.find_first_not_of(" \t", pos); - if(pos != string::npos) - { - if(txt[pos] == '"') - { - string::size_type pos2 = txt.find_first_of('\"', pos + 1); - attribute.val = txt.substr(pos + 1, pos2 == string::npos ? pos2 : (pos2 - pos - 1)); - pos = pos2 == string::npos ? pos2 : (pos2 + 1); - } else if(txt[pos] == '\'') - { - string::size_type pos2 = txt.find_first_of('\'', pos + 1); - attribute.val = txt.substr(pos + 1, pos2 == string::npos ? pos2 : (pos2 - pos - 1)); - pos = pos2 == string::npos ? pos2 : (pos2 + 1); - } else if(txt[pos] == ']') - { - pos ++; - } else - { - string::size_type pos2 = txt.find_first_of(']', pos + 1); - attribute.val = txt.substr(pos, pos2 == string::npos ? pos2 : (pos2 - pos)); - trim(attribute.val); - pos = pos2 == string::npos ? pos2 : (pos2 + 1); - } - } - } else - { - attribute.type = select_exists; - } - attribute.name = _id(attr); - m_attrs.push_back(attribute); - el_end = pos; - } else - { - el_end++; - } - el_end = txt.find_first_of(".#[:", el_end); + index++; + return { prefix, tok.name }; + } + // restore index to before <ns-prefix> if failed to parse <ident-token> + index = start; + + // handle situation when a name is erroneously parsed as prefix, eg. [x|=a] + tok = at(tokens, index); + if (tok.type == IDENT) + { + index++; + return { "", tok.name }; } + + return {}; } +// https://www.w3.org/TR/selectors-4/#typedef-type-selector +// <type-selector> = <wq-name> | <ns-prefix>? '*' +// <wq-name> = <ns-prefix>? <ident-token> +// So: +// <type-selector> = <ns-prefix>? [ <ident-token> | '*' ] +wq_name parse_type_selector(const css_token_vector& tokens, int& index) +{ + int start = index; + string prefix = parse_ns_prefix(tokens, index); + + const auto& tok = at(tokens, index); + if (tok.type == IDENT || tok.ch == '*') + { + index++; + string name = tok.type == IDENT ? tok.name : "*"; + // type selector is always ASCII-case-insensitive for HTML documents, regardless of document mode (quirks/no quirks) + return { lowcase(prefix), lowcase(name) }; + } + // restore index to before <ns-prefix> if failed to parse <ident-token> or '*' + index = start; + return {}; +} -bool litehtml::css_selector::parse( const string& text ) +// <attr-matcher> = [ '~' | '|' | '^' | '$' | '*' ]? '=' +bool parse_attr_matcher(const css_token_vector& tokens, int& index, attr_matcher& matcher) { - if(text.empty()) + const auto& a = at(tokens, index); + const auto& b = at(tokens, index + 1); + + if (a.ch == '=') { + index++; + matcher = attribute_equals; + return true; + } + + if (!(is_one_of(a.ch, '~', '|', '^', '$', '*') && b.ch == '=')) return false; + + index += 2; + matcher = (attr_matcher)a.ch; + return true; +} + +// https://www.w3.org/TR/selectors-4/#typedef-attribute-selector +// <attribute-selector> = '[' <wq-name> ']' | +// '[' <wq-name> <attr-matcher> [ <string-token> | <ident-token> ] <attr-modifier>? ']' +// <attr-matcher> = [ '~' | '|' | '^' | '$' | '*' ]? '=' +// <attr-modifier> = i | s +css_attribute_selector parse_attribute_selector(const css_token& block) +{ + css_attribute_selector selector; + + const css_token_vector& tokens = block.value; + int index = 0; + + // <wq-name> + skip_whitespace(tokens, index); + wq_name wq_name = parse_wq_name(tokens, index); + if (wq_name.name == "") return {}; + + // attribute name in attribute selector is ASCII case-insensitive for HTML documents, regardless of document mode (quirks/no quirks) + auto prefix = lowcase(wq_name.prefix); + auto name = lowcase(wq_name.name); + + skip_whitespace(tokens, index); + if (index == (int)tokens.size()) // [name] + { + selector.type = select_attr; + selector.prefix = _id(prefix); + selector.name = _id(name); + selector.matcher = attribute_exists; + return selector; } - string_vector tokens; - split_string(text, tokens, "", " \t>+~", "(["); - if(tokens.empty()) + // <attr-matcher> + skip_whitespace(tokens, index); + attr_matcher matcher; + if (!parse_attr_matcher(tokens, index, matcher)) + return {}; + + // <string-token> | <ident-token> + skip_whitespace(tokens, index); + const css_token& value = at(tokens, index); + if (value.type != STRING && value.type != IDENT) + return {}; + index++; + + // <attr-modifier>? + skip_whitespace(tokens, index); + char modifier = 0; + const css_token& tok = at(tokens, index); + if (tok.type == IDENT) { + if (tok.ident() == "s") modifier = 's'; + else if (tok.ident() == "i") modifier = 'i'; + else return {}; // junk at the end of attribute selector + index++; + } + + skip_whitespace(tokens, index); + if (index != (int)tokens.size()) + return {}; // junk at the end of attribute selector + + // https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors + // Attribute selectors on an HTML element in an HTML document must treat the values + // of attributes with the following names as ASCII case-insensitive (unless s modifier is specified): + static string_vector special_attributes = { + "accept", + "accept-charset", + "align", + "alink", + "axis", + "bgcolor", + "charset", + "checked", + "clear", + "codetype", + "color", + "compact", + "declare", + "defer", + "dir", + "direction", + "disabled", + "enctype", + "face", + "frame", + "hreflang", + "http-equiv", + "lang", + "language", + "link", + "media", + "method", + "multiple", + "nohref", + "noresize", + "noshade", + "nowrap", + "readonly", + "rel", + "rev", + "rules", + "scope", + "scrolling", + "selected", + "shape", + "target", + "text", + "type", + "valign", + "valuetype", + "vlink", + }; + + selector.type = select_attr; + selector.prefix = _id(prefix); + selector.name = _id(name); + selector.matcher = matcher; + selector.caseless_match = modifier == 'i' || (!modifier && name in special_attributes); + selector.value = selector.caseless_match ? lowcase(value.str) : value.str; + return selector; +} + +struct an_b +{ + int a, b; + bool valid; + an_b() : a(), b(), valid(false) {} + an_b(int a, int b) : a(a), b(b), valid(true) {} +}; + +// NOTE: "+ 5" is not valid, and strtol correctly fails to parse it +bool to_int(string s, int& number) +{ + if (s == "") return false; + + const char* ptr = s.c_str(); + char* end; + int n = strtol(ptr, &end, 10); + + if (end != ptr + s.size()) return false; + + number = n; + return true; +} + +// https://www.w3.org/TR/css-syntax-3/#anb-syntax +// I don't use the formal grammar because it creates a lot of unnecessary complexity. +// Deviations from the standard: +// * escapes are not allowed +// * comments are allowed inside numbers and identifiers: ev/**/en +an_b parse_an_b(string s) +{ + lcase(trim(s)); + if (s == "even") return {2, 0}; + if (s == "odd") return {2, 1}; + + int a, b; + + int i = (int)s.find('n'); + if (i == -1) + { + if (!to_int(s, b)) return {}; + return {0, b}; } - string left; - string right = tokens.back(); - char combinator = 0; + auto str_a = s.substr(0, i); + auto str_b = s.substr(i + 1); - tokens.pop_back(); - while(!tokens.empty() && (tokens.back() == " " || tokens.back() == "\t" || tokens.back() == "+" || tokens.back() == "~" || tokens.back() == ">")) + if (is_one_of(str_a, "", "+", "-")) + a = str_a == "-" ? -1 : 1; + else { - if(combinator == ' ' || combinator == 0) - { - combinator = tokens.back()[0]; - } - tokens.pop_back(); + if (!to_int(str_a, a)) return {}; } - for(const auto & token : tokens) + trim(str_b); // spaces after n are allowed: 2n + 3 + if (str_b != "") { - left += token; + if (str_b[0] == '+' || str_b[0] == '-') + while (is_whitespace(str_b[1])) str_b.erase(1, 1); // spaces after sign are allowed + + if (!to_int(str_b, b)) return {}; } + else + b = 0; - trim(left); - trim(right); + return {a, b}; +} - if(right.empty()) +int find_of_keyword(const css_token_vector& tokens) +{ + for (int i = 0; i < (int)tokens.size(); i++) { - return false; + if (tokens[i].ident() == "of") + return i; } + return -1; +} + +// :nth-child(An+B [of S]?) https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo +// :nth-last-child(An+B [of S]?) +// where S is a forgiving <complex-selector-list> +// +// :nth-of-type(An+B) https://www.w3.org/TR/selectors-4/#the-nth-of-type-pseudo +// :nth-last-of-type(An+B) +// +css_attribute_selector parse_nth_child(const css_token& token, bool of_keyword, document_mode mode) +{ + css_attribute_selector selector(select_pseudo_class, lowcase(token.name)); - m_right.parse(right); + const auto& tokens = token.value; - switch(combinator) + // find "of" keyword + int i = of_keyword ? find_of_keyword(tokens) : -1; + if (i >= 0) { - case '>': - m_combinator = combinator_child; - break; - case '+': - m_combinator = combinator_adjacent_sibling; - break; - case '~': - m_combinator = combinator_general_sibling; - break; - default: - m_combinator = combinator_descendant; - break; + const auto& selector_tokens = slice(tokens, i + 1); + + // The standard doesn't specify if pseudo-elements are allowed in this selector list. + // But specifying them will make selector match nothing anyway because + // "The structural pseudo-classes only apply to elements in the document tree; + // they must never match pseudo-elements." https://www.w3.org/TR/selectors-4/#structural-pseudos + // So I parse as if they were not allowed. + selector.selector_list = parse_selector_list(selector_tokens, forgiving_mode + forbid_pseudo_elements, mode); + // NOTE: selector_list may be empty, this does not invalidate the selector. + + // Chrome/Firefox behavior differs from the standard: they treat S as unforgiving and allow pseudo-elements. + // NOTE: :is(), which also accepts <forgiving-selector-list>, is handled correctly by Chrome/Firefox. + // Use this code instead of above to match Chrome/Firefox behavior: + //selector.selector_list = parse_selector_list(selector_tokens, strict_mode); + //if (selector.selector_list.empty()) return {}; } - m_left = nullptr; + // get string representation of everything between "nth-child(" and "of" or ")", except for comments + string str = get_repr(tokens, 0, i); // Note: i == -1 works as expected - if(!left.empty()) + an_b x = parse_an_b(str); + if (!x.valid) return {}; + + selector.a = x.a; + selector.b = x.b; + + return selector; +} + +css_attribute_selector parse_function_pseudo_class(const css_token& token, document_mode mode) +{ + string name = lowcase(token.name); + if (name == "nth-child" || name == "nth-last-child") { - m_left = std::make_shared<css_selector>(); - if(!m_left->parse(left)) - { - return false; - } + return parse_nth_child(token, true, mode); + } + else if (name == "nth-of-type" || name == "nth-last-of-type") + { + return parse_nth_child(token, false, mode); } + else if (name == "is") // https://www.w3.org/TR/selectors-4/#matches + { + css_attribute_selector selector(select_pseudo_class, name); + // "taking a <forgiving-selector-list> as its sole argument" + // "Pseudo-elements... are not valid within :is()." + selector.selector_list = parse_selector_list(token.value, forgiving_mode + forbid_pseudo_elements, mode); + return selector; + } + else if (name == "not") // https://www.w3.org/TR/selectors-4/#negation + { + css_attribute_selector selector(select_pseudo_class, name); + // "taking a selector list as an argument" + // "Pseudo-elements... are not valid within :not()." + selector.selector_list = parse_selector_list(token.value, strict_mode + forbid_pseudo_elements, mode); + if (selector.selector_list.empty()) return {}; + return selector; + } + else if (name == "lang") // https://www.w3.org/TR/selectors-4/#the-lang-pseudo + { + css_attribute_selector selector(select_pseudo_class, name); + selector.value = get_repr(token.value); + return selector; + } + return {}; +} - return true; +// simple = non-functional (without parentheses) +bool is_supported_simple_pseudo_class(const string& name) +{ + static std::set<string> supported_simple_pseudo_classes = + { + // Location Pseudo-classes https://www.w3.org/TR/selectors-4/#location + "any-link", "link", "visited", "local-link", "target", "target-within", "scope", + // User Action Pseudo-classes https://www.w3.org/TR/selectors-4/#useraction-pseudos + "hover", "active", "focus", "focus-visible", "focus-within", + // Tree-Structural pseudo-classes https://www.w3.org/TR/selectors-4/#structural-pseudos + "root", "empty", "first-child", "last-child", "only-child", "first-of-type", "last-of-type", "only-of-type", + }; + return supported_simple_pseudo_classes.count(lowcase(name)) == 1; } -void litehtml::css_selector::calc_specificity() +// https://www.w3.org/TR/selectors-4/#typedef-pseudo-class-selector +// <pseudo-class-selector> = ':' <ident-token> | +// ':' <function-token> <any-value> ')' +// where <ident-token> is not before, after, first-line or first-letter +css_attribute_selector parse_pseudo_class(const css_token_vector& tokens, int& index, document_mode mode) { - if(m_right.m_tag != star_id) + const auto& a = at(tokens, index); + const auto& b = at(tokens, index + 1); + + if (a.ch != ':') + return {}; + + if (b.type == IDENT) { - m_specificity.d = 1; + // unsupported pseudo-classes must be treated as invalid: https://www.w3.org/TR/selectors-4/#w3c-partial + if (!is_supported_simple_pseudo_class(b.ident())) + return {}; + + index += 2; + return { select_pseudo_class, b.ident() }; } - for(const auto& attr : m_right.m_attrs) + if (b.type == CV_FUNCTION) { - if(attr.type == select_id) + css_attribute_selector sel = parse_function_pseudo_class(b, mode); + if (sel) index += 2; + return sel; + } + return {}; +} + +// https://www.w3.org/TR/selectors-4/#typedef-subclass-selector +// <subclass-selector> = <id-selector> | <class-selector> | <attribute-selector> | <pseudo-class-selector> +// <id-selector> = <hash-token> with hash_type == ID +// <class-selector> = '.' <ident-token> +css_attribute_selector parse_subclass_selector(const css_token_vector& tokens, int& index, document_mode mode) +{ + css_attribute_selector selector; + + const auto& tok0 = at(tokens, index); + const auto& tok1 = at(tokens, index + 1); + + switch (tok0.type) + { + case HASH: + if (tok0.hash_type == css_hash_id) { - m_specificity.b++; - } else + index++; + selector.type = select_id; + string name = tok0.name; + // ids are matched ASCII case-insensitively in quirks mode + if (mode == quirks_mode) lcase(name); + selector.name = _id(name); + return selector; + } + return {}; + + case '.': + if (tok1.type == IDENT) { - m_specificity.c++; - } + index += 2; + selector.type = select_class; + string name = tok1.name; + // class names are matched ASCII case-insensitively in quirks mode + if (mode == quirks_mode) lcase(name); + selector.name = _id(name); + return selector; + } + return {}; + + case SQUARE_BLOCK: + selector = parse_attribute_selector(tok0); + if (selector) index++; + return selector; + + default: + return parse_pseudo_class(tokens, index, mode); } - if(m_left) +} + +// simple = non-functional (without parentheses) +bool is_supported_simple_pseudo_element(const string& name) +{ + return is_one_of(lowcase(name), + // Typographic Pseudo-elements https://www.w3.org/TR/css-pseudo-4/#typographic-pseudos + //"first-line", "first-letter", + // Highlight Pseudo-elements https://www.w3.org/TR/css-pseudo-4/#highlight-pseudos + //"selection", + // Tree-Abiding Pseudo-elements https://www.w3.org/TR/css-pseudo-4/#treelike + "before", "after" //"marker", "placeholder", + ); +} + +css_attribute_selector parse_pseudo_element(const css_token_vector& tokens, int& index) +{ + const auto& a = at(tokens, index); + const auto& b = at(tokens, index + 1); + const auto& c = at(tokens, index + 2); + + if (a.ch != ':') + return {}; + if (b.ch != ':' && b.type != IDENT) + return {}; + + if (b.type == IDENT) // legacy syntax with one ':' https://www.w3.org/TR/selectors-4/#single-colon-pseudos { - m_left->calc_specificity(); - m_specificity += m_left->m_specificity; + if (!is_one_of(b.ident(), "before", "after")) // first-line/letter are not supported + return {}; + + index += 2; + return {select_pseudo_element, b.ident()}; } + + if (c.type == IDENT) // normal syntax with '::' + { + if (!is_supported_simple_pseudo_element(c.ident())) + return {}; + + index += 3; + return {select_pseudo_element, c.ident()}; + } + + return {}; } -void litehtml::css_selector::add_media_to_doc( document* doc ) const +// https://www.w3.org/TR/selectors-4/#typedef-compound-selector +// <compound-selector> = [ <type-selector>? <subclass-selector>* +// [ <pseudo-element-selector> <pseudo-class-selector>* ]* ]! +// NOTE: This grammar allows pseudo-classes to go before #id and .class and [attr]. +// Whitespace is forbidden: +// * Between any of the top-level components of a <compound-selector> +css_element_selector::ptr parse_compound_selector(const css_token_vector& tokens, int& index, document_mode mode) { - if(m_media_query && doc) + auto selector = make_shared<css_element_selector>(); + + // <type-selector>? + wq_name wq_name = parse_type_selector(tokens, index); + selector->m_prefix = _id(wq_name.prefix); + selector->m_tag = _id(wq_name.name); + + // <subclass-selector>* + while (css_attribute_selector sel = parse_subclass_selector(tokens, index, mode)) + selector->m_attrs.push_back(sel); + + // [ <pseudo-element-selector> <pseudo-class-selector>* ]* + while (true) { - doc->add_media_list(m_media_query); + auto sel = parse_pseudo_element(tokens, index); + if (!sel) break; + selector->m_attrs.push_back(sel); + + while ((sel = parse_pseudo_class(tokens, index, mode))) + selector->m_attrs.push_back(sel); } + + // [..]! "must produce at least one value" https://www.w3.org/TR/css-values-4/#mult-req + if (selector->m_tag == empty_id && selector->m_attrs.empty()) + return nullptr; + + if (selector->m_tag == empty_id) + selector->m_tag = star_id; + + return selector; +} + +// <combinator> = '>' | '+' | '~' | [ '|' '|' ] +// <whitespace> combinator is also handled here +// parse_combinator consumes all leading and trailing whitespace +// column combinator || is at-risk https://www.w3.org/TR/selectors-4/ and not implemented in Chrome/Firefox https://caniuse.com/mdn-css_selectors_column +int parse_combinator(const css_token_vector& tokens, int& index) +{ + bool ws = skip_whitespace(tokens, index); + + const css_token& tok = at(tokens, index); + if (is_one_of(tok.ch, '>', '+', '~')) +// if (tok.ch in ${'>', '+', '~'}) + { + index++; + skip_whitespace(tokens, index); + return tok.ch; + } + + return ws ? ' ' : 0; +} + +css_selector::ptr parse_complex_selector(const css_token_vector& tokens, document_mode mode) +{ + int index = 0; + skip_whitespace(tokens, index); + auto sel = parse_compound_selector(tokens, index, mode); + if (!sel) return nullptr; + + auto selector = make_shared<css_selector>(); + selector->m_right = *sel; + + // NOTE: all the whitespace is handled by parse_combinator, that's why skip_whitespace is never called in the loop + // NOTE: parse_complex_selector is different from most other parse_xxx functions in that it's required + // to parse all input tokens, it doesn't just parse as much as possible. + while (true) + { + int combinator = parse_combinator(tokens, index); + if (index == (int)tokens.size()) + // combinator == 0 means index already was at the end before the call to parse_combinator + return !combinator || combinator == ' ' ? selector : nullptr; + if (!combinator) // not the end and combinator failed to parse + return nullptr; + + // otherwise: index is not at the end, combinator is good and tokens[index] is not whitespace + // it means if parse_compound_selector fails it's an error + sel = parse_compound_selector(tokens, index, mode); + if (!sel) + return nullptr; + + auto new_selector = make_shared<css_selector>(); + new_selector->m_left = selector; + new_selector->m_right = *sel; + new_selector->m_combinator = (css_combinator)combinator; + selector = new_selector; + } +} + +// Return true if `selector` has (in any of its css_element_selector's) a css_attribute_selector +// of type `type` and name `name`. name == "" matches any name. +bool has_selector(const css_selector& selector, attr_select_type type, const string& name = "") +{ + for (const auto& sel : selector.m_right.m_attrs) + { + if (sel.type == type && (name == "" || equal_i(_s(sel.name), name))) + return true; + } + + if (selector.m_left) + return has_selector(*selector.m_left, type, name); + + return false; +} + +// https://www.w3.org/TR/css-syntax-3/#parse-comma-list +// https://www.w3.org/TR/selectors-4/#selector-list +// https://www.w3.org/TR/selectors-4/#forgiving-selector +// Parse comma-separated list of complex selectors. +css_selector::vector parse_selector_list(const css_token_vector& tokens, int options, document_mode mode) +{ + // NOTE: this is unnecessary: "If input contains only <whitespace-token>s, return an empty list." + + vector<css_token_vector> list_of_lists = parse_comma_separated_list(tokens); + css_selector::vector result; + + for (const auto& list: list_of_lists) + { + css_selector::ptr selector = parse_complex_selector(list, mode); + + // if selector is failed to parse or not allowed by the options + if (!selector || + ((options & forbid_pseudo_elements) && has_selector(*selector, select_pseudo_element))) + { + // in forgiving mode, ignore the bad selector + if (options & forgiving_mode) + continue; + + // in strict mode, entire selector-list fails to parse because of one bad selector + return {}; + } + + result.push_back(selector); + } + + return result; +} + +bool css_selector::parse(const string& text, document_mode mode) +{ + auto tokens = normalize(text, f_componentize); + auto ptr = parse_complex_selector(tokens, mode); + if (!ptr) return false; + *this = *ptr; + return true; } +} // namespace litehtml diff --git a/libs/litehtml/src/document.cpp b/libs/litehtml/src/document.cpp index a197c66fc2..471f9204c7 100644 --- a/libs/litehtml/src/document.cpp +++ b/libs/litehtml/src/document.cpp @@ -21,8 +21,6 @@ #include "el_div.h" #include "el_font.h" #include "el_tr.h" -#include <cmath> -#include <cstdio> #include "gumbo.h" #include "render_item.h" #include "render_table.h" @@ -60,6 +58,14 @@ document::ptr document::createFromString( // Parse document into GumboOutput GumboOutput* output = doc->parse_html(str); + // mode must be set before doc->create_node because it is used in html_tag::set_attr + switch (output->document->v.document.doc_type_quirks_mode) + { + case GUMBO_DOCTYPE_NO_QUIRKS: doc->m_mode = no_quirks_mode; break; + case GUMBO_DOCTYPE_QUIRKS: doc->m_mode = quirks_mode; break; + case GUMBO_DOCTYPE_LIMITED_QUIRKS: doc->m_mode = limited_quirks_mode; break; + } + // Create litehtml::elements. elements_list root_elements; doc->create_node(output->root, root_elements, true); @@ -67,17 +73,18 @@ document::ptr document::createFromString( { doc->m_root = root_elements.back(); } + // Destroy GumboOutput gumbo_destroy_output(&kGumboDefaultOptions, output); if (master_styles != "") { - doc->m_master_css.parse_stylesheet(master_styles.c_str(), nullptr, doc, nullptr); + doc->m_master_css.parse_css_stylesheet(master_styles, "", doc); doc->m_master_css.sort_selectors(); } if (user_styles != "") { - doc->m_user_css.parse_stylesheet(user_styles.c_str(), nullptr, doc, nullptr); + doc->m_user_css.parse_css_stylesheet(user_styles, "", doc); doc->m_user_css.sort_selectors(); } @@ -95,27 +102,22 @@ document::ptr document::createFromString( doc->m_root->parse_attributes(); // parse style sheets linked in document - media_query_list::ptr media; for (const auto& css : doc->m_css) { - if (!css.media.empty()) - { - media = media_query_list::create_from_string(css.media, doc); - } - else + media_query_list_list::ptr media; + if (css.media != "") { - media = nullptr; + auto mq_list = parse_media_query_list(css.media, doc); + media = make_shared<media_query_list_list>(); + media->add(mq_list); } - doc->m_styles.parse_stylesheet(css.text.c_str(), css.baseurl.c_str(), doc, media); + doc->m_styles.parse_css_stylesheet(css.text, css.baseurl, doc, media); } // Sort css selectors using CSS rules. doc->m_styles.sort_selectors(); - // get current media features - if (!doc->m_media_lists.empty()) - { - doc->update_media_lists(doc->m_media); - } + // Apply media features. + doc->update_media_lists(doc->m_media); // Apply parsed styles. doc->m_root->apply_stylesheet(doc->m_styles); @@ -123,7 +125,7 @@ document::ptr document::createFromString( // Apply user styles if any doc->m_root->apply_stylesheet(doc->m_user_css); - // Initialize m_css + // Initialize element::m_css doc->m_root->compute_styles(); // Create rendering tree @@ -136,7 +138,10 @@ document::ptr document::createFromString( // Finally initialize elements // init() returns pointer to the render_init element because it can change its type - doc->m_root_render = doc->m_root_render->init(); + if(doc->m_root_render) + { + doc->m_root_render = doc->m_root_render->init(); + } } return doc; @@ -165,7 +170,7 @@ encoding adjust_meta_encoding(encoding meta_encoding, encoding current_encoding) encoding get_meta_encoding(GumboNode* root) { // find <head> - GumboNode* head = 0; + GumboNode* head = nullptr; for (size_t i = 0; i < root->v.element.children.length; i++) { GumboNode* node = (GumboNode*)root->v.element.children.data[i]; @@ -478,33 +483,6 @@ uint_ptr document::add_font( const char* name, int size, const char* weight, con case font_weight_normal: fw = 400; break; - case font_weight_100: - fw = 100; - break; - case font_weight_200: - fw = 200; - break; - case font_weight_300: - fw = 300; - break; - case font_weight_400: - fw = 400; - break; - case font_weight_500: - fw = 500; - break; - case font_weight_600: - fw = 600; - break; - case font_weight_700: - fw = 700; - break; - case font_weight_800: - fw = 800; - break; - case font_weight_900: - fw = 900; - break; } } else { @@ -589,7 +567,7 @@ uint_ptr document::get_font( const char* name, int size, const char* weight, con int document::render( int max_width, render_type rt ) { int ret = 0; - if(m_root) + if(m_root && m_root_render) { position client_rc; m_container->get_client_rect(client_rc); @@ -630,20 +608,7 @@ void document::draw( uint_ptr hdc, int x, int y, const position* clip ) } } -int document::to_pixels( const char* str, int fontSize, bool* is_percent/*= 0*/ ) const -{ - if(!str) return 0; - - css_length val; - val.fromString(str); - if(is_percent && val.units() == css_units_percentage && !val.is_predefined()) - { - *is_percent = true; - } - return to_pixels(val, fontSize); -} - -int document::to_pixels( const css_length& val, int fontSize, int size ) const +int document::to_pixels( const css_length& val, const font_metrics& metrics, int size ) const { if(val.is_predefined()) { @@ -656,20 +621,26 @@ int document::to_pixels( const css_length& val, int fontSize, int size ) const ret = val.calc_percent(size); break; case css_units_em: - ret = round_f(val.val() * (float) fontSize); + ret = round_f(val.val() * (float) metrics.font_size); break; + + // https://drafts.csswg.org/css-values-4/#absolute-lengths case css_units_pt: - ret = m_container->pt_to_px((int) val.val()); + ret = m_container->pt_to_px(round_f(val.val())); break; case css_units_in: - ret = m_container->pt_to_px((int) (val.val() * 72)); + ret = m_container->pt_to_px(round_f(val.val() * 72)); // 1in = 72pt + break; + case css_units_pc: + ret = m_container->pt_to_px(round_f(val.val() * 12)); // 1pc = (1/6)in = 12pt break; case css_units_cm: - ret = m_container->pt_to_px((int) (val.val() * 0.3937 * 72)); + ret = m_container->pt_to_px(round_f(val.val() * 0.3937f * 72)); // 1cm = (1/2.54)in = (72/2.54)pt break; case css_units_mm: - ret = m_container->pt_to_px((int) (val.val() * 0.3937 * 72) / 10); + ret = m_container->pt_to_px(round_f(val.val() * 0.3937f * 72 / 10)); break; + case css_units_vw: ret = (int)((double)m_media.width * (double)val.val() / 100.0); break; @@ -685,6 +656,12 @@ int document::to_pixels( const css_length& val, int fontSize, int size ) const case css_units_rem: ret = (int) ((double) m_root->css().get_font_size() * (double) val.val()); break; + case css_units_ex: + ret = (int) ((double) metrics.x_height * val.val()); + break; + case css_units_ch: + ret = (int) ((double) metrics.ch_width * val.val()); + break; default: ret = (int) val.val(); break; @@ -692,37 +669,15 @@ int document::to_pixels( const css_length& val, int fontSize, int size ) const return ret; } -void document::cvt_units( css_length& val, int fontSize, int /*size*/ ) const +void document::cvt_units( css_length& val, const font_metrics& metrics, int size ) const { if(val.is_predefined()) { return; } - int ret; - switch(val.units()) + if(val.units() != css_units_percentage) { - case css_units_em: - ret = round_f(val.val() * (float) fontSize); - val.set_value((float) ret, css_units_px); - break; - case css_units_pt: - ret = m_container->pt_to_px((int) val.val()); - val.set_value((float) ret, css_units_px); - break; - case css_units_in: - ret = m_container->pt_to_px((int) (val.val() * 72)); - val.set_value((float) ret, css_units_px); - break; - case css_units_cm: - ret = m_container->pt_to_px((int) (val.val() * 0.3937 * 72)); - val.set_value((float) ret, css_units_px); - break; - case css_units_mm: - ret = m_container->pt_to_px((int) (val.val() * 0.3937 * 72) / 10); - val.set_value((float) ret, css_units_px); - break; - default: - break; + val.set_value((float)to_pixels(val, metrics, size), css_units_px); } } @@ -751,7 +706,7 @@ void document::add_stylesheet( const char* str, const char* baseurl, const char* { if(str && str[0]) { - m_css.push_back(css_text(str, baseurl, media)); + m_css.emplace_back(str, baseurl, media); } } @@ -772,6 +727,7 @@ bool document::on_mouse_over( int x, int y, int client_x, int client_y, position { if(m_over_element->on_mouse_leave()) { + m_container->on_mouse_event(m_over_element, mouse_event_leave); state_was_changed = true; } } @@ -793,6 +749,7 @@ bool document::on_mouse_over( int x, int y, int client_x, int client_y, position if(state_was_changed) { + m_container->on_mouse_event(m_over_element, mouse_event_enter); return m_root->find_styles_changes(redraw_boxes); } return false; @@ -808,6 +765,7 @@ bool document::on_mouse_leave( position::vector& redraw_boxes ) { if(m_over_element->on_mouse_leave()) { + m_container->on_mouse_event(m_over_element, mouse_event_leave); return m_root->find_styles_changes(redraw_boxes); } } @@ -831,6 +789,7 @@ bool document::on_lbutton_down( int x, int y, int client_x, int client_y, positi { if(m_over_element->on_mouse_leave()) { + m_container->on_mouse_event(m_over_element, mouse_event_leave); state_was_changed = true; } } @@ -859,6 +818,7 @@ bool document::on_lbutton_down( int x, int y, int client_x, int client_y, positi if(state_was_changed) { + m_container->on_mouse_event(m_over_element, mouse_event_enter); return m_root->find_styles_changes(redraw_boxes); } @@ -905,11 +865,11 @@ bool document::media_changed() bool document::lang_changed() { - if(!m_media_lists.empty()) + if (!m_media_lists.empty()) { string culture; container()->get_language(m_lang, culture); - if(!culture.empty()) + if (!culture.empty()) { m_culture = m_lang + '-' + culture; } @@ -924,12 +884,13 @@ bool document::lang_changed() return false; } +// Apply media features (determine which selectors are active). bool document::update_media_lists(const media_features& features) { bool update_styles = false; - for(auto & m_media_list : m_media_lists) + for (auto& media_list : m_media_lists) { - if(m_media_list->apply_media_features(features)) + if (media_list->apply_media_features(features)) { update_styles = true; } @@ -937,15 +898,10 @@ bool document::update_media_lists(const media_features& features) return update_styles; } -void document::add_media_list( const media_query_list::ptr& list ) +void document::add_media_list(media_query_list_list::ptr list) { - if(list) - { - if(std::find(m_media_lists.begin(), m_media_lists.end(), list) == m_media_lists.end()) - { - m_media_lists.push_back(list); - } - } + if (list && !contains(m_media_lists, list)) + m_media_lists.push_back(list); } void document::fix_tables_layout() @@ -1197,4 +1153,4 @@ void document::dump(dumper& cout) } } -} // namespace litehtml
\ No newline at end of file +} // namespace litehtml diff --git a/libs/litehtml/src/el_before_after.cpp b/libs/litehtml/src/el_before_after.cpp index 5bf5aae81c..a3f754c2a4 100644 --- a/libs/litehtml/src/el_before_after.cpp +++ b/libs/litehtml/src/el_before_after.cpp @@ -30,7 +30,7 @@ void litehtml::el_before_after_base::add_style(const style& style) { if(str.at(i) == '"' || str.at(i) == '\'') { - auto chr = str.at(i); + auto chr = str.at(i); fnc.clear(); i++; string::size_type pos = str.find(chr, i); @@ -169,6 +169,7 @@ void litehtml::el_before_after_base::add_function( const string& fnc, const stri { string_vector tokens; split_string(params, tokens, ","); + for (auto& str : tokens) trim(str); add_text(get_counters_value(tokens)); } break; @@ -207,9 +208,9 @@ void litehtml::el_before_after_base::add_function( const string& fnc, const stri litehtml::string litehtml::el_before_after_base::convert_escape( const char* txt ) { - char* str_end; + char* str_end; char32_t u_str[2]; - u_str[0] = (char32_t) strtol(txt, &str_end, 16); - u_str[1] = 0; - return litehtml::string(litehtml_from_utf32(u_str)); + u_str[0] = (char32_t) strtol(txt, &str_end, 16); + u_str[1] = 0; + return string(litehtml_from_utf32(u_str)); } diff --git a/libs/litehtml/src/el_image.cpp b/libs/litehtml/src/el_image.cpp index 5c4d70e103..0dee84e011 100644 --- a/libs/litehtml/src/el_image.cpp +++ b/libs/litehtml/src/el_image.cpp @@ -21,16 +21,14 @@ void litehtml::el_image::parse_attributes() { m_src = get_attr("src", ""); - const char* attr_height = get_attr("height"); - if(attr_height) - { - m_style.add_property(_height_, attr_height); - } - const char* attr_width = get_attr("width"); - if(attr_width) - { - m_style.add_property(_width_, attr_width); - } + // https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images:the-img-element-5 + const char* str = get_attr("width"); + if (str) + map_to_dimension_property(_width_, str); + + str = get_attr("height"); + if (str) + map_to_dimension_property(_height_, str); } void litehtml::el_image::draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr<render_item> &ri) @@ -76,12 +74,12 @@ void litehtml::el_image::compute_styles(bool recursive) litehtml::string litehtml::el_image::dump_get_name() { - return "img src=\"" + m_src + "\""; + return "img src=\"" + m_src + "\""; } std::shared_ptr<litehtml::render_item> litehtml::el_image::create_render_item(const std::shared_ptr<render_item>& parent_ri) { - auto ret = std::make_shared<render_item_image>(shared_from_this()); - ret->parent(parent_ri); - return ret; + auto ret = std::make_shared<render_item_image>(shared_from_this()); + ret->parent(parent_ri); + return ret; } diff --git a/libs/litehtml/src/el_space.cpp b/libs/litehtml/src/el_space.cpp index f5e3818ca7..879e04bf98 100644 --- a/libs/litehtml/src/el_space.cpp +++ b/libs/litehtml/src/el_space.cpp @@ -35,10 +35,10 @@ bool litehtml::el_space::is_break() const bool litehtml::el_space::is_space() const { - return true; + return true; } litehtml::string litehtml::el_space::dump_get_name() { - return "space: \"" + get_escaped_string(m_text) + "\""; + return "space: \"" + get_escaped_string(m_text) + "\""; } diff --git a/libs/litehtml/src/el_table.cpp b/libs/litehtml/src/el_table.cpp index 0bb8648436..82b02c6921 100644 --- a/libs/litehtml/src/el_table.cpp +++ b/libs/litehtml/src/el_table.cpp @@ -3,13 +3,14 @@ #include "document.h" #include "iterators.h" +namespace litehtml +{ -litehtml::el_table::el_table(const std::shared_ptr<document>& doc) : html_tag(doc) +el_table::el_table(const shared_ptr<document>& doc) : html_tag(doc) { } - -bool litehtml::el_table::appendChild(const element::ptr& el) +bool el_table::appendChild(const element::ptr& el) { if(!el) return false; if( el->tag() == _tbody_ || @@ -22,29 +23,29 @@ bool litehtml::el_table::appendChild(const element::ptr& el) return false; } -void litehtml::el_table::parse_attributes() +void el_table::parse_attributes() { + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-table-width const char* str = get_attr("width"); - if(str) - { - m_style.add_property(_width_, str); - } + if (str) + map_to_dimension_property_ignoring_zero(_width_, str); + + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-table-height + str = get_attr("height"); + if (str) + map_to_dimension_property(_height_, str); + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-table-cellspacing str = get_attr("cellspacing"); - if(str) - { - string val = str; - val += " "; - val += str; - m_style.add_property(_border_spacing_, val); - } - + if (str) + map_to_pixel_length_property(_border_spacing_, str); + + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-table-border str = get_attr("border"); - if(str) - { - m_style.add_property(_border_width_, str); - } + if (str) + map_to_pixel_length_property_with_default_value(_border_width_, str, 1); + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-background str = get_attr("bgcolor"); if (str) { @@ -53,3 +54,5 @@ void litehtml::el_table::parse_attributes() html_tag::parse_attributes(); } + +} // namespace litehtml
\ No newline at end of file diff --git a/libs/litehtml/src/el_td.cpp b/libs/litehtml/src/el_td.cpp index 679d9212ab..659bfb1bc7 100644 --- a/libs/litehtml/src/el_td.cpp +++ b/libs/litehtml/src/el_td.cpp @@ -1,19 +1,26 @@ #include "html.h" #include "el_td.h" - -litehtml::el_td::el_td(const std::shared_ptr<document>& doc) : html_tag(doc) +namespace litehtml { +el_td::el_td(const shared_ptr<document>& doc) : html_tag(doc) +{ } -void litehtml::el_td::parse_attributes() +void el_td::parse_attributes() { + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-tdth-width const char* str = get_attr("width"); - if(str) - { - m_style.add_property(_width_, str); - } + if (str) + map_to_dimension_property_ignoring_zero(_width_, str); + + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-tdth-height + str = get_attr("height"); + if (str) + map_to_dimension_property_ignoring_zero(_height_, str); + + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-background str = get_attr("background"); if(str) { @@ -22,11 +29,6 @@ void litehtml::el_td::parse_attributes() url += "')"; m_style.add_property(_background_image_, url); } - str = get_attr("align"); - if(str) - { - m_style.add_property(_text_align_, str); - } str = get_attr("bgcolor"); if (str) @@ -34,10 +36,19 @@ void litehtml::el_td::parse_attributes() m_style.add_property(_background_color_, str, "", false, get_document()->container()); } + str = get_attr("align"); + if(str) + { + m_style.add_property(_text_align_, str); + } + str = get_attr("valign"); if(str) { m_style.add_property(_vertical_align_, str); } + html_tag::parse_attributes(); } + +} // namespace litehtml
\ No newline at end of file diff --git a/libs/litehtml/src/el_text.cpp b/libs/litehtml/src/el_text.cpp index 5a1dffb69e..70ed82e6af 100644 --- a/libs/litehtml/src/el_text.cpp +++ b/libs/litehtml/src/el_text.cpp @@ -10,7 +10,7 @@ litehtml::el_text::el_text(const char* text, const document::ptr& doc) : element } m_use_transformed = false; m_draw_spaces = true; - css_w().set_display(display_inline_text); + css_w().set_display(display_inline_text); } void litehtml::el_text::get_content_size( size& sz, int /*max_width*/ ) @@ -25,17 +25,17 @@ void litehtml::el_text::get_text( string& text ) void litehtml::el_text::compute_styles(bool /*recursive*/) { - element::ptr el_parent = parent(); - if (el_parent) - { - css_w().set_line_height(el_parent->css().get_line_height()); - css_w().set_font(el_parent->css().get_font()); - css_w().set_font_metrics(el_parent->css().get_font_metrics()); - css_w().set_white_space(el_parent->css().get_white_space()); + element::ptr el_parent = parent(); + if (el_parent) + { + css_w().set_line_height(el_parent->css().get_line_height()); + css_w().set_font(el_parent->css().get_font()); + css_w().set_font_metrics(el_parent->css().get_font_metrics()); + css_w().set_white_space(el_parent->css().get_white_space()); css_w().set_text_transform(el_parent->css().get_text_transform()); - } - css_w().set_display(display_inline_text); - css_w().set_float(float_none); + } + css_w().set_display(display_inline_text); + css_w().set_float(float_none); if(m_css.get_text_transform() != text_transform_none) { @@ -47,21 +47,21 @@ void litehtml::el_text::compute_styles(bool /*recursive*/) m_use_transformed = false; } - element::ptr p = parent(); - while(p && p->css().get_display() == display_inline) - { - if(p->css().get_position() == element_position_relative) - { - css_w().set_offsets(p->css().get_offsets()); - css_w().set_position(element_position_relative); - break; - } - p = p->parent(); - } - if(p) - { - css_w().set_position(element_position_static); - } + element::ptr p = parent(); + while(p && p->css().get_display() == display_inline) + { + if(p->css().get_position() == element_position_relative) + { + css_w().set_offsets(p->css().get_offsets()); + css_w().set_position(element_position_relative); + break; + } + p = p->parent(); + } + if(p) + { + css_w().set_position(element_position_static); + } if(is_white_space()) { @@ -86,7 +86,7 @@ void litehtml::el_text::compute_styles(bool /*recursive*/) if (el_parent) { font = el_parent->css().get_font(); - fm = el_parent->css().get_font_metrics(); + fm = el_parent->css().get_font_metrics(); } if(is_break() || !font) { @@ -131,10 +131,10 @@ void litehtml::el_text::draw(uint_ptr hdc, int x, int y, const position *clip, c litehtml::string litehtml::el_text::dump_get_name() { - return "text: \"" + get_escaped_string(m_text) + "\""; + return "text: \"" + get_escaped_string(m_text) + "\""; } std::vector<std::tuple<litehtml::string, litehtml::string>> litehtml::el_text::dump_get_attrs() { - return std::vector<std::tuple<string, string>>(); + return {}; } diff --git a/libs/litehtml/src/el_tr.cpp b/libs/litehtml/src/el_tr.cpp index f109976542..5b80cc1221 100644 --- a/libs/litehtml/src/el_tr.cpp +++ b/libs/litehtml/src/el_tr.cpp @@ -4,12 +4,16 @@ litehtml::el_tr::el_tr(const std::shared_ptr<document>& doc) : html_tag(doc) { - } void litehtml::el_tr::parse_attributes() { - const char* str = get_attr("align"); + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-tr-height + const char* str = get_attr("height"); + if (str) + map_to_dimension_property(_height_, str); + + str = get_attr("align"); if(str) { m_style.add_property(_text_align_, str); diff --git a/libs/litehtml/src/element.cpp b/libs/litehtml/src/element.cpp index 8b3d2ef495..fb4c2c2948 100644 --- a/libs/litehtml/src/element.cpp +++ b/libs/litehtml/src/element.cpp @@ -384,7 +384,7 @@ std::vector<element::ptr> litehtml::element::get_siblings_before() const void litehtml::element::parse_counter_tokens(const string_vector& tokens, const int default_value, std::function<void(const string_id&, const int)> handler) const { int pos = 0; while (pos < (int) tokens.size()) { - string name = tokens[pos]; + const string& name = tokens[pos]; int value = default_value; if (pos < (int) tokens.size() - 1 && litehtml::is_number(tokens[pos + 1], false)) { value = atoi(tokens[pos + 1].c_str()); @@ -423,8 +423,8 @@ element::ptr element::select_one( const css_selector& /*selector*/ ) LITEHTML element::ptr element::select_one( const string& /*selector*/ ) LITEHTML_RETURN_FUNC(nullptr) element::ptr element::find_adjacent_sibling(const element::ptr& /*el*/, const css_selector& /*selector*/, bool /*apply_pseudo*/ /*= true*/, bool* /*is_pseudo*/ /*= 0*/) LITEHTML_RETURN_FUNC(nullptr) element::ptr element::find_sibling(const element::ptr& /*el*/, const css_selector& /*selector*/, bool /*apply_pseudo*/ /*= true*/, bool* /*is_pseudo*/ /*= 0*/) LITEHTML_RETURN_FUNC(nullptr) -bool element::is_nth_last_child(const element::ptr& /*el*/, int /*num*/, int /*off*/, bool /*of_type*/) const LITEHTML_RETURN_FUNC(false) -bool element::is_nth_child(const element::ptr&, int /*num*/, int /*off*/, bool /*of_type*/) const LITEHTML_RETURN_FUNC(false) +bool element::is_nth_last_child(const element::ptr& /*el*/, int /*num*/, int /*off*/, bool /*of_type*/, const css_selector::vector& /*selector_list*/) const LITEHTML_RETURN_FUNC(false) +bool element::is_nth_child(const element::ptr&, int /*num*/, int /*off*/, bool /*of_type*/, const css_selector::vector& /*selector_list*/) const LITEHTML_RETURN_FUNC(false) bool element::is_only_child(const element::ptr& /*el*/, bool /*of_type*/) const LITEHTML_RETURN_FUNC(false) void element::get_content_size( size& /*sz*/, int /*max_width*/ ) LITEHTML_EMPTY_FUNC bool element::appendChild(const ptr &/*el*/) LITEHTML_RETURN_FUNC(false) @@ -459,9 +459,10 @@ void element::draw(uint_ptr /*hdc*/, int /*x*/, int /*y*/, const position */*cli void element::draw_background(uint_ptr /*hdc*/, int /*x*/, int /*y*/, const position */*clip*/, const std::shared_ptr<render_item> &/*ri*/) LITEHTML_EMPTY_FUNC void element::get_text( string& /*text*/ ) LITEHTML_EMPTY_FUNC void element::parse_attributes() LITEHTML_EMPTY_FUNC -int element::select(const string& /*selector*/) LITEHTML_RETURN_FUNC(select_no_match) -int element::select(const css_selector& /*selector*/, bool /*apply_pseudo*/) LITEHTML_RETURN_FUNC(select_no_match) -int element::select( const css_element_selector& /*selector*/, bool /*apply_pseudo*/ /*= true*/ ) LITEHTML_RETURN_FUNC(select_no_match) +int element::select(const css_selector::vector& /*selector_list*/, bool /*apply_pseudo*/) LITEHTML_RETURN_FUNC(select_no_match) +int element::select(const string& /*selector*/) LITEHTML_RETURN_FUNC(select_no_match) +int element::select(const css_selector& /*selector*/, bool /*apply_pseudo*/) LITEHTML_RETURN_FUNC(select_no_match) +int element::select(const css_element_selector& /*selector*/, bool /*apply_pseudo*/) LITEHTML_RETURN_FUNC(select_no_match) element::ptr element::find_ancestor(const css_selector& /*selector*/, bool /*apply_pseudo*/, bool* /*is_pseudo*/) LITEHTML_RETURN_FUNC(nullptr) -} // namespace litehtml
\ No newline at end of file +} // namespace litehtml diff --git a/libs/litehtml/src/encodings.cpp b/libs/litehtml/src/encodings.cpp index b1c58fed50..9cb64d821b 100644 --- a/libs/litehtml/src/encodings.cpp +++ b/libs/litehtml/src/encodings.cpp @@ -1,13 +1,10 @@ #include "html.h" #include "encodings.h" -#include <assert.h> +#include <cassert> #define out #define inout -#define countof(a) int(sizeof(a)/sizeof(a[0])) - -#define is_surrogate(ch) ((ch) >= 0xD800 && (ch) < 0xE000) - +#define countof(a) int(sizeof(a)/sizeof((a)[0])) namespace litehtml { @@ -43,13 +40,13 @@ struct decoder enum result { result_finished, // no more bytes in input - + result_error, // decoding error occurred, 0xFFFD will be emitted (see process_an_item) - + // decoder handler: need more bytes to compose a character // process_an_item: keep going (don't exit the loop in process_a_queue) result_continue, - + // handler returns one or two UTF-32 codepoints through ch parameter (only Big5 decoder can return two codepoints) result_codepoint }; @@ -65,7 +62,7 @@ struct decoder decoder::result decoder::process_a_queue(string& input, string& output, error_mode mode) { int index = 0; - while (1) + while (true) { // NOTE: we read byte from input in decoder handlers, not here (standard prescribes to do it here). auto result = process_an_item(input, index, output, mode); @@ -74,7 +71,7 @@ decoder::result decoder::process_a_queue(string& input, string& output, error_mo } // https://encoding.spec.whatwg.org/#concept-encoding-process -// ioQueue is represented by the pair {input, index}, index points to the head of the queue. +// ioQueue is represented by the pair {input, index}, index points to the head of the queue. // Some decoders may modify input (so they're not just incrementing the index). // "item" is the current input byte, input[index]. decoder::result decoder::process_an_item(string& input, int& index, string& output, error_mode mode) @@ -145,7 +142,7 @@ decoder::ptr get_decoder(encoding _encoding); // input is copied because it can be modified by GB18030, ISO-2022-JP and UTF-16 decoders void decode(string input, encoding _encoding, string& output) { - // 1. + // 1. encoding bom_encoding = bom_sniff(input); // 2. @@ -161,10 +158,17 @@ void decode(string input, encoding _encoding, string& output) decoder->process_a_queue(input, output, error_mode::replacement); } +string decode(string input, encoding encoding) +{ + string output; + decode(input, encoding, output); + return output; +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct utf_8_decoder : decoder +struct utf_8_decoder final : decoder { int m_code_point = 0; int m_bytes_seen = 0; @@ -221,7 +225,7 @@ decoder::result utf_8_decoder::handler(inout string& input, inout int& index, ou } else return result_error; - + return result_continue; } @@ -271,7 +275,7 @@ decoder::result utf_8_decoder::handler(inout string& input, inout int& index, ou ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct single_byte_decoder : decoder +struct single_byte_decoder final : decoder { int* m_index; // https://encoding.spec.whatwg.org/#index-single-byte @@ -405,12 +409,12 @@ decoder::result single_byte_decoder::handler(string& input, int& index, int ch[2 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct gb18030_decoder : decoder +struct gb18030_decoder final : decoder { int m_first = 0; int m_second = 0; int m_third = 0; - + result handler(string& input, int& index, int ch[2]) override; static int ranges_code_point(int pointer); @@ -440,7 +444,7 @@ int gb18030_decoder::ranges_code_point(int pointer) if (pointer == 7457) return 0xE7C7; - // 3. Let offset be the last pointer in index gb18030 ranges that is less than or equal to pointer and + // 3. Let offset be the last pointer in index gb18030 ranges that is less than or equal to pointer and // let code point offset be its corresponding code point. int i = 0; while (i < countof(m_ranges) && m_ranges[i].pointer <= pointer) i++; @@ -468,7 +472,7 @@ decoder::result gb18030_decoder::handler(string& input, int& index, int ch[2]) m_third = 0; return result_error; } - + // 3. If gb18030 third is not 0x00, then: if (m_third != 0) { @@ -533,7 +537,7 @@ decoder::result gb18030_decoder::handler(string& input, int& index, int ch[2]) m_second = b; return result_continue; } - + // 2. int lead = m_first; int pointer = null; @@ -563,7 +567,7 @@ decoder::result gb18030_decoder::handler(string& input, int& index, int ch[2]) return result_error; } - // 6. + // 6. if (b >= 0 && b <= 0x7F) { *ch = b; @@ -577,7 +581,7 @@ decoder::result gb18030_decoder::handler(string& input, int& index, int ch[2]) return result_codepoint; } - // 8. + // 8. if (b >= 0x81 && b <= 0xFE) { m_first = b; @@ -590,10 +594,10 @@ decoder::result gb18030_decoder::handler(string& input, int& index, int ch[2]) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct big5_decoder : decoder +struct big5_decoder final : decoder { int m_lead = 0; - + result handler(string& input, int& index, int ch[2]) override; static int m_index[19782]; @@ -618,7 +622,7 @@ decoder::result big5_decoder::handler(inout string& input, inout int& index, out if (b == EOF && m_lead == 0) return result_finished; - // 3. + // 3. if (m_lead != 0) { int lead = m_lead; @@ -628,7 +632,7 @@ decoder::result big5_decoder::handler(inout string& input, inout int& index, out // 1. int offset = b < 0x7F ? 0x40 : 0x62; - // 2. + // 2. if ((b >= 0x40 && b <= 0x7E) || (b >= 0xA1 && b <= 0xFE)) pointer = (lead - 0x81) * 157 + b - offset; @@ -662,7 +666,7 @@ decoder::result big5_decoder::handler(inout string& input, inout int& index, out return result_codepoint; } - // 5. + // 5. if (b >= 0x81 && b <= 0xFE) { m_lead = b; @@ -686,7 +690,7 @@ int jis_decoder::m_jis0208_index[] = {12288,12289,12290,65292,65294,12539,65306, int jis_decoder::m_jis0212_index[] = {null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,728,711,184,729,733,175,731,730,65374,900,901,null,null,null,null,null,null,null,null,161,166,191,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,186,170,169,174,8482,164,8470,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,902,904,905,906,938,null,908,null,910,939,null,911,null,null,null,null,940,941,942,943,970,912,972,962,973,971,944,974,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1038,1039,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1118,1119,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,198,272,null,294,null,306,null,321,319,null,330,216,338,null,358,222,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,230,273,240,295,305,307,312,322,320,329,331,248,339,223,359,254,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,193,192,196,194,258,461,256,260,197,195,262,264,268,199,266,270,201,200,203,202,282,278,274,280,null,284,286,290,288,292,205,204,207,206,463,304,298,302,296,308,310,313,317,315,323,327,325,209,211,210,214,212,465,336,332,213,340,344,342,346,348,352,350,356,354,218,217,220,219,364,467,368,362,370,366,360,471,475,473,469,372,221,376,374,377,381,379,null,null,null,null,null,null,null,225,224,228,226,259,462,257,261,229,227,263,265,269,231,267,271,233,232,235,234,283,279,275,281,501,285,287,null,289,293,237,236,239,238,464,null,299,303,297,309,311,314,318,316,324,328,326,241,243,242,246,244,466,337,333,245,341,345,343,347,349,353,351,357,355,250,249,252,251,365,468,369,363,371,367,361,472,476,474,470,373,253,255,375,378,382,380,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,19970,19972,19973,19980,19986,19999,20003,20004,20008,20011,20014,20015,20016,20021,20032,20033,20036,20039,20049,20058,20060,20067,20072,20073,20084,20085,20089,20095,20109,20118,20119,20125,20143,20153,20163,20176,20186,20187,20192,20193,20194,20200,20207,20209,20211,20213,20221,20222,20223,20224,20226,20227,20232,20235,20236,20242,20245,20246,20247,20249,20270,20273,20320,20275,20277,20279,20281,20283,20286,20288,20290,20296,20297,20299,20300,20306,20308,20310,20312,20319,20323,20330,20332,20334,20337,20343,20344,20345,20346,20349,20350,20353,20354,20356,20357,20361,20362,20364,20366,20368,20370,20371,20372,20375,20377,20378,20382,20383,20402,20407,20409,20411,20412,20413,20414,20416,20417,20421,20422,20424,20425,20427,20428,20429,20431,20434,20444,20448,20450,20464,20466,20476,20477,20479,20480,20481,20484,20487,20490,20492,20494,20496,20499,20503,20504,20507,20508,20509,20510,20514,20519,20526,20528,20530,20531,20533,20544,20545,20546,20549,20550,20554,20556,20558,20561,20562,20563,20567,20569,20575,20576,20578,20579,20582,20583,20586,20589,20592,20593,20539,20609,20611,20612,20614,20618,20622,20623,20624,20626,20627,20628,20630,20635,20636,20638,20639,20640,20641,20642,20650,20655,20656,20665,20666,20669,20672,20675,20676,20679,20684,20686,20688,20691,20692,20696,20700,20701,20703,20706,20708,20710,20712,20713,20719,20721,20726,20730,20734,20739,20742,20743,20744,20747,20748,20749,20750,20722,20752,20759,20761,20763,20764,20765,20766,20771,20775,20776,20780,20781,20783,20785,20787,20788,20789,20792,20793,20802,20810,20815,20819,20821,20823,20824,20831,20836,20838,20862,20867,20868,20875,20878,20888,20893,20897,20899,20909,20920,20922,20924,20926,20927,20930,20936,20943,20945,20946,20947,20949,20952,20958,20962,20965,20974,20978,20979,20980,20983,20993,20994,20997,21010,21011,21013,21014,21016,21026,21032,21041,21042,21045,21052,21061,21065,21077,21079,21080,21082,21084,21087,21088,21089,21094,21102,21111,21112,21113,21120,21122,21125,21130,21132,21139,21141,21142,21143,21144,21146,21148,21156,21157,21158,21159,21167,21168,21174,21175,21176,21178,21179,21181,21184,21188,21190,21192,21196,21199,21201,21204,21206,21211,21212,21217,21221,21224,21225,21226,21228,21232,21233,21236,21238,21239,21248,21251,21258,21259,21260,21265,21267,21272,21275,21276,21278,21279,21285,21287,21288,21289,21291,21292,21293,21296,21298,21301,21308,21309,21310,21314,21324,21323,21337,21339,21345,21347,21349,21356,21357,21362,21369,21374,21379,21383,21384,21390,21395,21396,21401,21405,21409,21412,21418,21419,21423,21426,21428,21429,21431,21432,21434,21437,21440,21445,21455,21458,21459,21461,21466,21469,21470,21472,21478,21479,21493,21506,21523,21530,21537,21543,21544,21546,21551,21553,21556,21557,21571,21572,21575,21581,21583,21598,21602,21604,21606,21607,21609,21611,21613,21614,21620,21631,21633,21635,21637,21640,21641,21645,21649,21653,21654,21660,21663,21665,21670,21671,21673,21674,21677,21678,21681,21687,21689,21690,21691,21695,21702,21706,21709,21710,21728,21738,21740,21743,21750,21756,21758,21759,21760,21761,21765,21768,21769,21772,21773,21774,21781,21802,21803,21810,21813,21814,21819,21820,21821,21825,21831,21833,21834,21837,21840,21841,21848,21850,21851,21854,21856,21857,21860,21862,21887,21889,21890,21894,21896,21902,21903,21905,21906,21907,21908,21911,21923,21924,21933,21938,21951,21953,21955,21958,21961,21963,21964,21966,21969,21970,21971,21975,21976,21979,21982,21986,21993,22006,22015,22021,22024,22026,22029,22030,22031,22032,22033,22034,22041,22060,22064,22067,22069,22071,22073,22075,22076,22077,22079,22080,22081,22083,22084,22086,22089,22091,22093,22095,22100,22110,22112,22113,22114,22115,22118,22121,22125,22127,22129,22130,22133,22148,22149,22152,22155,22156,22165,22169,22170,22173,22174,22175,22182,22183,22184,22185,22187,22188,22189,22193,22195,22199,22206,22213,22217,22218,22219,22223,22224,22220,22221,22233,22236,22237,22239,22241,22244,22245,22246,22247,22248,22257,22251,22253,22262,22263,22273,22274,22279,22282,22284,22289,22293,22298,22299,22301,22304,22306,22307,22308,22309,22313,22314,22316,22318,22319,22323,22324,22333,22334,22335,22341,22342,22348,22349,22354,22370,22373,22375,22376,22379,22381,22382,22383,22384,22385,22387,22388,22389,22391,22393,22394,22395,22396,22398,22401,22403,22412,22420,22423,22425,22426,22428,22429,22430,22431,22433,22421,22439,22440,22441,22444,22456,22461,22471,22472,22476,22479,22485,22493,22494,22500,22502,22503,22505,22509,22512,22517,22518,22520,22525,22526,22527,22531,22532,22536,22537,22497,22540,22541,22555,22558,22559,22560,22566,22567,22573,22578,22585,22591,22601,22604,22605,22607,22608,22613,22623,22625,22628,22631,22632,22648,22652,22655,22656,22657,22663,22664,22665,22666,22668,22669,22671,22672,22676,22678,22685,22688,22689,22690,22694,22697,22705,22706,22724,22716,22722,22728,22733,22734,22736,22738,22740,22742,22746,22749,22753,22754,22761,22771,22789,22790,22795,22796,22802,22803,22804,34369,22813,22817,22819,22820,22824,22831,22832,22835,22837,22838,22847,22851,22854,22866,22867,22873,22875,22877,22878,22879,22881,22883,22891,22893,22895,22898,22901,22902,22905,22907,22908,22923,22924,22926,22930,22933,22935,22943,22948,22951,22957,22958,22959,22960,22963,22967,22970,22972,22977,22979,22980,22984,22986,22989,22994,23005,23006,23007,23011,23012,23015,23022,23023,23025,23026,23028,23031,23040,23044,23052,23053,23054,23058,23059,23070,23075,23076,23079,23080,23082,23085,23088,23108,23109,23111,23112,23116,23120,23125,23134,23139,23141,23143,23149,23159,23162,23163,23166,23179,23184,23187,23190,23193,23196,23198,23199,23200,23202,23207,23212,23217,23218,23219,23221,23224,23226,23227,23231,23236,23238,23240,23247,23258,23260,23264,23269,23274,23278,23285,23286,23293,23296,23297,23304,23319,23348,23321,23323,23325,23329,23333,23341,23352,23361,23371,23372,23378,23382,23390,23400,23406,23407,23420,23421,23422,23423,23425,23428,23430,23434,23438,23440,23441,23443,23444,23446,23464,23465,23468,23469,23471,23473,23474,23479,23482,23484,23488,23489,23501,23503,23510,23511,23512,23513,23514,23520,23535,23537,23540,23549,23564,23575,23582,23583,23587,23590,23593,23595,23596,23598,23600,23602,23605,23606,23641,23642,23644,23650,23651,23655,23656,23657,23661,23664,23668,23669,23674,23675,23676,23677,23687,23688,23690,23695,23698,23709,23711,23712,23714,23715,23718,23722,23730,23732,23733,23738,23753,23755,23762,23773,23767,23790,23793,23794,23796,23809,23814,23821,23826,23851,23843,23844,23846,23847,23857,23860,23865,23869,23871,23874,23875,23878,23880,23893,23889,23897,23882,23903,23904,23905,23906,23908,23914,23917,23920,23929,23930,23934,23935,23937,23939,23944,23946,23954,23955,23956,23957,23961,23963,23967,23968,23975,23979,23984,23988,23992,23993,24003,24007,24011,24016,24014,24024,24025,24032,24036,24041,24056,24057,24064,24071,24077,24082,24084,24085,24088,24095,24096,24110,24104,24114,24117,24126,24139,24144,24137,24145,24150,24152,24155,24156,24158,24168,24170,24171,24172,24173,24174,24176,24192,24203,24206,24226,24228,24229,24232,24234,24236,24241,24243,24253,24254,24255,24262,24268,24267,24270,24273,24274,24276,24277,24284,24286,24293,24299,24322,24326,24327,24328,24334,24345,24348,24349,24353,24354,24355,24356,24360,24363,24364,24366,24368,24372,24374,24379,24381,24383,24384,24388,24389,24391,24397,24400,24404,24408,24411,24416,24419,24420,24423,24431,24434,24436,24437,24440,24442,24445,24446,24457,24461,24463,24470,24476,24477,24482,24487,24491,24484,24492,24495,24496,24497,24504,24516,24519,24520,24521,24523,24528,24529,24530,24531,24532,24542,24545,24546,24552,24553,24554,24556,24557,24558,24559,24562,24563,24566,24570,24572,24583,24586,24589,24595,24596,24599,24600,24602,24607,24612,24621,24627,24629,24640,24647,24648,24649,24652,24657,24660,24662,24663,24669,24673,24679,24689,24702,24703,24706,24710,24712,24714,24718,24721,24723,24725,24728,24733,24734,24738,24740,24741,24744,24752,24753,24759,24763,24766,24770,24772,24776,24777,24778,24779,24782,24783,24788,24789,24793,24795,24797,24798,24802,24805,24818,24821,24824,24828,24829,24834,24839,24842,24844,24848,24849,24850,24851,24852,24854,24855,24857,24860,24862,24866,24874,24875,24880,24881,24885,24886,24887,24889,24897,24901,24902,24905,24926,24928,24940,24946,24952,24955,24956,24959,24960,24961,24963,24964,24971,24973,24978,24979,24983,24984,24988,24989,24991,24992,24997,25000,25002,25005,25016,25017,25020,25024,25025,25026,25038,25039,25045,25052,25053,25054,25055,25057,25058,25063,25065,25061,25068,25069,25071,25089,25091,25092,25095,25107,25109,25116,25120,25122,25123,25127,25129,25131,25145,25149,25154,25155,25156,25158,25164,25168,25169,25170,25172,25174,25178,25180,25188,25197,25199,25203,25210,25213,25229,25230,25231,25232,25254,25256,25267,25270,25271,25274,25278,25279,25284,25294,25301,25302,25306,25322,25330,25332,25340,25341,25347,25348,25354,25355,25357,25360,25363,25366,25368,25385,25386,25389,25397,25398,25401,25404,25409,25410,25411,25412,25414,25418,25419,25422,25426,25427,25428,25432,25435,25445,25446,25452,25453,25457,25460,25461,25464,25468,25469,25471,25474,25476,25479,25482,25488,25492,25493,25497,25498,25502,25508,25510,25517,25518,25519,25533,25537,25541,25544,25550,25553,25555,25556,25557,25564,25568,25573,25578,25580,25586,25587,25589,25592,25593,25609,25610,25616,25618,25620,25624,25630,25632,25634,25636,25637,25641,25642,25647,25648,25653,25661,25663,25675,25679,25681,25682,25683,25684,25690,25691,25692,25693,25695,25696,25697,25699,25709,25715,25716,25723,25725,25733,25735,25743,25744,25745,25752,25753,25755,25757,25759,25761,25763,25766,25768,25772,25779,25789,25790,25791,25796,25801,25802,25803,25804,25806,25808,25809,25813,25815,25828,25829,25833,25834,25837,25840,25845,25847,25851,25855,25857,25860,25864,25865,25866,25871,25875,25876,25878,25881,25883,25886,25887,25890,25894,25897,25902,25905,25914,25916,25917,25923,25927,25929,25936,25938,25940,25951,25952,25959,25963,25978,25981,25985,25989,25994,26002,26005,26008,26013,26016,26019,26022,26030,26034,26035,26036,26047,26050,26056,26057,26062,26064,26068,26070,26072,26079,26096,26098,26100,26101,26105,26110,26111,26112,26116,26120,26121,26125,26129,26130,26133,26134,26141,26142,26145,26146,26147,26148,26150,26153,26154,26155,26156,26158,26160,26161,26163,26169,26167,26176,26181,26182,26186,26188,26193,26190,26199,26200,26201,26203,26204,26208,26209,26363,26218,26219,26220,26238,26227,26229,26239,26231,26232,26233,26235,26240,26236,26251,26252,26253,26256,26258,26265,26266,26267,26268,26271,26272,26276,26285,26289,26290,26293,26299,26303,26304,26306,26307,26312,26316,26318,26319,26324,26331,26335,26344,26347,26348,26350,26362,26373,26375,26382,26387,26393,26396,26400,26402,26419,26430,26437,26439,26440,26444,26452,26453,26461,26470,26476,26478,26484,26486,26491,26497,26500,26510,26511,26513,26515,26518,26520,26521,26523,26544,26545,26546,26549,26555,26556,26557,26617,26560,26562,26563,26565,26568,26569,26578,26583,26585,26588,26593,26598,26608,26610,26614,26615,26706,26644,26649,26653,26655,26664,26663,26668,26669,26671,26672,26673,26675,26683,26687,26692,26693,26698,26700,26709,26711,26712,26715,26731,26734,26735,26736,26737,26738,26741,26745,26746,26747,26748,26754,26756,26758,26760,26774,26776,26778,26780,26785,26787,26789,26793,26794,26798,26802,26811,26821,26824,26828,26831,26832,26833,26835,26838,26841,26844,26845,26853,26856,26858,26859,26860,26861,26864,26865,26869,26870,26875,26876,26877,26886,26889,26890,26896,26897,26899,26902,26903,26929,26931,26933,26936,26939,26946,26949,26953,26958,26967,26971,26979,26980,26981,26982,26984,26985,26988,26992,26993,26994,27002,27003,27007,27008,27021,27026,27030,27032,27041,27045,27046,27048,27051,27053,27055,27063,27064,27066,27068,27077,27080,27089,27094,27095,27106,27109,27118,27119,27121,27123,27125,27134,27136,27137,27139,27151,27153,27157,27162,27165,27168,27172,27176,27184,27186,27188,27191,27195,27198,27199,27205,27206,27209,27210,27214,27216,27217,27218,27221,27222,27227,27236,27239,27242,27249,27251,27262,27265,27267,27270,27271,27273,27275,27281,27291,27293,27294,27295,27301,27307,27311,27312,27313,27316,27325,27326,27327,27334,27337,27336,27340,27344,27348,27349,27350,27356,27357,27364,27367,27372,27376,27377,27378,27388,27389,27394,27395,27398,27399,27401,27407,27408,27409,27415,27419,27422,27428,27432,27435,27436,27439,27445,27446,27451,27455,27462,27466,27469,27474,27478,27480,27485,27488,27495,27499,27502,27504,27509,27517,27518,27522,27525,27543,27547,27551,27552,27554,27555,27560,27561,27564,27565,27566,27568,27576,27577,27581,27582,27587,27588,27593,27596,27606,27610,27617,27619,27622,27623,27630,27633,27639,27641,27647,27650,27652,27653,27657,27661,27662,27664,27666,27673,27679,27686,27687,27688,27692,27694,27699,27701,27702,27706,27707,27711,27722,27723,27725,27727,27730,27732,27737,27739,27740,27755,27757,27759,27764,27766,27768,27769,27771,27781,27782,27783,27785,27796,27797,27799,27800,27804,27807,27824,27826,27828,27842,27846,27853,27855,27856,27857,27858,27860,27862,27866,27868,27872,27879,27881,27883,27884,27886,27890,27892,27908,27911,27914,27918,27919,27921,27923,27930,27942,27943,27944,27751,27950,27951,27953,27961,27964,27967,27991,27998,27999,28001,28005,28007,28015,28016,28028,28034,28039,28049,28050,28052,28054,28055,28056,28074,28076,28084,28087,28089,28093,28095,28100,28104,28106,28110,28111,28118,28123,28125,28127,28128,28130,28133,28137,28143,28144,28148,28150,28156,28160,28164,28190,28194,28199,28210,28214,28217,28219,28220,28228,28229,28232,28233,28235,28239,28241,28242,28243,28244,28247,28252,28253,28254,28258,28259,28264,28275,28283,28285,28301,28307,28313,28320,28327,28333,28334,28337,28339,28347,28351,28352,28353,28355,28359,28360,28362,28365,28366,28367,28395,28397,28398,28409,28411,28413,28420,28424,28426,28428,28429,28438,28440,28442,28443,28454,28457,28458,28463,28464,28467,28470,28475,28476,28461,28495,28497,28498,28499,28503,28505,28506,28509,28510,28513,28514,28520,28524,28541,28542,28547,28551,28552,28555,28556,28557,28560,28562,28563,28564,28566,28570,28575,28576,28581,28582,28583,28584,28590,28591,28592,28597,28598,28604,28613,28615,28616,28618,28634,28638,28648,28649,28656,28661,28665,28668,28669,28672,28677,28678,28679,28685,28695,28704,28707,28719,28724,28727,28729,28732,28739,28740,28744,28745,28746,28747,28756,28757,28765,28766,28750,28772,28773,28780,28782,28789,28790,28798,28801,28805,28806,28820,28821,28822,28823,28824,28827,28836,28843,28848,28849,28852,28855,28874,28881,28883,28884,28885,28886,28888,28892,28900,28922,28931,28932,28933,28934,28935,28939,28940,28943,28958,28960,28971,28973,28975,28976,28977,28984,28993,28997,28998,28999,29002,29003,29008,29010,29015,29018,29020,29022,29024,29032,29049,29056,29061,29063,29068,29074,29082,29083,29088,29090,29103,29104,29106,29107,29114,29119,29120,29121,29124,29131,29132,29139,29142,29145,29146,29148,29176,29182,29184,29191,29192,29193,29203,29207,29210,29213,29215,29220,29227,29231,29236,29240,29241,29249,29250,29251,29253,29262,29263,29264,29267,29269,29270,29274,29276,29278,29280,29283,29288,29291,29294,29295,29297,29303,29304,29307,29308,29311,29316,29321,29325,29326,29331,29339,29352,29357,29358,29361,29364,29374,29377,29383,29385,29388,29397,29398,29400,29407,29413,29427,29428,29434,29435,29438,29442,29444,29445,29447,29451,29453,29458,29459,29464,29465,29470,29474,29476,29479,29480,29484,29489,29490,29493,29498,29499,29501,29507,29517,29520,29522,29526,29528,29533,29534,29535,29536,29542,29543,29545,29547,29548,29550,29551,29553,29559,29561,29564,29568,29569,29571,29573,29574,29582,29584,29587,29589,29591,29592,29596,29598,29599,29600,29602,29605,29606,29610,29611,29613,29621,29623,29625,29628,29629,29631,29637,29638,29641,29643,29644,29647,29650,29651,29654,29657,29661,29665,29667,29670,29671,29673,29684,29685,29687,29689,29690,29691,29693,29695,29696,29697,29700,29703,29706,29713,29722,29723,29732,29734,29736,29737,29738,29739,29740,29741,29742,29743,29744,29745,29753,29760,29763,29764,29766,29767,29771,29773,29777,29778,29783,29789,29794,29798,29799,29800,29803,29805,29806,29809,29810,29824,29825,29829,29830,29831,29833,29839,29840,29841,29842,29848,29849,29850,29852,29855,29856,29857,29859,29862,29864,29865,29866,29867,29870,29871,29873,29874,29877,29881,29883,29887,29896,29897,29900,29904,29907,29912,29914,29915,29918,29919,29924,29928,29930,29931,29935,29940,29946,29947,29948,29951,29958,29970,29974,29975,29984,29985,29988,29991,29993,29994,29999,30006,30009,30013,30014,30015,30016,30019,30023,30024,30030,30032,30034,30039,30046,30047,30049,30063,30065,30073,30074,30075,30076,30077,30078,30081,30085,30096,30098,30099,30101,30105,30108,30114,30116,30132,30138,30143,30144,30145,30148,30150,30156,30158,30159,30167,30172,30175,30176,30177,30180,30183,30188,30190,30191,30193,30201,30208,30210,30211,30212,30215,30216,30218,30220,30223,30226,30227,30229,30230,30233,30235,30236,30237,30238,30243,30245,30246,30249,30253,30258,30259,30261,30264,30265,30266,30268,30282,30272,30273,30275,30276,30277,30281,30283,30293,30297,30303,30308,30309,30317,30318,30319,30321,30324,30337,30341,30348,30349,30357,30363,30364,30365,30367,30368,30370,30371,30372,30373,30374,30375,30376,30378,30381,30397,30401,30405,30409,30411,30412,30414,30420,30425,30432,30438,30440,30444,30448,30449,30454,30457,30460,30464,30470,30474,30478,30482,30484,30485,30487,30489,30490,30492,30498,30504,30509,30510,30511,30516,30517,30518,30521,30525,30526,30530,30533,30534,30538,30541,30542,30543,30546,30550,30551,30556,30558,30559,30560,30562,30564,30567,30570,30572,30576,30578,30579,30580,30586,30589,30592,30596,30604,30605,30612,30613,30614,30618,30623,30626,30631,30634,30638,30639,30641,30645,30654,30659,30665,30673,30674,30677,30681,30686,30687,30688,30692,30694,30698,30700,30704,30705,30708,30712,30715,30725,30726,30729,30733,30734,30737,30749,30753,30754,30755,30765,30766,30768,30773,30775,30787,30788,30791,30792,30796,30798,30802,30812,30814,30816,30817,30819,30820,30824,30826,30830,30842,30846,30858,30863,30868,30872,30881,30877,30878,30879,30884,30888,30892,30893,30896,30897,30898,30899,30907,30909,30911,30919,30920,30921,30924,30926,30930,30931,30933,30934,30948,30939,30943,30944,30945,30950,30954,30962,30963,30976,30966,30967,30970,30971,30975,30982,30988,30992,31002,31004,31006,31007,31008,31013,31015,31017,31021,31025,31028,31029,31035,31037,31039,31044,31045,31046,31050,31051,31055,31057,31060,31064,31067,31068,31079,31081,31083,31090,31097,31099,31100,31102,31115,31116,31121,31123,31124,31125,31126,31128,31131,31132,31137,31144,31145,31147,31151,31153,31156,31160,31163,31170,31172,31175,31176,31178,31183,31188,31190,31194,31197,31198,31200,31202,31205,31210,31211,31213,31217,31224,31228,31234,31235,31239,31241,31242,31244,31249,31253,31259,31262,31265,31271,31275,31277,31279,31280,31284,31285,31288,31289,31290,31300,31301,31303,31304,31308,31317,31318,31321,31324,31325,31327,31328,31333,31335,31338,31341,31349,31352,31358,31360,31362,31365,31366,31370,31371,31376,31377,31380,31390,31392,31395,31404,31411,31413,31417,31419,31420,31430,31433,31436,31438,31441,31451,31464,31465,31467,31468,31473,31476,31483,31485,31486,31495,31508,31519,31523,31527,31529,31530,31531,31533,31534,31535,31536,31537,31540,31549,31551,31552,31553,31559,31566,31573,31584,31588,31590,31593,31594,31597,31599,31602,31603,31607,31620,31625,31630,31632,31633,31638,31643,31646,31648,31653,31660,31663,31664,31666,31669,31670,31674,31675,31676,31677,31682,31685,31688,31690,31700,31702,31703,31705,31706,31707,31720,31722,31730,31732,31733,31736,31737,31738,31740,31742,31745,31746,31747,31748,31750,31753,31755,31756,31758,31759,31769,31771,31776,31781,31782,31784,31788,31793,31795,31796,31798,31801,31802,31814,31818,31829,31825,31826,31827,31833,31834,31835,31836,31837,31838,31841,31843,31847,31849,31853,31854,31856,31858,31865,31868,31869,31878,31879,31887,31892,31902,31904,31910,31920,31926,31927,31930,31931,31932,31935,31940,31943,31944,31945,31949,31951,31955,31956,31957,31959,31961,31962,31965,31974,31977,31979,31989,32003,32007,32008,32009,32015,32017,32018,32019,32022,32029,32030,32035,32038,32042,32045,32049,32060,32061,32062,32064,32065,32071,32072,32077,32081,32083,32087,32089,32090,32092,32093,32101,32103,32106,32112,32120,32122,32123,32127,32129,32130,32131,32133,32134,32136,32139,32140,32141,32145,32150,32151,32157,32158,32166,32167,32170,32179,32182,32183,32185,32194,32195,32196,32197,32198,32204,32205,32206,32215,32217,32256,32226,32229,32230,32234,32235,32237,32241,32245,32246,32249,32250,32264,32272,32273,32277,32279,32284,32285,32288,32295,32296,32300,32301,32303,32307,32310,32319,32324,32325,32327,32334,32336,32338,32344,32351,32353,32354,32357,32363,32366,32367,32371,32376,32382,32385,32390,32391,32394,32397,32401,32405,32408,32410,32413,32414,32572,32571,32573,32574,32575,32579,32580,32583,32591,32594,32595,32603,32604,32605,32609,32611,32612,32613,32614,32621,32625,32637,32638,32639,32640,32651,32653,32655,32656,32657,32662,32663,32668,32673,32674,32678,32682,32685,32692,32700,32703,32704,32707,32712,32718,32719,32731,32735,32739,32741,32744,32748,32750,32751,32754,32762,32765,32766,32767,32775,32776,32778,32781,32782,32783,32785,32787,32788,32790,32797,32798,32799,32800,32804,32806,32812,32814,32816,32820,32821,32823,32825,32826,32828,32830,32832,32836,32864,32868,32870,32877,32881,32885,32897,32904,32910,32924,32926,32934,32935,32939,32952,32953,32968,32973,32975,32978,32980,32981,32983,32984,32992,33005,33006,33008,33010,33011,33014,33017,33018,33022,33027,33035,33046,33047,33048,33052,33054,33056,33060,33063,33068,33072,33077,33082,33084,33093,33095,33098,33100,33106,33111,33120,33121,33127,33128,33129,33133,33135,33143,33153,33168,33156,33157,33158,33163,33166,33174,33176,33179,33182,33186,33198,33202,33204,33211,33227,33219,33221,33226,33230,33231,33237,33239,33243,33245,33246,33249,33252,33259,33260,33264,33265,33266,33269,33270,33272,33273,33277,33279,33280,33283,33295,33299,33300,33305,33306,33309,33313,33314,33320,33330,33332,33338,33347,33348,33349,33350,33355,33358,33359,33361,33366,33372,33376,33379,33383,33389,33396,33403,33405,33407,33408,33409,33411,33412,33415,33417,33418,33422,33425,33428,33430,33432,33434,33435,33440,33441,33443,33444,33447,33448,33449,33450,33454,33456,33458,33460,33463,33466,33468,33470,33471,33478,33488,33493,33498,33504,33506,33508,33512,33514,33517,33519,33526,33527,33533,33534,33536,33537,33543,33544,33546,33547,33620,33563,33565,33566,33567,33569,33570,33580,33581,33582,33584,33587,33591,33594,33596,33597,33602,33603,33604,33607,33613,33614,33617,33621,33622,33623,33648,33656,33661,33663,33664,33666,33668,33670,33677,33682,33684,33685,33688,33689,33691,33692,33693,33702,33703,33705,33708,33726,33727,33728,33735,33737,33743,33744,33745,33748,33757,33619,33768,33770,33782,33784,33785,33788,33793,33798,33802,33807,33809,33813,33817,33709,33839,33849,33861,33863,33864,33866,33869,33871,33873,33874,33878,33880,33881,33882,33884,33888,33892,33893,33895,33898,33904,33907,33908,33910,33912,33916,33917,33921,33925,33938,33939,33941,33950,33958,33960,33961,33962,33967,33969,33972,33978,33981,33982,33984,33986,33991,33992,33996,33999,34003,34012,34023,34026,34031,34032,34033,34034,34039,34098,34042,34043,34045,34050,34051,34055,34060,34062,34064,34076,34078,34082,34083,34084,34085,34087,34090,34091,34095,34099,34100,34102,34111,34118,34127,34128,34129,34130,34131,34134,34137,34140,34141,34142,34143,34144,34145,34146,34148,34155,34159,34169,34170,34171,34173,34175,34177,34181,34182,34185,34187,34188,34191,34195,34200,34205,34207,34208,34210,34213,34215,34228,34230,34231,34232,34236,34237,34238,34239,34242,34247,34250,34251,34254,34221,34264,34266,34271,34272,34278,34280,34285,34291,34294,34300,34303,34304,34308,34309,34317,34318,34320,34321,34322,34328,34329,34331,34334,34337,34343,34345,34358,34360,34362,34364,34365,34368,34370,34374,34386,34387,34390,34391,34392,34393,34397,34400,34401,34402,34403,34404,34409,34412,34415,34421,34422,34423,34426,34445,34449,34454,34456,34458,34460,34465,34470,34471,34472,34477,34481,34483,34484,34485,34487,34488,34489,34495,34496,34497,34499,34501,34513,34514,34517,34519,34522,34524,34528,34531,34533,34535,34440,34554,34556,34557,34564,34565,34567,34571,34574,34575,34576,34579,34580,34585,34590,34591,34593,34595,34600,34606,34607,34609,34610,34617,34618,34620,34621,34622,34624,34627,34629,34637,34648,34653,34657,34660,34661,34671,34673,34674,34683,34691,34692,34693,34694,34695,34696,34697,34699,34700,34704,34707,34709,34711,34712,34713,34718,34720,34723,34727,34732,34733,34734,34737,34741,34750,34751,34753,34760,34761,34762,34766,34773,34774,34777,34778,34780,34783,34786,34787,34788,34794,34795,34797,34801,34803,34808,34810,34815,34817,34819,34822,34825,34826,34827,34832,34841,34834,34835,34836,34840,34842,34843,34844,34846,34847,34856,34861,34862,34864,34866,34869,34874,34876,34881,34883,34885,34888,34889,34890,34891,34894,34897,34901,34902,34904,34906,34908,34911,34912,34916,34921,34929,34937,34939,34944,34968,34970,34971,34972,34975,34976,34984,34986,35002,35005,35006,35008,35018,35019,35020,35021,35022,35025,35026,35027,35035,35038,35047,35055,35056,35057,35061,35063,35073,35078,35085,35086,35087,35093,35094,35096,35097,35098,35100,35104,35110,35111,35112,35120,35121,35122,35125,35129,35130,35134,35136,35138,35141,35142,35145,35151,35154,35159,35162,35163,35164,35169,35170,35171,35179,35182,35184,35187,35189,35194,35195,35196,35197,35209,35213,35216,35220,35221,35227,35228,35231,35232,35237,35248,35252,35253,35254,35255,35260,35284,35285,35286,35287,35288,35301,35305,35307,35309,35313,35315,35318,35321,35325,35327,35332,35333,35335,35343,35345,35346,35348,35349,35358,35360,35362,35364,35366,35371,35372,35375,35381,35383,35389,35390,35392,35395,35397,35399,35401,35405,35406,35411,35414,35415,35416,35420,35421,35425,35429,35431,35445,35446,35447,35449,35450,35451,35454,35455,35456,35459,35462,35467,35471,35472,35474,35478,35479,35481,35487,35495,35497,35502,35503,35507,35510,35511,35515,35518,35523,35526,35528,35529,35530,35537,35539,35540,35541,35543,35549,35551,35564,35568,35572,35573,35574,35580,35583,35589,35590,35595,35601,35612,35614,35615,35594,35629,35632,35639,35644,35650,35651,35652,35653,35654,35656,35666,35667,35668,35673,35661,35678,35683,35693,35702,35704,35705,35708,35710,35713,35716,35717,35723,35725,35727,35732,35733,35740,35742,35743,35896,35897,35901,35902,35909,35911,35913,35915,35919,35921,35923,35924,35927,35928,35931,35933,35929,35939,35940,35942,35944,35945,35949,35955,35957,35958,35963,35966,35974,35975,35979,35984,35986,35987,35993,35995,35996,36004,36025,36026,36037,36038,36041,36043,36047,36054,36053,36057,36061,36065,36072,36076,36079,36080,36082,36085,36087,36088,36094,36095,36097,36099,36105,36114,36119,36123,36197,36201,36204,36206,36223,36226,36228,36232,36237,36240,36241,36245,36254,36255,36256,36262,36267,36268,36271,36274,36277,36279,36281,36283,36288,36293,36294,36295,36296,36298,36302,36305,36308,36309,36311,36313,36324,36325,36327,36332,36336,36284,36337,36338,36340,36349,36353,36356,36357,36358,36363,36369,36372,36374,36384,36385,36386,36387,36390,36391,36401,36403,36406,36407,36408,36409,36413,36416,36417,36427,36429,36430,36431,36436,36443,36444,36445,36446,36449,36450,36457,36460,36461,36463,36464,36465,36473,36474,36475,36482,36483,36489,36496,36498,36501,36506,36507,36509,36510,36514,36519,36521,36525,36526,36531,36533,36538,36539,36544,36545,36547,36548,36551,36559,36561,36564,36572,36584,36590,36592,36593,36599,36601,36602,36589,36608,36610,36615,36616,36623,36624,36630,36631,36632,36638,36640,36641,36643,36645,36647,36648,36652,36653,36654,36660,36661,36662,36663,36666,36672,36673,36675,36679,36687,36689,36690,36691,36692,36693,36696,36701,36702,36709,36765,36768,36769,36772,36773,36774,36789,36790,36792,36798,36800,36801,36806,36810,36811,36813,36816,36818,36819,36821,36832,36835,36836,36840,36846,36849,36853,36854,36859,36862,36866,36868,36872,36876,36888,36891,36904,36905,36911,36906,36908,36909,36915,36916,36919,36927,36931,36932,36940,36955,36957,36962,36966,36967,36972,36976,36980,36985,36997,37000,37003,37004,37006,37008,37013,37015,37016,37017,37019,37024,37025,37026,37029,37040,37042,37043,37044,37046,37053,37068,37054,37059,37060,37061,37063,37064,37077,37079,37080,37081,37084,37085,37087,37093,37074,37110,37099,37103,37104,37108,37118,37119,37120,37124,37125,37126,37128,37133,37136,37140,37142,37143,37144,37146,37148,37150,37152,37157,37154,37155,37159,37161,37166,37167,37169,37172,37174,37175,37177,37178,37180,37181,37187,37191,37192,37199,37203,37207,37209,37210,37211,37217,37220,37223,37229,37236,37241,37242,37243,37249,37251,37253,37254,37258,37262,37265,37267,37268,37269,37272,37278,37281,37286,37288,37292,37293,37294,37296,37297,37298,37299,37302,37307,37308,37309,37311,37314,37315,37317,37331,37332,37335,37337,37338,37342,37348,37349,37353,37354,37356,37357,37358,37359,37360,37361,37367,37369,37371,37373,37376,37377,37380,37381,37382,37383,37385,37386,37388,37392,37394,37395,37398,37400,37404,37405,37411,37412,37413,37414,37416,37422,37423,37424,37427,37429,37430,37432,37433,37434,37436,37438,37440,37442,37443,37446,37447,37450,37453,37454,37455,37457,37464,37465,37468,37469,37472,37473,37477,37479,37480,37481,37486,37487,37488,37493,37494,37495,37496,37497,37499,37500,37501,37503,37512,37513,37514,37517,37518,37522,37527,37529,37535,37536,37540,37541,37543,37544,37547,37551,37554,37558,37560,37562,37563,37564,37565,37567,37568,37569,37570,37571,37573,37574,37575,37576,37579,37580,37581,37582,37584,37587,37589,37591,37592,37593,37596,37597,37599,37600,37601,37603,37605,37607,37608,37612,37614,37616,37625,37627,37631,37632,37634,37640,37645,37649,37652,37653,37660,37661,37662,37663,37665,37668,37669,37671,37673,37674,37683,37684,37686,37687,37703,37704,37705,37712,37713,37714,37717,37719,37720,37722,37726,37732,37733,37735,37737,37738,37741,37743,37744,37745,37747,37748,37750,37754,37757,37759,37760,37761,37762,37768,37770,37771,37773,37775,37778,37781,37784,37787,37790,37793,37795,37796,37798,37800,37803,37812,37813,37814,37818,37801,37825,37828,37829,37830,37831,37833,37834,37835,37836,37837,37843,37849,37852,37854,37855,37858,37862,37863,37881,37879,37880,37882,37883,37885,37889,37890,37892,37896,37897,37901,37902,37903,37909,37910,37911,37919,37934,37935,37937,37938,37939,37940,37947,37951,37949,37955,37957,37960,37962,37964,37973,37977,37980,37983,37985,37987,37992,37995,37997,37998,37999,38001,38002,38020,38019,38264,38265,38270,38276,38280,38284,38285,38286,38301,38302,38303,38305,38310,38313,38315,38316,38324,38326,38330,38333,38335,38342,38344,38345,38347,38352,38353,38354,38355,38361,38362,38365,38366,38367,38368,38372,38374,38429,38430,38434,38436,38437,38438,38444,38449,38451,38455,38456,38457,38458,38460,38461,38465,38482,38484,38486,38487,38488,38497,38510,38516,38523,38524,38526,38527,38529,38530,38531,38532,38537,38545,38550,38554,38557,38559,38564,38565,38566,38569,38574,38575,38579,38586,38602,38610,23986,38616,38618,38621,38622,38623,38633,38639,38641,38650,38658,38659,38661,38665,38682,38683,38685,38689,38690,38691,38696,38705,38707,38721,38723,38730,38734,38735,38741,38743,38744,38746,38747,38755,38759,38762,38766,38771,38774,38775,38776,38779,38781,38783,38784,38793,38805,38806,38807,38809,38810,38814,38815,38818,38828,38830,38833,38834,38837,38838,38840,38841,38842,38844,38846,38847,38849,38852,38853,38855,38857,38858,38860,38861,38862,38864,38865,38868,38871,38872,38873,38877,38878,38880,38875,38881,38884,38895,38897,38900,38903,38904,38906,38919,38922,38937,38925,38926,38932,38934,38940,38942,38944,38947,38950,38955,38958,38959,38960,38962,38963,38965,38949,38974,38980,38983,38986,38993,38994,38995,38998,38999,39001,39002,39010,39011,39013,39014,39018,39020,39083,39085,39086,39088,39092,39095,39096,39098,39099,39103,39106,39109,39112,39116,39137,39139,39141,39142,39143,39146,39155,39158,39170,39175,39176,39185,39189,39190,39191,39194,39195,39196,39199,39202,39206,39207,39211,39217,39218,39219,39220,39221,39225,39226,39227,39228,39232,39233,39238,39239,39240,39245,39246,39252,39256,39257,39259,39260,39262,39263,39264,39323,39325,39327,39334,39344,39345,39346,39349,39353,39354,39357,39359,39363,39369,39379,39380,39385,39386,39388,39390,39399,39402,39403,39404,39408,39412,39413,39417,39421,39422,39426,39427,39428,39435,39436,39440,39441,39446,39454,39456,39458,39459,39460,39463,39469,39470,39475,39477,39478,39480,39495,39489,39492,39498,39499,39500,39502,39505,39508,39510,39517,39594,39596,39598,39599,39602,39604,39605,39606,39609,39611,39614,39615,39617,39619,39622,39624,39630,39632,39634,39637,39638,39639,39643,39644,39648,39652,39653,39655,39657,39660,39666,39667,39669,39673,39674,39677,39679,39680,39681,39682,39683,39684,39685,39688,39689,39691,39692,39693,39694,39696,39698,39702,39705,39707,39708,39712,39718,39723,39725,39731,39732,39733,39735,39737,39738,39741,39752,39755,39756,39765,39766,39767,39771,39774,39777,39779,39781,39782,39784,39786,39787,39788,39789,39790,39795,39797,39799,39800,39801,39807,39808,39812,39813,39814,39815,39817,39818,39819,39821,39823,39824,39828,39834,39837,39838,39846,39847,39849,39852,39856,39857,39858,39863,39864,39867,39868,39870,39871,39873,39879,39880,39886,39888,39895,39896,39901,39903,39909,39911,39914,39915,39919,39923,39927,39928,39929,39930,39933,39935,39936,39938,39947,39951,39953,39958,39960,39961,39962,39964,39966,39970,39971,39974,39975,39976,39977,39978,39985,39989,39990,39991,39997,40001,40003,40004,40005,40009,40010,40014,40015,40016,40019,40020,40022,40024,40027,40029,40030,40031,40035,40041,40042,40028,40043,40040,40046,40048,40050,40053,40055,40059,40166,40178,40183,40185,40203,40194,40209,40215,40216,40220,40221,40222,40239,40240,40242,40243,40244,40250,40252,40261,40253,40258,40259,40263,40266,40275,40276,40287,40291,40290,40293,40297,40298,40299,40304,40310,40311,40315,40316,40318,40323,40324,40326,40330,40333,40334,40338,40339,40341,40342,40343,40344,40353,40362,40364,40366,40369,40373,40377,40380,40383,40387,40391,40393,40394,40404,40405,40406,40407,40410,40414,40415,40416,40421,40423,40425,40427,40430,40432,40435,40436,40446,40458,40450,40455,40462,40464,40465,40466,40469,40470,40473,40476,40477,40570,40571,40572,40576,40578,40579,40580,40581,40583,40590,40591,40598,40600,40603,40606,40612,40616,40620,40622,40623,40624,40627,40628,40629,40646,40648,40651,40661,40671,40676,40679,40684,40685,40686,40688,40689,40690,40693,40696,40703,40706,40707,40713,40719,40720,40721,40722,40724,40726,40727,40729,40730,40731,40735,40738,40742,40746,40747,40751,40753,40754,40756,40759,40761,40762,40764,40765,40767,40769,40771,40772,40773,40774,40775,40787,40789,40790,40791,40792,40794,40797,40798,40808,40809,40813,40814,40815,40816,40817,40819,40821,40826,40829,40847,40848,40849,40850,40852,40854,40855,40862,40865,40866,40867,40869,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null}; -struct euc_jp_decoder : jis_decoder +struct euc_jp_decoder final : jis_decoder { int m_lead = 0; bool m_jis0212 = false; @@ -710,7 +714,7 @@ decoder::result euc_jp_decoder::handler(inout string& input, inout int& index, o if (b == EOF && m_lead == 0) return result_finished; - // 3. + // 3. if (m_lead == 0x8E && b >= 0xA1 && b <= 0xDF) { m_lead = 0; @@ -718,7 +722,7 @@ decoder::result euc_jp_decoder::handler(inout string& input, inout int& index, o return result_codepoint; } - // 4. + // 4. if (m_lead == 0x8F && b >= 0xA1 && b <= 0xFE) { m_jis0212 = true; @@ -735,12 +739,12 @@ decoder::result euc_jp_decoder::handler(inout string& input, inout int& index, o // 1. int code_point = null; - // 2. + // 2. if ((lead >= 0xA1 && lead <= 0xFE) && (b >= 0xA1 && b <= 0xFE)) { int pointer = (lead - 0xA1) * 94 + b - 0xA1; - code_point = m_jis0212 ? - index_code_point(pointer, m_jis0212_index) : + code_point = m_jis0212 ? + index_code_point(pointer, m_jis0212_index) : index_code_point(pointer, m_jis0208_index); } @@ -768,7 +772,7 @@ decoder::result euc_jp_decoder::handler(inout string& input, inout int& index, o return result_codepoint; } - // 7. + // 7. if (b == 0x8E || b == 0x8F || (b >= 0xA1 && b <= 0xFE)) { m_lead = b; @@ -782,7 +786,7 @@ decoder::result euc_jp_decoder::handler(inout string& input, inout int& index, o ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #undef NULL -struct iso_2022_jp_decoder : jis_decoder +struct iso_2022_jp_decoder final : jis_decoder { enum state { @@ -957,7 +961,7 @@ decoder::result iso_2022_jp_decoder::handler(inout string& input, inout int& ind m_output = false; m_state = m_output_state; return result_error; - + case ESCAPE: { // 1. @@ -998,7 +1002,7 @@ decoder::result iso_2022_jp_decoder::handler(inout string& input, inout int& ind ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct shift_jis_decoder : jis_decoder +struct shift_jis_decoder final : jis_decoder { int m_lead = 0; @@ -1021,7 +1025,7 @@ decoder::result shift_jis_decoder::handler(inout string& input, inout int& index if (b == EOF && m_lead == 0) return result_finished; - // 3. + // 3. if (m_lead != 0) { int lead = m_lead; @@ -1032,7 +1036,7 @@ decoder::result shift_jis_decoder::handler(inout string& input, inout int& index int offset = b < 0x7F ? 0x40 : 0x41; int lead_offset = lead < 0xA0 ? 0x81 : 0xC1; - // 3. + // 3. if ((b >= 0x40 && b <= 0x7E) || (b >= 0x80 && b <= 0xFC)) pointer = (lead - lead_offset) * 188 + b - offset; @@ -1074,7 +1078,7 @@ decoder::result shift_jis_decoder::handler(inout string& input, inout int& index return result_codepoint; } - // 6. + // 6. if ((b >= 0x81 && b <= 0x9F) || (b >= 0xE0 && b <= 0xFC)) { m_lead = b; @@ -1087,7 +1091,7 @@ decoder::result shift_jis_decoder::handler(inout string& input, inout int& index ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct euc_kr_decoder : decoder +struct euc_kr_decoder final : decoder { int m_lead = 0; @@ -1121,23 +1125,23 @@ decoder::result euc_kr_decoder::handler(inout string& input, inout int& index, o int lead = m_lead; int pointer = null; m_lead = 0; - + // 1. if (b >= 0x41 && b <= 0xFE) pointer = (lead - 0x81) * 190 + (b - 0x41); - + // 2. int code_point = pointer != null ? index_code_point(pointer, m_index) : null; - + // 3. if (code_point != null) { *ch = code_point; return result_codepoint; } - + // 4. If byte is an ASCII byte, restore byte to ioQueue. if (b >= 0 && b <= 0x7F) index--; - + // 5. return result_error; } @@ -1162,7 +1166,7 @@ decoder::result euc_kr_decoder::handler(inout string& input, inout int& index, o ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct replacement_decoder : decoder +struct replacement_decoder final : decoder { bool error_returned = false; @@ -1189,7 +1193,7 @@ decoder::result replacement_decoder::handler(inout string& input, inout int& ind ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct utf_16_decoder : decoder +struct utf_16_decoder final : decoder { int m_lead_byte = null; int m_lead_surrogate = null; @@ -1240,14 +1244,14 @@ decoder::result utf_16_decoder::handler(inout string& input, inout int& index, o *ch = 0x10000 + ((lead_surrogate - 0xD800) << 10) + (code_unit - 0xDC00); return result_codepoint; } - + // 2,3. char b1 = char(code_unit >> 8); char b2 = char(code_unit & 0xFF); - + // 4. Let bytes be two bytes whose values are byte1 and byte2, if is UTF-16BE decoder is true, and byte2 and byte1 otherwise. string bytes = m_utf_16be ? string{b1, b2} : string{b2, b1}; - + // 5. Restore bytes to ioQueue and return error. input.insert(index, bytes); return result_error; @@ -1271,7 +1275,7 @@ decoder::result utf_16_decoder::handler(inout string& input, inout int& index, o ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct x_user_defined_decoder : decoder +struct x_user_defined_decoder final : decoder { result handler(string& input, int& index, int ch[2]) override; }; @@ -1305,7 +1309,7 @@ decoder::ptr get_decoder(encoding _encoding) { case encoding::utf_8: return make_shared<utf_8_decoder>(); - + case encoding::gbk: // https://encoding.spec.whatwg.org/#gbk-decoder case encoding::gb18030: return make_shared<gb18030_decoder>(); @@ -1355,283 +1359,283 @@ struct { encoding coding; } labels[] = { - "unicode-1-1-utf-8", encoding::utf_8, - "unicode11utf8", encoding::utf_8, - "unicode20utf8", encoding::utf_8, - "utf-8", encoding::utf_8, - "utf8", encoding::utf_8, - "x-unicode20utf8", encoding::utf_8, - - "866", encoding::ibm866, - "cp866", encoding::ibm866, - "csibm866", encoding::ibm866, - "ibm866", encoding::ibm866, - - "csisolatin2", encoding::iso_8859_2, - "iso-8859-2", encoding::iso_8859_2, - "iso-ir-101", encoding::iso_8859_2, - "iso8859-2", encoding::iso_8859_2, - "iso88592", encoding::iso_8859_2, - "iso_8859-2", encoding::iso_8859_2, - "iso_8859-2:1987", encoding::iso_8859_2, - "l2", encoding::iso_8859_2, - "latin2", encoding::iso_8859_2, - - "csisolatin3", encoding::iso_8859_3, - "iso-8859-3", encoding::iso_8859_3, - "iso-ir-109", encoding::iso_8859_3, - "iso8859-3", encoding::iso_8859_3, - "iso88593", encoding::iso_8859_3, - "iso_8859-3", encoding::iso_8859_3, - "iso_8859-3:1988", encoding::iso_8859_3, - "l3", encoding::iso_8859_3, - "latin3", encoding::iso_8859_3, - - "csisolatin4", encoding::iso_8859_4, - "iso-8859-4", encoding::iso_8859_4, - "iso-ir-110", encoding::iso_8859_4, - "iso8859-4", encoding::iso_8859_4, - "iso88594", encoding::iso_8859_4, - "iso_8859-4", encoding::iso_8859_4, - "iso_8859-4:1988", encoding::iso_8859_4, - "l4", encoding::iso_8859_4, - "latin4", encoding::iso_8859_4, - - "csisolatincyrillic", encoding::iso_8859_5, - "cyrillic", encoding::iso_8859_5, - "iso-8859-5", encoding::iso_8859_5, - "iso-ir-144", encoding::iso_8859_5, - "iso8859-5", encoding::iso_8859_5, - "iso88595", encoding::iso_8859_5, - "iso_8859-5", encoding::iso_8859_5, - "iso_8859-5:1988", encoding::iso_8859_5, - - "arabic", encoding::iso_8859_6, - "asmo-708", encoding::iso_8859_6, - "csiso88596e", encoding::iso_8859_6, - "csiso88596i", encoding::iso_8859_6, - "csisolatinarabic", encoding::iso_8859_6, - "ecma-114", encoding::iso_8859_6, - "iso-8859-6", encoding::iso_8859_6, - "iso-8859-6-e", encoding::iso_8859_6, - "iso-8859-6-i", encoding::iso_8859_6, - "iso-ir-127", encoding::iso_8859_6, - "iso8859-6", encoding::iso_8859_6, - "iso88596", encoding::iso_8859_6, - "iso_8859-6", encoding::iso_8859_6, - "iso_8859-6:1987", encoding::iso_8859_6, - - "csisolatingreek", encoding::iso_8859_7, - "ecma-118", encoding::iso_8859_7, - "elot_928", encoding::iso_8859_7, - "greek", encoding::iso_8859_7, - "greek8", encoding::iso_8859_7, - "iso-8859-7", encoding::iso_8859_7, - "iso-ir-126", encoding::iso_8859_7, - "iso8859-7", encoding::iso_8859_7, - "iso88597", encoding::iso_8859_7, - "iso_8859-7", encoding::iso_8859_7, - "iso_8859-7:1987", encoding::iso_8859_7, - "sun_eu_greek", encoding::iso_8859_7, - - "csiso88598e", encoding::iso_8859_8, - "csisolatinhebrew", encoding::iso_8859_8, - "hebrew", encoding::iso_8859_8, - "iso-8859-8", encoding::iso_8859_8, - "iso-8859-8-e", encoding::iso_8859_8, - "iso-ir-138", encoding::iso_8859_8, - "iso8859-8", encoding::iso_8859_8, - "iso88598", encoding::iso_8859_8, - "iso_8859-8", encoding::iso_8859_8, - "iso_8859-8:1988", encoding::iso_8859_8, - "visual", encoding::iso_8859_8, - - "csiso88598i", encoding::iso_8859_8_i, - "iso-8859-8-i", encoding::iso_8859_8_i, - "logical", encoding::iso_8859_8_i, - - "csisolatin6", encoding::iso_8859_10, - "iso-8859-10", encoding::iso_8859_10, - "iso-ir-157", encoding::iso_8859_10, - "iso8859-10", encoding::iso_8859_10, - "iso885910", encoding::iso_8859_10, - "l6", encoding::iso_8859_10, - "latin6", encoding::iso_8859_10, - - "iso-8859-13", encoding::iso_8859_13, - "iso8859-13", encoding::iso_8859_13, - "iso885913", encoding::iso_8859_13, - - "iso-8859-14", encoding::iso_8859_14, - "iso8859-14", encoding::iso_8859_14, - "iso885914", encoding::iso_8859_14, - - "csisolatin9", encoding::iso_8859_15, - "iso-8859-15", encoding::iso_8859_15, - "iso8859-15", encoding::iso_8859_15, - "iso885915", encoding::iso_8859_15, - "iso_8859-15", encoding::iso_8859_15, - "l9", encoding::iso_8859_15, - - "iso-8859-16", encoding::iso_8859_16, - - "cskoi8r", encoding::koi8_r, - "koi", encoding::koi8_r, - "koi8", encoding::koi8_r, - "koi8-r", encoding::koi8_r, - "koi8_r", encoding::koi8_r, - - "koi8-ru", encoding::koi8_u, - "koi8-u", encoding::koi8_u, - - "csmacintosh", encoding::macintosh, - "mac", encoding::macintosh, - "macintosh", encoding::macintosh, - "x-mac-roman", encoding::macintosh, - - "dos-874", encoding::windows_874, - "iso-8859-11", encoding::windows_874, - "iso8859-11", encoding::windows_874, - "iso885911", encoding::windows_874, - "tis-620", encoding::windows_874, - "windows-874", encoding::windows_874, - - "cp1250", encoding::windows_1250, - "windows-1250", encoding::windows_1250, - "x-cp1250", encoding::windows_1250, - - "cp1251", encoding::windows_1251, - "windows-1251", encoding::windows_1251, - "x-cp1251", encoding::windows_1251, - - "ansi_x3.4-1968", encoding::windows_1252, - "ascii", encoding::windows_1252, - "cp1252", encoding::windows_1252, - "cp819", encoding::windows_1252, - "csisolatin1", encoding::windows_1252, - "ibm819", encoding::windows_1252, - "iso-8859-1", encoding::windows_1252, - "iso-ir-100", encoding::windows_1252, - "iso8859-1", encoding::windows_1252, - "iso88591", encoding::windows_1252, - "iso_8859-1", encoding::windows_1252, - "iso_8859-1:1987", encoding::windows_1252, - "l1", encoding::windows_1252, - "latin1", encoding::windows_1252, - "us-ascii", encoding::windows_1252, - "windows-1252", encoding::windows_1252, - "x-cp1252", encoding::windows_1252, - - "cp1253", encoding::windows_1253, - "windows-1253", encoding::windows_1253, - "x-cp1253", encoding::windows_1253, - - "cp1254", encoding::windows_1254, - "csisolatin5", encoding::windows_1254, - "iso-8859-9", encoding::windows_1254, - "iso-ir-148", encoding::windows_1254, - "iso8859-9", encoding::windows_1254, - "iso88599", encoding::windows_1254, - "iso_8859-9", encoding::windows_1254, - "iso_8859-9:1989", encoding::windows_1254, - "l5", encoding::windows_1254, - "latin5", encoding::windows_1254, - "windows-1254", encoding::windows_1254, - "x-cp1254", encoding::windows_1254, - - "cp1255", encoding::windows_1255, - "windows-1255", encoding::windows_1255, - "x-cp1255", encoding::windows_1255, - - "cp1256", encoding::windows_1256, - "windows-1256", encoding::windows_1256, - "x-cp1256", encoding::windows_1256, - - "cp1257", encoding::windows_1257, - "windows-1257", encoding::windows_1257, - "x-cp1257", encoding::windows_1257, - - "cp1258", encoding::windows_1258, - "windows-1258", encoding::windows_1258, - "x-cp1258", encoding::windows_1258, - - "x-mac-cyrillic", encoding::x_mac_cyrillic, - "x-mac-ukrainian", encoding::x_mac_cyrillic, - - "chinese", encoding::gbk, - "csgb2312", encoding::gbk, - "csiso58gb231280", encoding::gbk, - "gb2312", encoding::gbk, - "gb_2312", encoding::gbk, - "gb_2312-80", encoding::gbk, - "gbk", encoding::gbk, - "iso-ir-58", encoding::gbk, - "x-gbk", encoding::gbk, - - "gb18030", encoding::gb18030, - - "big5", encoding::big5, - "big5-hkscs", encoding::big5, - "cn-big5", encoding::big5, - "csbig5", encoding::big5, - "x-x-big5", encoding::big5, - - "cseucpkdfmtjapanese", encoding::euc_jp, - "euc-jp", encoding::euc_jp, - "x-euc-jp", encoding::euc_jp, - - "csiso2022jp", encoding::iso_2022_jp, - "iso-2022-jp", encoding::iso_2022_jp, - - "csshiftjis", encoding::shift_jis, - "ms932", encoding::shift_jis, - "ms_kanji", encoding::shift_jis, - "shift-jis", encoding::shift_jis, - "shift_jis", encoding::shift_jis, - "sjis", encoding::shift_jis, - "windows-31j", encoding::shift_jis, - "x-sjis", encoding::shift_jis, - - "cseuckr", encoding::euc_kr, - "csksc56011987", encoding::euc_kr, - "euc-kr", encoding::euc_kr, - "iso-ir-149", encoding::euc_kr, - "korean", encoding::euc_kr, - "ks_c_5601-1987", encoding::euc_kr, - "ks_c_5601-1989", encoding::euc_kr, - "ksc5601", encoding::euc_kr, - "ksc_5601", encoding::euc_kr, - "windows-949", encoding::euc_kr, - - "csiso2022kr", encoding::replacement, - "hz-gb-2312", encoding::replacement, - "iso-2022-cn", encoding::replacement, - "iso-2022-cn-ext", encoding::replacement, - "iso-2022-kr", encoding::replacement, - "replacement", encoding::replacement, - - "unicodefffe", encoding::utf_16be, - "utf-16be", encoding::utf_16be, - - "csunicode", encoding::utf_16le, - "iso-10646-ucs-2", encoding::utf_16le, - "ucs-2", encoding::utf_16le, - "unicode", encoding::utf_16le, - "unicodefeff", encoding::utf_16le, - "utf-16", encoding::utf_16le, - "utf-16le", encoding::utf_16le, - - "x-user-defined", encoding::x_user_defined + { "unicode-1-1-utf-8", encoding::utf_8 }, + { "unicode11utf8", encoding::utf_8 }, + { "unicode20utf8", encoding::utf_8 }, + { "utf-8", encoding::utf_8 }, + { "utf8", encoding::utf_8 }, + { "x-unicode20utf8", encoding::utf_8 }, + + { "866", encoding::ibm866 }, + { "cp866", encoding::ibm866 }, + { "csibm866", encoding::ibm866 }, + { "ibm866", encoding::ibm866 }, + + { "csisolatin2", encoding::iso_8859_2 }, + { "iso-8859-2", encoding::iso_8859_2 }, + { "iso-ir-101", encoding::iso_8859_2 }, + { "iso8859-2", encoding::iso_8859_2 }, + { "iso88592", encoding::iso_8859_2 }, + { "iso_8859-2", encoding::iso_8859_2 }, + { "iso_8859-2:1987", encoding::iso_8859_2 }, + { "l2", encoding::iso_8859_2 }, + { "latin2", encoding::iso_8859_2 }, + + { "csisolatin3", encoding::iso_8859_3 }, + { "iso-8859-3", encoding::iso_8859_3 }, + { "iso-ir-109", encoding::iso_8859_3 }, + { "iso8859-3", encoding::iso_8859_3 }, + { "iso88593", encoding::iso_8859_3 }, + { "iso_8859-3", encoding::iso_8859_3 }, + { "iso_8859-3:1988", encoding::iso_8859_3 }, + { "l3", encoding::iso_8859_3 }, + { "latin3", encoding::iso_8859_3 }, + + { "csisolatin4", encoding::iso_8859_4 }, + { "iso-8859-4", encoding::iso_8859_4 }, + { "iso-ir-110", encoding::iso_8859_4 }, + { "iso8859-4", encoding::iso_8859_4 }, + { "iso88594", encoding::iso_8859_4 }, + { "iso_8859-4", encoding::iso_8859_4 }, + { "iso_8859-4:1988", encoding::iso_8859_4 }, + { "l4", encoding::iso_8859_4 }, + { "latin4", encoding::iso_8859_4 }, + + { "csisolatincyrillic", encoding::iso_8859_5 }, + { "cyrillic", encoding::iso_8859_5 }, + { "iso-8859-5", encoding::iso_8859_5 }, + { "iso-ir-144", encoding::iso_8859_5 }, + { "iso8859-5", encoding::iso_8859_5 }, + { "iso88595", encoding::iso_8859_5 }, + { "iso_8859-5", encoding::iso_8859_5 }, + { "iso_8859-5:1988", encoding::iso_8859_5 }, + + { "arabic", encoding::iso_8859_6 }, + { "asmo-708", encoding::iso_8859_6 }, + { "csiso88596e", encoding::iso_8859_6 }, + { "csiso88596i", encoding::iso_8859_6 }, + { "csisolatinarabic", encoding::iso_8859_6 }, + { "ecma-114", encoding::iso_8859_6 }, + { "iso-8859-6", encoding::iso_8859_6 }, + { "iso-8859-6-e", encoding::iso_8859_6 }, + { "iso-8859-6-i", encoding::iso_8859_6 }, + { "iso-ir-127", encoding::iso_8859_6 }, + { "iso8859-6", encoding::iso_8859_6 }, + { "iso88596", encoding::iso_8859_6 }, + { "iso_8859-6", encoding::iso_8859_6 }, + { "iso_8859-6:1987", encoding::iso_8859_6 }, + + { "csisolatingreek", encoding::iso_8859_7 }, + { "ecma-118", encoding::iso_8859_7 }, + { "elot_928", encoding::iso_8859_7 }, + { "greek", encoding::iso_8859_7 }, + { "greek8", encoding::iso_8859_7 }, + { "iso-8859-7", encoding::iso_8859_7 }, + { "iso-ir-126", encoding::iso_8859_7 }, + { "iso8859-7", encoding::iso_8859_7 }, + { "iso88597", encoding::iso_8859_7 }, + { "iso_8859-7", encoding::iso_8859_7 }, + { "iso_8859-7:1987", encoding::iso_8859_7 }, + { "sun_eu_greek", encoding::iso_8859_7 }, + + { "csiso88598e", encoding::iso_8859_8 }, + { "csisolatinhebrew", encoding::iso_8859_8 }, + { "hebrew", encoding::iso_8859_8 }, + { "iso-8859-8", encoding::iso_8859_8 }, + { "iso-8859-8-e", encoding::iso_8859_8 }, + { "iso-ir-138", encoding::iso_8859_8 }, + { "iso8859-8", encoding::iso_8859_8 }, + { "iso88598", encoding::iso_8859_8 }, + { "iso_8859-8", encoding::iso_8859_8 }, + { "iso_8859-8:1988", encoding::iso_8859_8 }, + { "visual", encoding::iso_8859_8 }, + + { "csiso88598i", encoding::iso_8859_8_i }, + { "iso-8859-8-i", encoding::iso_8859_8_i }, + { "logical", encoding::iso_8859_8_i }, + + { "csisolatin6", encoding::iso_8859_10 }, + { "iso-8859-10", encoding::iso_8859_10 }, + { "iso-ir-157", encoding::iso_8859_10 }, + { "iso8859-10", encoding::iso_8859_10 }, + { "iso885910", encoding::iso_8859_10 }, + { "l6", encoding::iso_8859_10 }, + { "latin6", encoding::iso_8859_10 }, + + { "iso-8859-13", encoding::iso_8859_13 }, + { "iso8859-13", encoding::iso_8859_13 }, + { "iso885913", encoding::iso_8859_13 }, + + { "iso-8859-14", encoding::iso_8859_14 }, + { "iso8859-14", encoding::iso_8859_14 }, + { "iso885914", encoding::iso_8859_14 }, + + { "csisolatin9", encoding::iso_8859_15 }, + { "iso-8859-15", encoding::iso_8859_15 }, + { "iso8859-15", encoding::iso_8859_15 }, + { "iso885915", encoding::iso_8859_15 }, + { "iso_8859-15", encoding::iso_8859_15 }, + { "l9", encoding::iso_8859_15 }, + + { "iso-8859-16", encoding::iso_8859_16 }, + + { "cskoi8r", encoding::koi8_r }, + { "koi", encoding::koi8_r }, + { "koi8", encoding::koi8_r }, + { "koi8-r", encoding::koi8_r }, + { "koi8_r", encoding::koi8_r }, + + { "koi8-ru", encoding::koi8_u }, + { "koi8-u", encoding::koi8_u }, + + { "csmacintosh", encoding::macintosh }, + { "mac", encoding::macintosh }, + { "macintosh", encoding::macintosh }, + { "x-mac-roman", encoding::macintosh }, + + { "dos-874", encoding::windows_874 }, + { "iso-8859-11", encoding::windows_874 }, + { "iso8859-11", encoding::windows_874 }, + { "iso885911", encoding::windows_874 }, + { "tis-620", encoding::windows_874 }, + { "windows-874", encoding::windows_874 }, + + { "cp1250", encoding::windows_1250 }, + { "windows-1250", encoding::windows_1250 }, + { "x-cp1250", encoding::windows_1250 }, + + { "cp1251", encoding::windows_1251 }, + { "windows-1251", encoding::windows_1251 }, + { "x-cp1251", encoding::windows_1251 }, + + { "ansi_x3.4-1968", encoding::windows_1252 }, + { "ascii", encoding::windows_1252 }, + { "cp1252", encoding::windows_1252 }, + { "cp819", encoding::windows_1252 }, + { "csisolatin1", encoding::windows_1252 }, + { "ibm819", encoding::windows_1252 }, + { "iso-8859-1", encoding::windows_1252 }, + { "iso-ir-100", encoding::windows_1252 }, + { "iso8859-1", encoding::windows_1252 }, + { "iso88591", encoding::windows_1252 }, + { "iso_8859-1", encoding::windows_1252 }, + { "iso_8859-1:1987", encoding::windows_1252 }, + { "l1", encoding::windows_1252 }, + { "latin1", encoding::windows_1252 }, + { "us-ascii", encoding::windows_1252 }, + { "windows-1252", encoding::windows_1252 }, + { "x-cp1252", encoding::windows_1252 }, + + { "cp1253", encoding::windows_1253 }, + { "windows-1253", encoding::windows_1253 }, + { "x-cp1253", encoding::windows_1253 }, + + { "cp1254", encoding::windows_1254 }, + { "csisolatin5", encoding::windows_1254 }, + { "iso-8859-9", encoding::windows_1254 }, + { "iso-ir-148", encoding::windows_1254 }, + { "iso8859-9", encoding::windows_1254 }, + { "iso88599", encoding::windows_1254 }, + { "iso_8859-9", encoding::windows_1254 }, + { "iso_8859-9:1989", encoding::windows_1254 }, + { "l5", encoding::windows_1254 }, + { "latin5", encoding::windows_1254 }, + { "windows-1254", encoding::windows_1254 }, + { "x-cp1254", encoding::windows_1254 }, + + { "cp1255", encoding::windows_1255 }, + { "windows-1255", encoding::windows_1255 }, + { "x-cp1255", encoding::windows_1255 }, + + { "cp1256", encoding::windows_1256 }, + { "windows-1256", encoding::windows_1256 }, + { "x-cp1256", encoding::windows_1256 }, + + { "cp1257", encoding::windows_1257 }, + { "windows-1257", encoding::windows_1257 }, + { "x-cp1257", encoding::windows_1257 }, + + { "cp1258", encoding::windows_1258 }, + { "windows-1258", encoding::windows_1258 }, + { "x-cp1258", encoding::windows_1258 }, + + { "x-mac-cyrillic", encoding::x_mac_cyrillic }, + { "x-mac-ukrainian", encoding::x_mac_cyrillic }, + + { "chinese", encoding::gbk }, + { "csgb2312", encoding::gbk }, + { "csiso58gb231280", encoding::gbk }, + { "gb2312", encoding::gbk }, + { "gb_2312", encoding::gbk }, + { "gb_2312-80", encoding::gbk }, + { "gbk", encoding::gbk }, + { "iso-ir-58", encoding::gbk }, + { "x-gbk", encoding::gbk }, + + { "gb18030", encoding::gb18030 }, + + { "big5", encoding::big5 }, + { "big5-hkscs", encoding::big5 }, + { "cn-big5", encoding::big5 }, + { "csbig5", encoding::big5 }, + { "x-x-big5", encoding::big5 }, + + { "cseucpkdfmtjapanese", encoding::euc_jp }, + { "euc-jp", encoding::euc_jp }, + { "x-euc-jp", encoding::euc_jp }, + + { "csiso2022jp", encoding::iso_2022_jp }, + { "iso-2022-jp", encoding::iso_2022_jp }, + + { "csshiftjis", encoding::shift_jis }, + { "ms932", encoding::shift_jis }, + { "ms_kanji", encoding::shift_jis }, + { "shift-jis", encoding::shift_jis }, + { "shift_jis", encoding::shift_jis }, + { "sjis", encoding::shift_jis }, + { "windows-31j", encoding::shift_jis }, + { "x-sjis", encoding::shift_jis }, + + { "cseuckr", encoding::euc_kr }, + { "csksc56011987", encoding::euc_kr }, + { "euc-kr", encoding::euc_kr }, + { "iso-ir-149", encoding::euc_kr }, + { "korean", encoding::euc_kr }, + { "ks_c_5601-1987", encoding::euc_kr }, + { "ks_c_5601-1989", encoding::euc_kr }, + { "ksc5601", encoding::euc_kr }, + { "ksc_5601", encoding::euc_kr }, + { "windows-949", encoding::euc_kr }, + + { "csiso2022kr", encoding::replacement }, + { "hz-gb-2312", encoding::replacement }, + { "iso-2022-cn", encoding::replacement }, + { "iso-2022-cn-ext", encoding::replacement }, + { "iso-2022-kr", encoding::replacement }, + { "replacement", encoding::replacement }, + + { "unicodefffe", encoding::utf_16be }, + { "utf-16be", encoding::utf_16be }, + + { "csunicode", encoding::utf_16le }, + { "iso-10646-ucs-2", encoding::utf_16le }, + { "ucs-2", encoding::utf_16le }, + { "unicode", encoding::utf_16le }, + { "unicodefeff", encoding::utf_16le }, + { "utf-16", encoding::utf_16le }, + { "utf-16le", encoding::utf_16le }, + + { "x-user-defined", encoding::x_user_defined } }; // https://encoding.spec.whatwg.org/#concept-encoding-get encoding get_encoding(string label) { lcase(trim(label)); - for (int i = 0; i < countof(labels); i++) + for (const auto& l : labels) { - if (label == labels[i].name) - return labels[i].coding; + if (label == l.name) + return l.coding; } return encoding::null; } @@ -1646,7 +1650,7 @@ encoding extract_encoding_from_meta_element(string s) // 1. Let position be a pointer into s, initially pointing at the start of the string. size_t pos = 0; - // 2. Loop: Find the first seven characters in s after position that are an ASCII case-insensitive match for + // 2. Loop: Find the first seven characters in s after position that are an ASCII case-insensitive match for // the word "charset". If no such match is found, return nothing. loop: pos = s.find("charset", pos); @@ -1656,7 +1660,7 @@ loop: pos += strlen("charset"); // skip "charset" while (is_whitespace(s[pos])) pos++; - // 4. If the next character is not a U+003D (=), then move position to point just before that next character, + // 4. If the next character is not a U+003D (=), then move position to point just before that next character, // and jump back to the step labeled loop. if (s[pos] != '=') goto loop; @@ -1681,27 +1685,27 @@ loop: return encoding::null; // Return nothing. // -> Otherwise - // Return the result of getting an encoding from the substring that consists of this character up to + // Return the result of getting an encoding from the substring that consists of this character up to // but not including the first ASCII whitespace or U+003B (;), or the end of s, whichever comes first. end = s.find_first_of(" \n\r\f\t;", pos + 1); return get_encoding(s.substr(pos, end - pos)); // works for end == EOL too } // see step 5 of https://html.spec.whatwg.org/multipage/parsing.html#encoding-sniffing-algorithm -bool end_condition(size_t index) +bool end_condition(int index) { return index >= 1024; } -void increment(size_t& index, const string& str) +void increment(int& index, const string& str) { index++; - if (index >= str.size() || end_condition(index)) + if (index >= (int)str.size() || end_condition(index)) throw 0; // abort prescan } // https://html.spec.whatwg.org/multipage/parsing.html#concept-get-attributes-when-sniffing -bool prescan_get_attribute(const string& str, inout size_t& index, out string& name, out string& value) +bool prescan_get_attribute(const string& str, inout int& index, out string& name, out string& value) { // 1. while (is_whitespace(str[index]) || str[index] == '/') increment(index, str); @@ -1786,40 +1790,12 @@ step_11: goto step_11; } -template<class T> -bool contains(const std::vector<T>& vector, const T& item) -{ - return std::find(vector.begin(), vector.end(), item) != vector.end(); -} -bool is_one_of(int x, int a, int b, int c) -{ - return x == a || x == b || x == c; -} -bool equal_i(const string& s1, const string& s2) -{ - if (s1.size() != s2.size()) return false; - return t_strncasecmp(s1.c_str(), s2.c_str(), s1.size()) == 0; -} -bool match(const string& str, size_t index, const string& substr) -{ - return str.substr(index, substr.size()) == substr; -} -bool match_i(const string& str, size_t index, const string& substr) -{ - return equal_i(str.substr(index, substr.size()), substr); -} -int is_letter(int c) -{ - return t_isalpha(c); -} - - // https://html.spec.whatwg.org/multipage/parsing.html#prescan-a-byte-stream-to-determine-its-encoding encoding prescan_a_byte_stream_to_determine_its_encoding(const string& str) { // 1. Let fallback encoding be null. - bogus, never used // 2. Let position be a pointer to a byte in the input byte stream, initially pointing at the first byte. - size_t index = 0; + int index = 0; // 3. Prescan for UTF-16 XML declarations: if (match(str, index, {"<\0?\0x\0", 6})) return encoding::utf_16le; @@ -1829,27 +1805,27 @@ encoding prescan_a_byte_stream_to_determine_its_encoding(const string& str) loop: if (match(str, index, "<!--")) { - index = str.find("-->", index); - if (index == EOL || end_condition(index)) throw 0; // abort prescan + index = (int)str.find("-->", index); + if (index == -1 || end_condition(index)) throw 0; // abort prescan index += 2; // not 3 because it will be incremented one more time in step 5 (next_byte) } else if (match_i(str, index, "<meta") && (is_whitespace(str[index + 5]) || str[index + 5] == '/')) { // 1. // NOTE: Should be 6, but the standard says 5. It doesn't really matter because prescan_get_attribute will skip the WS or / anyway. - index += 5; + index += 5; // 2,3,4,5. string_vector attribute_list; bool got_pragma = false; int need_pragma = -1; // three values: -1 ("null"), true and false encoding charset = encoding::null; - + // 6. attributes: string attr_name, attr_value; if (!prescan_get_attribute(str, index, attr_name, attr_value)) goto processing; - + // 7. If the attribute's name is already in attribute list, then return to the step labeled attributes. if (contains(attribute_list, attr_name)) goto attributes; @@ -1859,7 +1835,7 @@ loop: // 9. // NOTE: attr_name and attr_value are already lowcased, see prescan_get_attribute - if (attr_name == "http-equiv" && attr_value == "content-type") + if (attr_name == "http-equiv" && attr_value == "content-type") { got_pragma = true; } @@ -1886,7 +1862,7 @@ loop: processing: if (need_pragma == -1) goto next_byte; - + // 12. if (need_pragma == (int)true && !got_pragma) goto next_byte; @@ -1910,8 +1886,8 @@ loop: (str[index] == '<' && is_letter(str[index + 1]))) { // 1. - index = str.find_first_of(" \t\r\n\f>", index); - if (index == EOL || end_condition(index)) throw 0; // abort prescan + index = (int)str.find_first_of(" \t\r\n\f>", index); + if (index == -1 || end_condition(index)) throw 0; // abort prescan // 2. string tmp; @@ -1920,8 +1896,8 @@ loop: } else if (str[index] == '<' && is_one_of(str[index + 1], '!', '/', '?')) { - index = str.find('>', index); - if (index == EOL || end_condition(index)) throw 0; // abort prescan + index = (int)str.find('>', index); + if (index == -1 || end_condition(index)) throw 0; // abort prescan } // 5. @@ -1934,7 +1910,7 @@ next_byte: encoding get_xml_encoding(const string& str) { // 1. Let encodingPosition be a pointer to the start of the stream. - size_t index = 0; + int index = 0; // 2. if (!match(str, index, "<?xml")) @@ -1942,18 +1918,18 @@ encoding get_xml_encoding(const string& str) // 3. // NOTE: xmlDeclarationEnd is unused - index = str.find('>', index); - if (index == EOL) return encoding::null; + index = (int)str.find('>', index); + if (index == -1) return encoding::null; // 4. - index = str.find("encoding", index); - if (index == EOL) return encoding::null; + index = (int)str.find("encoding", index); + if (index == -1) return encoding::null; // 5. - index += strlen("encoding"); + index += (int)strlen("encoding"); // 6. - while ((byte)str[index] <= 0x20 && index < str.size()) index++; + while ((byte)str[index] <= 0x20 && index < (int)str.size()) index++; // 7. if (str[index] != '=') return encoding::null; @@ -1962,7 +1938,7 @@ encoding get_xml_encoding(const string& str) index++; // skip '=' // 9. - while ((byte)str[index] <= 0x20 && index < str.size()) index++; + while ((byte)str[index] <= 0x20 && index < (int)str.size()) index++; // 10. Let quoteMark be the byte at encodingPosition. char q = str[index]; @@ -1975,7 +1951,7 @@ encoding get_xml_encoding(const string& str) // 13. Let encodingEndPosition be the position of the next occurrence of quoteMark size_t end = str.find(q, index); - if (index == EOL) return encoding::null; + if (index == -1) return encoding::null; // 14. string potentialEncoding = str.substr(index, end - index); @@ -2058,4 +2034,4 @@ void encoding_sniffing_algorithm(estring& str) // otherwise use str.encoding (tentative) } -} // namespace litehtml
\ No newline at end of file +} // namespace litehtml diff --git a/libs/litehtml/src/flex_line.cpp b/libs/litehtml/src/flex_line.cpp index 5fcbaa766c..ce45a06335 100644 --- a/libs/litehtml/src/flex_line.cpp +++ b/libs/litehtml/src/flex_line.cpp @@ -49,7 +49,6 @@ void litehtml::flex_line::distribute_free_space(int container_main_size) while (processed) { int sum_scaled_flex_shrink_factor = 0; - int sum_flex_factors = 0; int remaining_free_space = container_main_size; int total_not_frozen = 0; for (auto &item: items) @@ -57,13 +56,6 @@ void litehtml::flex_line::distribute_free_space(int container_main_size) if (!item->frozen) { sum_scaled_flex_shrink_factor += item->scaled_flex_shrink_factor; - if(grow) - { - sum_flex_factors += item->grow; - } else - { - sum_flex_factors += item->shrink; - } remaining_free_space -= item->base_size; total_not_frozen++; } else diff --git a/libs/litehtml/src/gradient.cpp b/libs/litehtml/src/gradient.cpp index 081b0d405c..b7c7883d60 100644 --- a/libs/litehtml/src/gradient.cpp +++ b/libs/litehtml/src/gradient.cpp @@ -1,553 +1,565 @@ #include "html.h" #include "gradient.h" - -#ifndef M_PI -# define M_PI 3.14159265358979323846 -#endif +#include "css_parser.h" namespace litehtml { - /** - * Parse CSS angle - * @param str css text of angle - * @param with_percent true to pare percent as angle (for conic gradient) - * @param angle output angle value in degrees - * @return - */ - static bool parse_css_angle(const string& str, bool with_percent, float& angle) + +bool parse_linear_gradient_direction(const css_token_vector& tokens, int& index, float& angle, int& side); +bool parse_linear_gradient_direction_and_interpolation(const css_token_vector& tokens, gradient& gradient); +bool parse_color_interpolation_method(const css_token_vector& tokens, int& index, color_space_t& color_space, hue_interpolation_t& hue_interpolation); +bool parse_gradient_position(const css_token_vector& tokens, int& index, gradient& gradient); +bool parse_radial_gradient_shape_size_position_interpolation(const css_token_vector& tokens, gradient& result); +bool parse_conic_gradient_angle_position_interpolation(const css_token_vector& tokens, gradient& gradient); +template<class T> +bool parse_color_stop_list(const vector<css_token_vector>& list, gradient& grad, document_container* container); + +//////////////////////////////////////////////////////////////////////////////////////////// +// These combinators are currently used only in one place because the code is usually shorter without them. + +using parse_fn = std::function<bool(const css_token_vector& tokens, int& index)>; + +// a? +parse_fn opt(parse_fn a) +{ + return [=](auto&... x) + { + a(x...); + return true; + }; +} + +// a b +parse_fn seq(parse_fn a, parse_fn b) +{ + return [=](auto& t, auto& i) + { + auto save = i; + bool result = a(t, i) && b(t, i); + if (!result) i = save; // backtrack + return result; + }; +} + +// Not overloading operator|| because it is easier to get a bug: a || b || c does the wrong thing, +// see the note at https://www.w3.org/TR/css-values-4/#component-combinators. +// a || b +parse_fn oror(parse_fn a, parse_fn b) +{ + return [=](auto&... x) { - const char* start = str.c_str(); - for(;start[0]; start++) + if (a(x...)) { - if(!isspace(start[0])) break; + b(x...); + return true; } - if(start[0] == 0) return false; - char* end = nullptr; - auto a = (float) t_strtod(start, &end); - if(end && end[0] == 0) return false; - if(!strcmp(end, "rad")) - { - a = (float) (a * 180.0 / M_PI); - } else if(!strcmp(end, "grad")) + else if (b(x...)) { - a = a * 180.0f / 200.0f; - } else if(!strcmp(end, "turn")) - { - a = a * 360.0f; - } else if(strcmp(end, "deg")) + a(x...); + return true; + } + return false; + }; +} + +parse_fn operator""_x(const char* str, size_t len) +{ + return [=](const css_token_vector& tokens, int& index) + { + if (at(tokens, index).ident() == string(str, len)) { - if(with_percent && strcmp(end, "%")) - { - a = a * 360.0f / 100.0f; - } else - { - return false; - } + index++; + return true; } - angle = a; + return false; + }; +} + +bool end(const css_token_vector& tokens, int index) +{ + return index == (int)tokens.size(); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +// https://drafts.csswg.org/css-images-4/#gradients +// +// <gradient> = +// <linear-gradient()> | <repeating-linear-gradient()> | +// <radial-gradient()> | <repeating-radial-gradient()> | +// <conic-gradient()> | <repeating-conic-gradient()> +// +bool parse_gradient(const css_token& token, gradient& result, document_container* container) +{ + if (token.type != CV_FUNCTION) + return false; + + auto type = _id(lowcase(token.name)); + + if (!is_one_of(type, + _linear_gradient_, _repeating_linear_gradient_, + _radial_gradient_, _repeating_radial_gradient_, + _conic_gradient_, _repeating_conic_gradient_)) + return false; + + gradient grad(type); + + if (!grad.is_linear()) { + // radial and conic position defaults to 'center' + // https://drafts.csswg.org/css-images-3/#valdef-radial-gradient-position + // https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-position + grad.m_side = gradient_side_x_center | gradient_side_y_center; + } + + auto list = parse_comma_separated_list(token.value); + if (list.empty()) return false; + + bool ok; + + if (grad.is_linear()) + ok = parse_linear_gradient_direction_and_interpolation(list[0], grad); + else if (grad.is_radial()) + ok = parse_radial_gradient_shape_size_position_interpolation(list[0], grad); + else + ok = parse_conic_gradient_angle_position_interpolation(list[0], grad); + + if (ok) remove(list, 0); + + + if (grad.is_conic()) + ok = parse_color_stop_list<float>(list, grad, container); + else + ok = parse_color_stop_list<css_length>(list, grad, container); + + if (!ok) return false; + + + result = grad; + return true; +} + +// parse <length-percentage> or <angle-percentage> +bool parse_lenang(const css_token& tok, css_length& length) +{ + return parse_length(tok, length, f_length_percentage); +} +bool parse_lenang(const css_token& tok, float& angle) +{ + return parse_angle(tok, angle, true); +} + +// <color-hint> = <length-percentage> | <angle-percentage> +template<class T> // T == css_length or float +bool parse_color_hint(const css_token_vector& tokens, vector<gradient::color_stop>& color_stops) +{ + T lenang; + if (tokens.size() == 1 && parse_lenang(tokens[0], lenang)) + { + color_stops.push_back(lenang); return true; } + return false; +} + +// <linear-color-stop> = <color> <length-percentage>{1,2}? +// <angular-color-stop> = <color> <angle-percentage>{1,2}? +template<class T> // T == css_length or float +bool parse_color_stop(const css_token_vector& tokens, vector<gradient::color_stop>& color_stops, document_container* container) +{ + if (tokens.empty() || tokens.size() > 3) + return false; - /** - * Parse colors stop list for radial and linear gradients. - * Formal syntax: - * \code - * <linear-color-stop> = - * <color> <length-percentage>? - * - * <linear-color-hint> = - * <length-percentage> - * - * <length-percentage> = - * <length> | - * <percentage> - * \endcode - * @param parts - * @param container - * @param grad - */ - static void parse_color_stop_list(const string_vector& parts, document_container *container, std::vector<background_gradient::gradient_color>& colors) + web_color color; + if (!parse_color(tokens[0], color, container)) + return false; + + if (tokens.size() == 1) // <color> { - auto color = web_color::from_string(parts[0], container); - css_length length; - if(parts.size() > 1) + color_stops.emplace_back(color); + return true; + } + else if (tokens.size() == 2) // <color> <length-angle-percentage> + { + T lenang; + if (parse_lenang(tokens[1], lenang)) { - length.fromString(parts[1]); - if(!length.is_predefined()) - { - background_gradient::gradient_color gc; - gc.color = color; - gc.length = length; - colors.push_back(gc); - } - if(parts.size() > 2) - { - length.fromString(parts[2]); - if(!length.is_predefined()) - { - background_gradient::gradient_color gc; - gc.color = color; - gc.length = length; - colors.push_back(gc); - } - } - } else + color_stops.emplace_back(color, lenang); + return true; + } + } + else if (tokens.size() == 3) // <color> <length-angle-percentage> <length-angle-percentage> + { + T lenang1, lenang2; + if (parse_lenang(tokens[1], lenang1) && + parse_lenang(tokens[2], lenang2)) { - background_gradient::gradient_color gc; - gc.color = color; - colors.push_back(gc); + color_stops.emplace_back(color, lenang1); + color_stops.emplace_back(color, lenang2); + return true; } } + return false; +} - static void parse_color_stop_angle_list(const string_vector& parts, document_container *container, background_gradient& grad) +// <color-stop-list> = <color-stop> , [ <color-hint>? , <color-stop> ]# +template<class T> // T == css_length or float +bool parse_color_stop_list(const vector<css_token_vector>& list, gradient& grad, document_container* container) +{ + if (list.size() < 2) // at least two color-stops must be present + return false; + + if (!parse_color_stop<T>(list[0], grad.m_colors, container)) + return false; + + // [ <color-hint>? , <color-stop> ]# + for (size_t i = 1; i < list.size(); i++) { - auto color = web_color::from_string(parts[0], container); - if(parts.size() > 1) + if (parse_color_hint<T>(list[i], grad.m_colors)) { - float angle = 0; - if(parse_css_angle(parts[1], true, angle)) - { - background_gradient::gradient_color gc; - gc.angle = angle; - gc.color = color; - grad.m_colors.push_back(gc); - } - if(parts.size() > 2) - { - if(parse_css_angle(parts[1], true, angle)) - { - background_gradient::gradient_color gc; - gc.color = color; - gc.angle = angle; - grad.m_colors.push_back(gc); - } - } - } else - { - background_gradient::gradient_color gc; - gc.color = color; - grad.m_colors.push_back(gc); + i++; + if (i == list.size()) return false; // color-hint not followed by color-stop } + if (!parse_color_stop<T>(list[i], grad.m_colors, container)) + return false; } + return true; +} - /** - * Parse linear gradient definition. - * Formal syntax: - * \code{plain} - * <linear-gradient()> = - * linear-gradient( [ <linear-gradient-syntax> ] ) - * - * <linear-gradient-syntax> = - * [ <angle> | to <side-or-corner> ]? , <color-stop-list> - * - * <side-or-corner> = - * [ left | right ] || - * [ top | bottom ] - * - * <color-stop-list> = - * <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]# - * - * <linear-color-stop> = - * <color> <length-percentage>? - * - * <linear-color-hint> = - * <length-percentage> - * - * <length-percentage> = - * <length> | - * <percentage> - * \endcode - * @param gradient_str - * @param container - * @param grad - */ - void parse_linear_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad) +// https://drafts.csswg.org/css-images-4/#linear-gradients +// [ <angle> | to <side-or-corner> ] || <color-interpolation-method> +bool parse_linear_gradient_direction_and_interpolation(const css_token_vector& tokens, gradient& gradient) +{ + float angle = 180; + int side = gradient_side_none; + auto color_space = color_space_oklab; + auto hue_interpolation = hue_interpolation_shorter; + + int index = 0; + if (parse_linear_gradient_direction(tokens, index, angle, side)) + { + parse_color_interpolation_method(tokens, index, color_space, hue_interpolation); + } + else if (parse_color_interpolation_method(tokens, index, color_space, hue_interpolation)) { - string_vector items; - split_string(gradient_str, items, ",", "", "()"); + parse_linear_gradient_direction(tokens, index, angle, side); + } + else + return false; + + if (index != (int)tokens.size()) return false; - for (auto &item: items) - { - trim(item); - string_vector parts; - split_string(item, parts, split_delims_spaces, "", "()"); - if (parts.empty()) continue; + gradient.angle = angle; + gradient.m_side = side; + gradient.color_space = color_space; + gradient.hue_interpolation = hue_interpolation; + return true; +} - if (parts[0] == "to") - { - uint32_t grad_side = 0; - for (size_t part_idx = 1; part_idx < parts.size(); part_idx++) - { - int side = value_index(parts[part_idx], "left;right;top;bottom"); - if (side >= 0) - { - grad_side |= 1 << side; - } - } - switch(grad_side) - { - case background_gradient::gradient_side_top: - grad.angle = 0; - break; - case background_gradient::gradient_side_bottom: - grad.angle = 180; - break; - case background_gradient::gradient_side_left: - grad.angle = 270; - break; - case background_gradient::gradient_side_right: - grad.angle = 90; - break; - case background_gradient::gradient_side_top | background_gradient::gradient_side_left: - case background_gradient::gradient_side_top | background_gradient::gradient_side_right: - case background_gradient::gradient_side_bottom | background_gradient::gradient_side_left: - case background_gradient::gradient_side_bottom | background_gradient::gradient_side_right: - grad.m_side = grad_side; - break; - default: - break; - } - } else if (parts.size() == 1 && parse_css_angle(parts[0], false, grad.angle)) - { - continue; - } else if (web_color::is_color(parts[0], container)) - { - parse_color_stop_list(parts, container, grad.m_colors); - } else - { - css_length length; - length.fromString(parts[0]); - if (!length.is_predefined()) - { - background_gradient::gradient_color gc; - gc.length = length; - gc.is_color_hint = true; - grad.m_colors.push_back(gc); - } - } - } +// https://drafts.csswg.org/css-images-4/#linear-gradients +// <angle> | to <side-or-corner> +// <side-or-corner> = [left | right] || [top | bottom] +bool parse_linear_gradient_direction(const css_token_vector& tokens, int& index, float& angle, int& side) +{ + if (parse_angle(at(tokens, index), angle)) + { + index++; + return true; } - /** - * Parse position part for radial gradient. - * Formal syntax: - * \code - * <position> = - * [ left | center | right | top | bottom | <length-percentage> ] | - * [ left | center | right ] && [ top | center | bottom ] | - * [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ] | - * [ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ] - * \endcode - * @param grad - * @param parts - * @param i - */ - static inline void parse_radial_position(litehtml::background_gradient &grad, const litehtml::string_vector &parts, size_t i) + if (at(tokens, index).ident() != "to") + return false; + + string a = at(tokens, index + 1).ident(); + string b = at(tokens, index + 2).ident(); + + if (is_one_of(a, "left", "right", "top", "bottom")) { - grad.m_side = 0; - while (i < parts.size()) + if (!is_one_of(b, "left", "right", "top", "bottom")) { - int side = litehtml::value_index(parts[i], "left;right;top;bottom;center"); - if (side >= 0) - { - if(side == 4) - { - if(grad.m_side & litehtml::background_gradient::gradient_side_x_center) - { - grad.m_side |= litehtml::background_gradient::gradient_side_y_center; - } else - { - grad.m_side |= litehtml::background_gradient::gradient_side_x_center; - } - } else - { - grad.m_side |= 1 << side; - } - } else + switch (_id(a)) { - litehtml::css_length length; - length.fromString(parts[i]); - if (!length.is_predefined()) - { - if(grad.m_side & litehtml::background_gradient::gradient_side_x_length) - { - grad.m_side |= litehtml::background_gradient::gradient_side_y_length; - grad.radial_position_y = length; - } else - { - grad.m_side |= litehtml::background_gradient::gradient_side_x_length; - grad.radial_position_x = length; - } - } + case _top_: angle = 0; break; + case _bottom_: angle = 180; break; + case _left_: angle = 270; break; + case _right_: angle = 90; break; + default: return false; } - i++; + index += 2; + return true; + } + else + { + // fix order + if (is_one_of(a, "top", "bottom")) + swap(a, b); + + // check order + if (!is_one_of(a, "left", "right") || !is_one_of(b, "top", "bottom")) + return false; + + side = a == "left" ? gradient_side_left : gradient_side_right; + side |= b == "top" ? gradient_side_top : gradient_side_bottom; + index += 3; + return true; } } + return false; +} + +// https://drafts.csswg.org/css-images-4/#typedef-conic-gradient-syntax +// [ from <angle> ]? [ at <position> ]? +bool parse_conic_angle_position(const css_token_vector& tokens, int& index, gradient& gradient) +{ + if (at(tokens, index).ident() == "from" && parse_angle(at(tokens, index + 1), gradient.conic_from_angle)) + index += 2; + + int i = index; + if (at(tokens, i).ident() == "at" && parse_gradient_position(tokens, ++i, gradient)) + index = i; - /** - * Parse radial gradient definition - * Formal syntax: - * \code - * <radial-gradient()> = - * radial-gradient( [ <radial-gradient-syntax> ] ) - * - * <radial-gradient-syntax> = - * [ <radial-shape> || <radial-size> ]? [ at <position> ]? , <color-stop-list> - * - * <radial-shape> = - * circle | - * ellipse - * - * <radial-size> = - * <radial-extent> | - * <length [0,∞]> | - * <length-percentage [0,∞]>{2} - * - * <position> = - * [ left | center | right | top | bottom | <length-percentage> ] | - * [ left | center | right ] && [ top | center | bottom ] | - * [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ] | - * [ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ] - * - * <color-stop-list> = - * <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]# - * - * <radial-extent> = - * closest-corner | - * closest-side | - * farthest-corner | - * farthest-side - * - * <length-percentage> = - * <length> | - * <percentage> - - * <linear-color-stop> = - * <color> <length-percentage>? - * - * <linear-color-hint> = - * <length-percentage> - * \endcode - * @param gradient_str - * @param container - * @param grad - */ - void parse_radial_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad) + return true; +} +// [ [ from <angle> ]? [ at <position> ]? ] || <color-interpolation-method> +bool parse_conic_gradient_angle_position_interpolation(const css_token_vector& tokens, gradient& gradient) +{ + if (tokens.empty()) return false; + + auto color_space = color_space_oklab; + auto hue_interpolation = hue_interpolation_shorter; + + int index = 0; + // checking color interpolation first because parse_conic_angle_position always succeeds + if (parse_color_interpolation_method(tokens, index, color_space, hue_interpolation)) + { + parse_conic_angle_position(tokens, index, gradient); + } + else if (parse_conic_angle_position(tokens, index, gradient)) { - string_vector items; - split_string(gradient_str, items, ",", "", "()"); + parse_color_interpolation_method(tokens, index, color_space, hue_interpolation); + } + else + return false; - for (auto &item: items) - { - trim(item); - string_vector parts; - split_string(item, parts, split_delims_spaces, "", "()"); - if (parts.empty()) continue; + if (index != (int)tokens.size()) return false; - if (web_color::is_color(parts[0], container)) - { - parse_color_stop_list(parts, container, grad.m_colors); - } else - { - size_t i = 0; - while(i < parts.size()) - { - if(parts[i] == "at") - { - parse_radial_position(grad, parts, i + 1); - break; - } else // parts[i] == "at" - { - int val = value_index(parts[i], "closest-corner;closest-side;farthest-corner;farthest-side"); - if(val >= 0) - { - grad.radial_extent = (background_gradient::radial_extent_t) (val + 1); - } else - { - val = value_index(parts[i], "circle;ellipse"); - if(val >= 0) - { - grad.radial_shape = (background_gradient::radial_shape_t) (val + 1); - } else - { - css_length length; - length.fromString(parts[i]); - if (!length.is_predefined()) - { - if(!grad.radial_length_x.is_predefined()) - { - grad.radial_length_y = length; - } else - { - grad.radial_length_x = length; - } - } - } - } - } // else parts[i] == "at" - i++; - } // while(i < parts.size()) - } // else web_color::is_color(parts[0], container) - } // for - if(grad.radial_extent == background_gradient::radial_extent_none) - { - if(grad.radial_length_x.is_predefined()) - { - grad.radial_extent = background_gradient::radial_extent_farthest_corner; - } else if(grad.radial_length_y.is_predefined()) - { - grad.radial_length_y = grad.radial_length_x.val(); - grad.radial_shape = background_gradient::radial_shape_circle; - } - } - if(grad.radial_shape == background_gradient::radial_shape_none) + gradient.color_space = color_space; + gradient.hue_interpolation = hue_interpolation; + return true; +} + +const float pi = 3.14159265f; + +// https://drafts.csswg.org/css-values-4/#angles +bool parse_angle(const css_token& tok, float& angle, bool percents_allowed) +{ + // The unit identifier may be omitted if the <angle> is zero. https://drafts.csswg.org/css-images-3/#linear-gradient-syntax + if (tok.type == NUMBER && tok.n.number == 0) + { + angle = 0; + return true; + } + + // <angle-percentage> in conic gradient + if (tok.type == PERCENTAGE && percents_allowed) + { + angle = tok.n.number * 360 / 100; + return true; + } + + if (tok.type == DIMENSION) + { + switch (_id(lowcase(tok.unit))) { - grad.radial_shape = background_gradient::radial_shape_ellipse; + case _deg_: angle = tok.n.number; break; + case _grad_: angle = (tok.n.number / 400) * 360; break; + case _rad_: angle = (tok.n.number / (2 * pi)) * 360; break; + case _turn_: angle = tok.n.number * 360; break; + default: return false; } + return true; } - /** - * Parse conic gradient definition. - * Formal syntax: - * \code - * conic-gradient-syntax = - * [ [ [ from <angle> ]? [ at position ]? ] || <color-interpolation-method> ]? , <angular-color-stop-list> - * - * <position> = - * [ left | center | right | top | bottom | <length-percentage> ] | - * [ left | center | right ] && [ top | center | bottom ] | - * [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ] | - * [ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ] - * - * <color-interpolation-method> = - * in [ <rectangular-color-space> | <polar-color-space> <hue-interpolation-method>? ] - * - * <angular-color-stop-list> = - * <angular-color-stop> , [ <angular-color-hint>? , <angular-color-stop> ]# - * - * <length-percentage> = - * <length> | - * <percentage> - * - * <rectangular-color-space> = - * srgb | - * srgb-linear | - * display-p3 | - * a98-rgb | - * prophoto-rgb | - * rec2020 | - * lab | - * oklab | - * xyz | - * xyz-d50 | - * xyz-d65 - * - * <polar-color-space> = - * hsl | - * hwb | - * lch | - * oklch - * - * <hue-interpolation-method> = - * [ shorter | longer | increasing | decreasing ] hue - * - * <angular-color-stop> = - * <color> <color-stop-angle>? - * - * <angular-color-hint> = - * <angle-percentage> - * - * <color-stop-angle> = - * <angle-percentage>{1,2} - * - * <angle-percentage> = - * <angle> | - * <percentage> - * \endcode - * @param gradient_str - * @param container - * @param grad - */ - void parse_conic_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad) + return false; +} + +// https://www.w3.org/TR/css-color-4/#color-interpolation-method +// <rectangular-color-space> = srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020 | lab | oklab | xyz | xyz-d50 | xyz-d65 +// <polar-color-space> = hsl | hwb | lch | oklch +// <hue-interpolation-method> = [ shorter | longer | increasing | decreasing ] hue +// <color-interpolation-method> = in [ <rectangular-color-space> | <polar-color-space> <hue-interpolation-method>? ] +bool parse_color_interpolation_method(const css_token_vector& tokens, int& index, + color_space_t& color_space, hue_interpolation_t& hue_interpolation) +{ + if (at(tokens, index).ident() == "in" && + parse_keyword(at(tokens, index + 1), color_space, color_space_strings, 1)) { - string_vector items; - split_string(gradient_str, items, ",", "", "()"); + index += 2; + } + else + return false; - for (auto &item: items) - { - trim(item); - string_vector parts; - split_string(item, parts, split_delims_spaces, "", "()"); - if (parts.empty()) continue; + if (color_space >= color_space_polar_start && + at(tokens, index + 1).ident() == "hue" && // must be checked before parse_keyword, otherwise hue_interpolation may be assigned a value when there is no "hue" keyword + parse_keyword(at(tokens, index), hue_interpolation, hue_interpolation_strings, 1)) + { + index += 2; + } + return true; +} - // Parse colors stop list - if (web_color::is_color(parts[0], container)) - { - parse_color_stop_angle_list(parts, container, grad); - continue; - } +// https://www.w3.org/TR/css-images-3/#typedef-radial-size +// <radial-size> = <radial-extent> | <length [0,∞]> | <length-percentage [0,∞]>{2} +// <radial-extent> = closest-corner | closest-side | farthest-corner | farthest-side +// Permitted values also depend on <radial-shape>, see parse_radial_gradient_shape_size_position_interpolation. +// TODO: <radial-size> syntax was extended in https://drafts.csswg.org/css-images-4/#radial-size +bool parse_radial_size(const css_token_vector& tokens, int& index, gradient& gradient) +{ + auto& tok0 = at(tokens, index); + auto& tok1 = at(tokens, index + 1); - size_t i = 0; - while(i < parts.size()) - { - // parse position - if(parts[i] == "at") - { - parse_radial_position(grad, parts, i + 1); - break; - } - // parse "from angle" - if(parts[i] == "from") - { - i++; - if(i >= parts.size()) continue; - parse_css_angle(parts[i], false, grad.conic_from_angle); - continue; - } - if(parts[i] == "in") - { - i++; - if(i >= parts.size()) continue; - int val = value_index(parts[i], "srgb;" - "srgb-linear;" - "display-p3;" - "a98-rgb;" - "prophoto-rgb;" - "rec2020;" - "lab;" - "oklab;" - "xyz;" - "xyz-d50;" - "xyz-d65"); - if(val >= 0) - { - grad.conic_color_space = (decltype(grad.conic_color_space)) (val + 1); - continue; - } - val = value_index(parts[i], "hsl;" - "hwb;" - "lch;" - "oklch"); - if(val < 0) continue; - - grad.conic_color_space = (decltype(grad.conic_color_space)) (background_gradient::conic_color_space_polar_start + 1 + val); - int interpol = value_index(parts[i], "hue;shorter;longer;increasing;decreasing"); - if(interpol == 0) - { - grad.conic_interpolation = background_gradient::interpolation_method_hue; - continue; - } - if(interpol > 0) - { - i++; - if(i >= parts.size()) continue; - if(parts[i] != "hue") continue; - grad.conic_interpolation = (decltype(grad.conic_interpolation)) (val + 1); - continue; - } - } - i++; - } - } + if (parse_keyword(tok0, gradient.radial_extent, radial_extent_strings, 1)) + { + index++; + return true; + } + + css_length length[2]; + if (length[0].from_token(tok0, f_length_percentage | f_positive) && + length[1].from_token(tok1, f_length_percentage | f_positive)) + { + gradient.radial_extent = radial_extent_none; + gradient.radial_radius_x = length[0]; + gradient.radial_radius_y = length[1]; + index += 2; + return true; + } + + if (length[0].from_token(tok0, f_length | f_positive)) + { + gradient.radial_extent = radial_extent_none; + gradient.radial_radius_x = length[0]; + index++; + return true; } -}
\ No newline at end of file + + return false; +} + +bool parse_gradient_position(const css_token_vector& tokens, int& index, gradient& gradient) +{ + css_length x, y; + if (!parse_bg_position(tokens, index, x, y, false)) + return false; + + gradient.m_side = 0; + if (x.is_predefined()) + { + if (x.predef() == background_position_center) + gradient.m_side |= gradient_side_x_center; + else + gradient.m_side |= 1 << x.predef(); + } + else + { + gradient.m_side |= gradient_side_x_length; + gradient.position_x = x; + } + + if (y.is_predefined()) + { + if (y.predef() == background_position_center) + gradient.m_side |= gradient_side_y_center; + else + gradient.m_side |= 1 << y.predef(); + } + else + { + gradient.m_side |= gradient_side_y_length; + gradient.position_y = y; + } + return true; +} + +// https://drafts.csswg.org/css-images-4/#radial-gradients +// [ [ <radial-shape> || <radial-size> ]? [ at <position> ]? ] || <color-interpolation-method> +bool parse_radial_gradient_shape_size_position_interpolation(const css_token_vector& tokens, gradient& result) +{ + // this check is needed because parse may succeed without consuming any input + if (tokens.empty()) return false; + + auto shape = radial_shape_none; + auto radial_shape = [&](const css_token_vector& tokens, int& index) + { + if (!parse_keyword(at(tokens, index), shape, "circle;ellipse", 1)) + return false; + index++; + return true; + }; + + using namespace std::placeholders; + gradient grad; + // sets grad.radial_extent or grad.radial_radius_{x,y} + parse_fn radial_size = std::bind( parse_radial_size, _1, _2, std::ref(grad) ); + // sets grad.m_side and grad.radial_position_{x,y} + parse_fn radial_position = std::bind( parse_gradient_position, _1, _2, std::ref(grad) ); + + auto color_space = color_space_oklab; + auto hue_interpolation = hue_interpolation_shorter; + auto color_interpolation_method = [&](const css_token_vector& tokens, int& index) + { + return parse_color_interpolation_method(tokens, index, color_space, hue_interpolation); + }; + + ///////////////////////////////////////////////////////////////////////////////////////// + + auto parse = oror( + color_interpolation_method, // first trying this because seq(opt,opt) always succeeds + seq(opt(oror(radial_shape, radial_size)), opt(seq("at"_x, radial_position))) + ); + + int index = 0; + bool ok = parse(tokens, index) && end(tokens, index); + if (!ok) return false; + + ///////////////////////////////////////////////////////////////////////////////////////// + + // If <radial-shape> is specified as circle or is omitted, the <radial-size> may be given explicitly as <length [0,∞]> + if (shape == radial_shape_ellipse && + // radius_x is specified, but radius_y is not + !grad.radial_radius_x.is_predefined() && grad.radial_radius_y.is_predefined()) + return false; + + // If <radial-shape> is specified as ellipse or is omitted, <radial-size> may instead be given explicitly as <length-percentage [0,∞]>{2} + if (shape == radial_shape_circle && + // both radius_x and radius_y are specified + !grad.radial_radius_y.is_predefined()) + return false; + + // If <radial-shape> is omitted, the ending shape defaults to a circle if the <radial-size> is a single <length>, and to an ellipse otherwise. + if (shape == radial_shape_none) + { + if (!grad.radial_radius_x.is_predefined() && grad.radial_radius_y.is_predefined()) + shape = radial_shape_circle; + else + shape = radial_shape_ellipse; + } + + ///////////////////////////////////////////////////////////////////////////////////////// + + result.radial_shape = shape; + + result.radial_extent = grad.radial_extent; + result.radial_radius_x = grad.radial_radius_x; + result.radial_radius_y = grad.radial_radius_y; + + result.m_side = grad.m_side; + result.position_x = grad.position_x; + result.position_y = grad.position_y; + + result.color_space = color_space; + result.hue_interpolation = hue_interpolation; + + return true; +} + +} diff --git a/libs/litehtml/src/gumbo/CMakeLists.txt b/libs/litehtml/src/gumbo/CMakeLists.txt index cd55186a15..7282604893 100644 --- a/libs/litehtml/src/gumbo/CMakeLists.txt +++ b/libs/litehtml/src/gumbo/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.5) project(gumbo C) @@ -46,6 +46,10 @@ set(HEADER_GUMBO set(PROJECT_LIB_VERSION ${PROJECT_MAJOR}.${PROJECT_MINOR}.0) set(PROJECT_SO_VERSION ${PROJECT_MAJOR}) +if (MSVC) + add_compile_options(/wd4244 /wd4267) +endif() + add_library(${PROJECT_NAME} ${SOURCE_GUMBO}) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_LIB_VERSION} SOVERSION ${PROJECT_SO_VERSION}) @@ -54,7 +58,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${HEADER_GUMBO}" ) -if(MSVC) +if (MSVC) target_include_directories(${PROJECT_NAME} PRIVATE visualc/include) endif() diff --git a/libs/litehtml/src/gumbo/char_ref.c b/libs/litehtml/src/gumbo/char_ref.c index b3c5eccaf3..a1d74fd5df 100644 --- a/libs/litehtml/src/gumbo/char_ref.c +++ b/libs/litehtml/src/gumbo/char_ref.c @@ -23009,7 +23009,7 @@ _again: if (cs >= 7623) { assert(output->first != kGumboNoChar); char last_char = *(te - 1); - ptrdiff_t len = te - start; + int len = te - start; if (last_char == ';') { bool matched = utf8iterator_maybe_consume_match(input, start, len, true); assert(matched); diff --git a/libs/litehtml/src/gumbo/char_ref.rl b/libs/litehtml/src/gumbo/char_ref.rl index 7c1c410d93..139a4bbd33 100644 --- a/libs/litehtml/src/gumbo/char_ref.rl +++ b/libs/litehtml/src/gumbo/char_ref.rl @@ -2493,7 +2493,7 @@ static bool consume_named_ref( if (cs >= %%{ write first_final; }%%) { assert(output->first != kGumboNoChar); char last_char = *(te - 1); - ptrdiff_t len = te - start; + int len = te - start; if (last_char == ';') { bool matched = utf8iterator_maybe_consume_match(input, start, len, true); assert(matched); diff --git a/libs/litehtml/src/gumbo/error.c b/libs/litehtml/src/gumbo/error.c index 369e7c11e8..057e89ce29 100644 --- a/libs/litehtml/src/gumbo/error.c +++ b/libs/litehtml/src/gumbo/error.c @@ -33,7 +33,7 @@ static int print_message( GumboParser* parser, GumboStringBuffer* output, const char* format, ...) { va_list args; - size_t remaining_capacity = output->capacity - output->length; + int remaining_capacity = output->capacity - output->length; va_start(args, format); int bytes_written = vsnprintf( output->data + output->length, remaining_capacity, format, args); @@ -59,7 +59,7 @@ static int print_message( } #endif - if (bytes_written > remaining_capacity) { + if (bytes_written >= remaining_capacity) { gumbo_string_buffer_reserve( parser, output->capacity + bytes_written, output); remaining_capacity = output->capacity - output->length; @@ -79,7 +79,7 @@ static void print_tag_stack(GumboParser* parser, const GumboParserError* error, if (i) { print_message(parser, output, ", "); } - GumboTag tag = (GumboTag)(uintptr_t) error->tag_stack.data[i]; + GumboTag tag = (GumboTag) error->tag_stack.data[i]; print_message(parser, output, gumbo_normalized_tagname(tag)); } gumbo_string_buffer_append_codepoint(parser, '.', output); @@ -136,6 +136,13 @@ static const char* find_last_newline( const char* original_text, const char* error_location) { assert(error_location >= original_text); const char* c = error_location; + // If the error location itself is a newline then start searching for the + // preceding newline one character earlier, if possible. See: + // https://github.com/rubys/nokogumbo/commit/bd623555730cdd260f6cec6d7cf990ff297da63d + // https://github.com/google/gumbo-parser/pull/371 + if (*c == '\n' && c != original_text) { + c -= 1; + } for (; c != original_text && *c != '\n'; --c) { // There may be an error at EOF, which would be a nul byte. assert(*c || c == error_location); diff --git a/libs/litehtml/src/gumbo/include/gumbo.h b/libs/litehtml/src/gumbo/include/gumbo.h index 27e6c6c575..f8137cf061 100644 --- a/libs/litehtml/src/gumbo/include/gumbo.h +++ b/libs/litehtml/src/gumbo/include/gumbo.h @@ -43,13 +43,9 @@ #define GUMBO_GUMBO_H_ #ifdef _MSC_VER -#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS -#endif -#ifndef fileno #define fileno _fileno #endif -#endif #include <stdbool.h> #include <stddef.h> diff --git a/libs/litehtml/src/gumbo/include/gumbo/error.h b/libs/litehtml/src/gumbo/include/gumbo/error.h index 3aa54a6b27..afc998b1fc 100644 --- a/libs/litehtml/src/gumbo/include/gumbo/error.h +++ b/libs/litehtml/src/gumbo/include/gumbo/error.h @@ -19,10 +19,8 @@ #ifndef GUMBO_ERROR_H_ #define GUMBO_ERROR_H_ #ifdef _MSC_VER -#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif -#endif #include <stdint.h> #include "gumbo.h" @@ -201,7 +199,7 @@ void gumbo_error_destroy(struct GumboInternalParser* parser, GumboError* error); // Prints an error to a string. This fills an empty GumboStringBuffer with a // freshly-allocated buffer containing the error message text. The caller is // responsible for deleting the buffer. (Note that the buffer is allocated with -// the allocator specified in the GumboParser ~config and hence should be freed +// the allocator specified in the GumboParser config and hence should be freed // by gumbo_parser_deallocate().) void gumbo_error_to_string(struct GumboInternalParser* parser, const GumboError* error, GumboStringBuffer* output); @@ -209,7 +207,7 @@ void gumbo_error_to_string(struct GumboInternalParser* parser, // Prints a caret diagnostic to a string. This fills an empty GumboStringBuffer // with a freshly-allocated buffer containing the error message text. The // caller is responsible for deleting the buffer. (Note that the buffer is -// allocated with the allocator specified in the GumboParser ~config and hence +// allocated with the allocator specified in the GumboParser config and hence // should be freed by gumbo_parser_deallocate().) void gumbo_caret_diagnostic_to_string(struct GumboInternalParser* parser, const GumboError* error, const char* source_text, diff --git a/libs/litehtml/src/gumbo/include/gumbo/tag_enum.h b/libs/litehtml/src/gumbo/include/gumbo/tag_enum.h index 6d7aeb3d7d..7a33d1e114 100644 --- a/libs/litehtml/src/gumbo/include/gumbo/tag_enum.h +++ b/libs/litehtml/src/gumbo/include/gumbo/tag_enum.h @@ -73,6 +73,7 @@ GUMBO_TAG_INS, GUMBO_TAG_DEL, GUMBO_TAG_IMAGE, GUMBO_TAG_IMG, +GUMBO_TAG_PICTURE, GUMBO_TAG_IFRAME, GUMBO_TAG_EMBED, GUMBO_TAG_OBJECT, @@ -149,5 +150,6 @@ GUMBO_TAG_MARQUEE, GUMBO_TAG_MULTICOL, GUMBO_TAG_NOBR, GUMBO_TAG_SPACER, +GUMBO_TAG_DIALOG, GUMBO_TAG_TT, GUMBO_TAG_RTC, diff --git a/libs/litehtml/src/gumbo/include/gumbo/tag_gperf.h b/libs/litehtml/src/gumbo/include/gumbo/tag_gperf.h index 378eaf958c..525453946f 100644 --- a/libs/litehtml/src/gumbo/include/gumbo/tag_gperf.h +++ b/libs/litehtml/src/gumbo/include/gumbo/tag_gperf.h @@ -1,105 +1,321 @@ -static unsigned int tag_hash( - register const char *str, register unsigned int len) { - static unsigned short asso_values[] = {296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 6, 4, 3, 1, 1, 0, - 1, 0, 0, 296, 296, 296, 296, 296, 296, 296, 22, 73, 151, 4, 13, 59, 65, 2, - 69, 0, 134, 9, 16, 52, 55, 28, 101, 0, 1, 6, 63, 126, 104, 93, 124, 296, - 296, 296, 296, 296, 296, 296, 22, 73, 151, 4, 13, 59, 65, 2, 69, 0, 134, - 9, 16, 52, 55, 28, 101, 0, 1, 6, 63, 126, 104, 93, 124, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, - 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296}; +static unsigned int tag_hash(register const char *str, register size_t len) +{ + static unsigned short asso_values[] = + { + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 30, + 27, 27, 9, 6, 3, 6, 6, 3, 274, 274, + 274, 274, 274, 274, 274, 78, 3, 171, 12, 30, + 39, 129, 12, 105, 24, 156, 9, 51, 60, 87, + 12, 96, 3, 6, 18, 75, 99, 96, 36, 123, + 274, 274, 274, 274, 274, 274, 274, 78, 3, 171, + 12, 30, 39, 129, 12, 105, 24, 156, 9, 51, + 60, 87, 12, 96, 3, 6, 18, 75, 99, 96, + 36, 123, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 274, 274, 274, 274 + }; register unsigned int hval = len; - switch (hval) { - default: - hval += asso_values[(unsigned char) str[1] + 3]; - /*FALLTHROUGH*/ - case 1: - hval += asso_values[(unsigned char) str[0]]; - break; - } - return hval + asso_values[(unsigned char) str[len - 1]]; + switch (hval) + { + default: + hval += asso_values[(unsigned char)str[1]+3]; + /*FALLTHROUGH*/ + case 1: + hval += asso_values[(unsigned char)str[0]]; + break; + } + return hval + asso_values[(unsigned char)str[len - 1]]; } -static const unsigned char kGumboTagMap[] = {GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_S, GUMBO_TAG_H6, GUMBO_TAG_H5, GUMBO_TAG_H4, - GUMBO_TAG_H3, GUMBO_TAG_SPACER, GUMBO_TAG_H2, GUMBO_TAG_HEADER, - GUMBO_TAG_H1, GUMBO_TAG_HEAD, GUMBO_TAG_LAST, GUMBO_TAG_DETAILS, - GUMBO_TAG_SELECT, GUMBO_TAG_DIR, GUMBO_TAG_LAST, GUMBO_TAG_DEL, - GUMBO_TAG_LAST, GUMBO_TAG_SOURCE, GUMBO_TAG_LEGEND, GUMBO_TAG_DATALIST, - GUMBO_TAG_METER, GUMBO_TAG_MGLYPH, GUMBO_TAG_LAST, GUMBO_TAG_MATH, - GUMBO_TAG_LABEL, GUMBO_TAG_TABLE, GUMBO_TAG_TEMPLATE, GUMBO_TAG_LAST, - GUMBO_TAG_RP, GUMBO_TAG_TIME, GUMBO_TAG_TITLE, GUMBO_TAG_DATA, - GUMBO_TAG_APPLET, GUMBO_TAG_HGROUP, GUMBO_TAG_SAMP, GUMBO_TAG_TEXTAREA, - GUMBO_TAG_ABBR, GUMBO_TAG_MARQUEE, GUMBO_TAG_LAST, GUMBO_TAG_MENUITEM, - GUMBO_TAG_SMALL, GUMBO_TAG_META, GUMBO_TAG_A, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_EMBED, - GUMBO_TAG_MAP, GUMBO_TAG_LAST, GUMBO_TAG_PARAM, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_NOBR, GUMBO_TAG_P, GUMBO_TAG_SPAN, GUMBO_TAG_EM, - GUMBO_TAG_LAST, GUMBO_TAG_NOFRAMES, GUMBO_TAG_SECTION, GUMBO_TAG_NOEMBED, - GUMBO_TAG_NEXTID, GUMBO_TAG_FOOTER, GUMBO_TAG_NOSCRIPT, GUMBO_TAG_HR, - GUMBO_TAG_LAST, GUMBO_TAG_FONT, GUMBO_TAG_DL, GUMBO_TAG_TR, - GUMBO_TAG_SCRIPT, GUMBO_TAG_MO, GUMBO_TAG_LAST, GUMBO_TAG_DD, - GUMBO_TAG_MAIN, GUMBO_TAG_TD, GUMBO_TAG_FOREIGNOBJECT, GUMBO_TAG_FORM, - GUMBO_TAG_OBJECT, GUMBO_TAG_LAST, GUMBO_TAG_FIELDSET, GUMBO_TAG_LAST, - GUMBO_TAG_BGSOUND, GUMBO_TAG_MENU, GUMBO_TAG_TFOOT, GUMBO_TAG_FIGURE, - GUMBO_TAG_RB, GUMBO_TAG_LI, GUMBO_TAG_LISTING, GUMBO_TAG_BASEFONT, - GUMBO_TAG_OPTGROUP, GUMBO_TAG_LAST, GUMBO_TAG_BASE, GUMBO_TAG_ADDRESS, - GUMBO_TAG_MI, GUMBO_TAG_LAST, GUMBO_TAG_PLAINTEXT, GUMBO_TAG_LAST, - GUMBO_TAG_PROGRESS, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_ACRONYM, GUMBO_TAG_ARTICLE, GUMBO_TAG_LAST, GUMBO_TAG_PRE, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_AREA, - GUMBO_TAG_RT, GUMBO_TAG_LAST, GUMBO_TAG_OPTION, GUMBO_TAG_IMAGE, - GUMBO_TAG_DT, GUMBO_TAG_LAST, GUMBO_TAG_TT, GUMBO_TAG_HTML, GUMBO_TAG_WBR, - GUMBO_TAG_OL, GUMBO_TAG_LAST, GUMBO_TAG_STYLE, GUMBO_TAG_STRIKE, - GUMBO_TAG_SUP, GUMBO_TAG_MULTICOL, GUMBO_TAG_U, GUMBO_TAG_DFN, GUMBO_TAG_UL, - GUMBO_TAG_FIGCAPTION, GUMBO_TAG_MTEXT, GUMBO_TAG_LAST, GUMBO_TAG_VAR, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_FRAMESET, GUMBO_TAG_LAST, - GUMBO_TAG_BR, GUMBO_TAG_I, GUMBO_TAG_FRAME, GUMBO_TAG_LAST, GUMBO_TAG_DIV, - GUMBO_TAG_LAST, GUMBO_TAG_TH, GUMBO_TAG_MS, GUMBO_TAG_ANNOTATION_XML, - GUMBO_TAG_B, GUMBO_TAG_TBODY, GUMBO_TAG_THEAD, GUMBO_TAG_BIG, - GUMBO_TAG_BLOCKQUOTE, GUMBO_TAG_XMP, GUMBO_TAG_LAST, GUMBO_TAG_KBD, - GUMBO_TAG_LAST, GUMBO_TAG_LINK, GUMBO_TAG_IFRAME, GUMBO_TAG_MARK, - GUMBO_TAG_CENTER, GUMBO_TAG_OUTPUT, GUMBO_TAG_DESC, GUMBO_TAG_CANVAS, - GUMBO_TAG_COL, GUMBO_TAG_MALIGNMARK, GUMBO_TAG_IMG, GUMBO_TAG_ASIDE, - GUMBO_TAG_LAST, GUMBO_TAG_CODE, GUMBO_TAG_LAST, GUMBO_TAG_SUB, GUMBO_TAG_MN, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_INS, GUMBO_TAG_AUDIO, - GUMBO_TAG_STRONG, GUMBO_TAG_CITE, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_INPUT, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_NAV, GUMBO_TAG_LAST, GUMBO_TAG_COLGROUP, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_SVG, GUMBO_TAG_KEYGEN, GUMBO_TAG_VIDEO, - GUMBO_TAG_BDO, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_BODY, GUMBO_TAG_LAST, GUMBO_TAG_Q, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_TRACK, - GUMBO_TAG_LAST, GUMBO_TAG_BDI, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_CAPTION, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_RUBY, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_BUTTON, - GUMBO_TAG_SUMMARY, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_RTC, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_BLINK, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, - GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_ISINDEX}; +static const unsigned char kGumboTagMap[] = { + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_B, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_S, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_H6, + GUMBO_TAG_SPACER, + GUMBO_TAG_LAST, + GUMBO_TAG_RP, + GUMBO_TAG_LAST, + GUMBO_TAG_P, + GUMBO_TAG_H5, + GUMBO_TAG_DIR, + GUMBO_TAG_LAST, + GUMBO_TAG_H4, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_SMALL, + GUMBO_TAG_HEADER, + GUMBO_TAG_SAMP, + GUMBO_TAG_LABEL, + GUMBO_TAG_DEL, + GUMBO_TAG_DETAILS, + GUMBO_TAG_RB, + GUMBO_TAG_LEGEND, + GUMBO_TAG_HEAD, + GUMBO_TAG_BASEFONT, + GUMBO_TAG_SELECT, + GUMBO_TAG_LAST, + GUMBO_TAG_H3, + GUMBO_TAG_SOURCE, + GUMBO_TAG_BGSOUND, + GUMBO_TAG_H2, + GUMBO_TAG_SUB, + GUMBO_TAG_BASE, + GUMBO_TAG_DATALIST, + GUMBO_TAG_FOOTER, + GUMBO_TAG_LAST, + GUMBO_TAG_H1, + GUMBO_TAG_HGROUP, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_SUP, + GUMBO_TAG_PICTURE, + GUMBO_TAG_EMBED, + GUMBO_TAG_LAST, + GUMBO_TAG_TIME, + GUMBO_TAG_TITLE, + GUMBO_TAG_XMP, + GUMBO_TAG_FONT, + GUMBO_TAG_TABLE, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_TEMPLATE, + GUMBO_TAG_SCRIPT, + GUMBO_TAG_NOBR, + GUMBO_TAG_METER, + GUMBO_TAG_LAST, + GUMBO_TAG_FOREIGNOBJECT, + GUMBO_TAG_FIELDSET, + GUMBO_TAG_LAST, + GUMBO_TAG_SPAN, + GUMBO_TAG_NOFRAMES, + GUMBO_TAG_MAP, + GUMBO_TAG_MATH, + GUMBO_TAG_PARAM, + GUMBO_TAG_LAST, + GUMBO_TAG_NOEMBED, + GUMBO_TAG_BR, + GUMBO_TAG_FIGURE, + GUMBO_TAG_SECTION, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_NOSCRIPT, + GUMBO_TAG_NEXTID, + GUMBO_TAG_LAST, + GUMBO_TAG_HR, + GUMBO_TAG_MGLYPH, + GUMBO_TAG_LAST, + GUMBO_TAG_EM, + GUMBO_TAG_LAST, + GUMBO_TAG_FORM, + GUMBO_TAG_TR, + GUMBO_TAG_LAST, + GUMBO_TAG_MARQUEE, + GUMBO_TAG_PROGRESS, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_MULTICOL, + GUMBO_TAG_BUTTON, + GUMBO_TAG_DATA, + GUMBO_TAG_LAST, + GUMBO_TAG_APPLET, + GUMBO_TAG_LAST, + GUMBO_TAG_DL, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_OPTGROUP, + GUMBO_TAG_LAST, + GUMBO_TAG_ABBR, + GUMBO_TAG_TEXTAREA, + GUMBO_TAG_VAR, + GUMBO_TAG_FIGCAPTION, + GUMBO_TAG_RT, + GUMBO_TAG_PRE, + GUMBO_TAG_HTML, + GUMBO_TAG_MENUITEM, + GUMBO_TAG_DIV, + GUMBO_TAG_LAST, + GUMBO_TAG_LI, + GUMBO_TAG_PLAINTEXT, + GUMBO_TAG_MAIN, + GUMBO_TAG_DT, + GUMBO_TAG_LAST, + GUMBO_TAG_BLOCKQUOTE, + GUMBO_TAG_LAST, + GUMBO_TAG_WBR, + GUMBO_TAG_BODY, + GUMBO_TAG_TT, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_STYLE, + GUMBO_TAG_STRIKE, + GUMBO_TAG_LAST, + GUMBO_TAG_FRAMESET, + GUMBO_TAG_OBJECT, + GUMBO_TAG_MENU, + GUMBO_TAG_MO, + GUMBO_TAG_BIG, + GUMBO_TAG_META, + GUMBO_TAG_TFOOT, + GUMBO_TAG_OUTPUT, + GUMBO_TAG_LAST, + GUMBO_TAG_FRAME, + GUMBO_TAG_LAST, + GUMBO_TAG_U, + GUMBO_TAG_IMAGE, + GUMBO_TAG_LAST, + GUMBO_TAG_LISTING, + GUMBO_TAG_DD, + GUMBO_TAG_DIALOG, + GUMBO_TAG_A, + GUMBO_TAG_MS, + GUMBO_TAG_OPTION, + GUMBO_TAG_LAST, + GUMBO_TAG_TD, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_RUBY, + GUMBO_TAG_MI, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_MTEXT, + GUMBO_TAG_LAST, + GUMBO_TAG_SUMMARY, + GUMBO_TAG_UL, + GUMBO_TAG_NAV, + GUMBO_TAG_ACRONYM, + GUMBO_TAG_TBODY, + GUMBO_TAG_LAST, + GUMBO_TAG_LINK, + GUMBO_TAG_LAST, + GUMBO_TAG_DFN, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_OL, + GUMBO_TAG_COL, + GUMBO_TAG_LAST, + GUMBO_TAG_TH, + GUMBO_TAG_LAST, + GUMBO_TAG_ARTICLE, + GUMBO_TAG_THEAD, + GUMBO_TAG_CENTER, + GUMBO_TAG_Q, + GUMBO_TAG_COLGROUP, + GUMBO_TAG_CANVAS, + GUMBO_TAG_LAST, + GUMBO_TAG_ANNOTATION_XML, + GUMBO_TAG_LAST, + GUMBO_TAG_DESC, + GUMBO_TAG_VIDEO, + GUMBO_TAG_KBD, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_AUDIO, + GUMBO_TAG_LAST, + GUMBO_TAG_CODE, + GUMBO_TAG_MN, + GUMBO_TAG_INS, + GUMBO_TAG_I, + GUMBO_TAG_ASIDE, + GUMBO_TAG_LAST, + GUMBO_TAG_CITE, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_ADDRESS, + GUMBO_TAG_LAST, + GUMBO_TAG_BDO, + GUMBO_TAG_MARK, + GUMBO_TAG_INPUT, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_MALIGNMARK, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_KEYGEN, + GUMBO_TAG_AREA, + GUMBO_TAG_LAST, + GUMBO_TAG_STRONG, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_BDI, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_IFRAME, + GUMBO_TAG_ISINDEX, + GUMBO_TAG_LAST, + GUMBO_TAG_IMG, + GUMBO_TAG_CAPTION, + GUMBO_TAG_BLINK, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_TRACK, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_SVG, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_LAST, + GUMBO_TAG_RTC +}; diff --git a/libs/litehtml/src/gumbo/include/gumbo/tag_sizes.h b/libs/litehtml/src/gumbo/include/gumbo/tag_sizes.h index 7c92de073b..5b93c22fa2 100644 --- a/libs/litehtml/src/gumbo/include/gumbo/tag_sizes.h +++ b/libs/litehtml/src/gumbo/include/gumbo/tag_sizes.h @@ -1,4 +1,4 @@ // Generated via `gentags.py src/tag.in`. // Do not edit; edit src/tag.in instead. // clang-format off -4, 4, 5, 4, 4, 4, 5, 6, 8, 8, 4, 7, 7, 3, 5, 2, 2, 2, 2, 2, 2, 6, 6, 6, 7, 1, 2, 3, 10, 2, 2, 2, 2, 2, 2, 6, 10, 4, 3, 1, 2, 6, 5, 1, 4, 1, 3, 4, 4, 4, 4, 3, 4, 3, 3, 3, 1, 1, 1, 4, 4, 2, 2, 3, 3, 4, 2, 3, 3, 3, 5, 3, 6, 5, 6, 5, 5, 5, 6, 5, 6, 3, 4, 4, 2, 2, 2, 2, 5, 6, 10, 14, 3, 13, 4, 5, 7, 8, 3, 5, 5, 5, 2, 2, 2, 4, 8, 6, 5, 5, 6, 6, 8, 8, 6, 8, 6, 6, 8, 5, 7, 7, 4, 8, 6, 7, 7, 3, 5, 8, 8, 7, 7, 3, 6, 7, 9, 2, 6, 8, 3, 5, 6, 4, 7, 8, 4, 6, 2, 3,
\ No newline at end of file +4, 4, 5, 4, 4, 4, 5, 6, 8, 8, 4, 7, 7, 3, 5, 2, 2, 2, 2, 2, 2, 6, 6, 6, 7, 1, 2, 3, 10, 2, 2, 2, 2, 2, 2, 6, 10, 4, 3, 1, 2, 6, 5, 1, 4, 1, 3, 4, 4, 4, 4, 3, 4, 3, 3, 3, 1, 1, 1, 4, 4, 2, 2, 3, 3, 4, 2, 3, 3, 3, 5, 3, 7, 6, 5, 6, 5, 5, 5, 6, 5, 6, 3, 4, 4, 2, 2, 2, 2, 5, 6, 10, 14, 3, 13, 4, 5, 7, 8, 3, 5, 5, 5, 2, 2, 2, 4, 8, 6, 5, 5, 6, 6, 8, 8, 6, 8, 6, 6, 8, 5, 7, 7, 4, 8, 6, 7, 7, 3, 5, 8, 8, 7, 7, 3, 6, 7, 9, 2, 6, 8, 3, 5, 6, 4, 7, 8, 4, 6, 6, 2, 3,
\ No newline at end of file diff --git a/libs/litehtml/src/gumbo/include/gumbo/tag_strings.h b/libs/litehtml/src/gumbo/include/gumbo/tag_strings.h index 6540e2e6ba..03d793c05d 100644 --- a/libs/litehtml/src/gumbo/include/gumbo/tag_strings.h +++ b/libs/litehtml/src/gumbo/include/gumbo/tag_strings.h @@ -73,6 +73,7 @@ "del", "image", "img", +"picture", "iframe", "embed", "object", @@ -149,5 +150,6 @@ "multicol", "nobr", "spacer", +"dialog", "tt", "rtc", diff --git a/libs/litehtml/src/gumbo/include/gumbo/utf8.h b/libs/litehtml/src/gumbo/include/gumbo/utf8.h index ee852abfba..bd31a781ee 100644 --- a/libs/litehtml/src/gumbo/include/gumbo/utf8.h +++ b/libs/litehtml/src/gumbo/include/gumbo/utf8.h @@ -62,7 +62,7 @@ typedef struct GumboInternalUtf8Iterator { int _current; // The width in bytes of the current code point. - ptrdiff_t _width; + int _width; // The SourcePosition for the current location. GumboSourcePosition _pos; diff --git a/libs/litehtml/src/gumbo/include/gumbo/util.h b/libs/litehtml/src/gumbo/include/gumbo/util.h index 98a7d1c466..6ad65649e6 100644 --- a/libs/litehtml/src/gumbo/include/gumbo/util.h +++ b/libs/litehtml/src/gumbo/include/gumbo/util.h @@ -20,10 +20,8 @@ #ifndef GUMBO_UTIL_H_ #define GUMBO_UTIL_H_ #ifdef _MSC_VER -#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif -#endif #include <stdbool.h> #include <stddef.h> @@ -43,12 +41,12 @@ struct GumboInternalParser; char* gumbo_copy_stringz(struct GumboInternalParser* parser, const char* str); // Allocate a chunk of memory, using the allocator specified in the Parser's -// ~config options. +// config options. void* gumbo_parser_allocate( struct GumboInternalParser* parser, size_t num_bytes); // Deallocate a chunk of memory, using the deallocator specified in the Parser's -// ~config options. +// config options. void gumbo_parser_deallocate(struct GumboInternalParser* parser, void* ptr); // Debug wrapper for printf, to make it easier to turn off debugging info when diff --git a/libs/litehtml/src/gumbo/parser.c b/libs/litehtml/src/gumbo/parser.c index 653fd85acc..968fcc0f41 100644 --- a/libs/litehtml/src/gumbo/parser.c +++ b/libs/litehtml/src/gumbo/parser.c @@ -45,7 +45,7 @@ typedef char gumbo_tagset[GUMBO_TAG_LAST]; #define TAG_MATHML(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_MATHML) #define TAGSET_INCLUDES(tagset, namespace, tag) \ - (tag < GUMBO_TAG_LAST && tagset[(int) tag] == (1 << (int) namespace)) + (tag < GUMBO_TAG_LAST && tagset[(int) tag] & (1 << (int) namespace)) // selected forward declarations as it is getting hard to find // an appropriate order @@ -572,6 +572,10 @@ static GumboInsertionMode get_appropriate_insertion_mode( } assert(node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE); + if (node->v.element.tag_namespace != GUMBO_NAMESPACE_HTML) + return is_last ? + GUMBO_INSERTION_MODE_IN_BODY : GUMBO_INSERTION_MODE_INITIAL; + switch (node->v.element.tag) { case GUMBO_TAG_SELECT: { if (is_last) { @@ -812,7 +816,7 @@ InsertionLocation get_appropriate_insertion_location( GumboNode* last_table = open_elements->data[last_table_index]; if (last_table->parent != NULL) { retval.target = last_table->parent; - retval.index = (int)last_table->index_within_parent; + retval.index = last_table->index_within_parent; return retval; } @@ -2512,8 +2516,8 @@ static bool handle_in_body(GumboParser* parser, GumboToken* token) { return success; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(ADDRESS), TAG(ARTICLE), TAG(ASIDE), - TAG(BLOCKQUOTE), TAG(CENTER), TAG(DETAILS), TAG(DIR), - TAG(DIV), TAG(DL), TAG(FIELDSET), TAG(FIGCAPTION), + TAG(BLOCKQUOTE), TAG(CENTER), TAG(DETAILS), TAG(DIALOG), + TAG(DIR), TAG(DIV), TAG(DL), TAG(FIELDSET), TAG(FIGCAPTION), TAG(FIGURE), TAG(FOOTER), TAG(HEADER), TAG(HGROUP), TAG(MENU), TAG(MAIN), TAG(NAV), TAG(OL), TAG(P), TAG(SECTION), TAG(SUMMARY), TAG(UL)})) { @@ -2582,7 +2586,7 @@ static bool handle_in_body(GumboParser* parser, GumboToken* token) { } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(ADDRESS), TAG(ARTICLE), TAG(ASIDE), TAG(BLOCKQUOTE), TAG(BUTTON), TAG(CENTER), TAG(DETAILS), - TAG(DIR), TAG(DIV), TAG(DL), TAG(FIELDSET), + TAG(DIALOG), TAG(DIR), TAG(DIV), TAG(DL), TAG(FIELDSET), TAG(FIGCAPTION), TAG(FIGURE), TAG(FOOTER), TAG(HEADER), TAG(HGROUP), TAG(LISTING), TAG(MAIN), TAG(MENU), TAG(NAV), TAG(OL), TAG(PRE), TAG(SECTION), TAG(SUMMARY), TAG(UL)})) { @@ -2613,7 +2617,7 @@ static bool handle_in_body(GumboParser* parser, GumboToken* token) { return success; } else { bool result = true; - const GumboNode* node = state->_form_element; + GumboNode* node = state->_form_element; assert(!node || node->type == GUMBO_NODE_ELEMENT); state->_form_element = NULL; if (!node || !has_node_in_scope(parser, node)) { @@ -2622,10 +2626,15 @@ static bool handle_in_body(GumboParser* parser, GumboToken* token) { ignore_token(parser); return false; } + // Since we remove the form node without popping, we need to make sure + // that we flush any text nodes at the end of the form. + maybe_flush_text_node_buffer(parser); // This differs from implicitly_close_tags because we remove *only* the // <form> element; other nodes are left in scope. generate_implied_end_tags(parser, GUMBO_TAG_LAST); - if (get_current_node(parser) != node) { + if (get_current_node(parser) == node) { + record_end_of_element(token, &node->v.element); + } else { parser_add_parse_error(parser, token); result = false; } @@ -2845,7 +2854,7 @@ static bool handle_in_body(GumboParser* parser, GumboToken* token) { text_state->_start_position = token->position; text_state->_type = GUMBO_NODE_TEXT; if (prompt_attr) { - size_t prompt_attr_length = strlen(prompt_attr->value); + int prompt_attr_length = strlen(prompt_attr->value); gumbo_string_buffer_destroy(parser, &text_state->_buffer); text_state->_buffer.data = gumbo_copy_stringz(parser, prompt_attr->value); text_state->_buffer.length = prompt_attr_length; diff --git a/libs/litehtml/src/gumbo/tag.c b/libs/litehtml/src/gumbo/tag.c index a394c0a677..85d58d28f0 100644 --- a/libs/litehtml/src/gumbo/tag.c +++ b/libs/litehtml/src/gumbo/tag.c @@ -41,7 +41,6 @@ void gumbo_tag_from_original_text(GumboStringPiece* text) { if (text->data == NULL) { return; } - assert(text->length >= 2); assert(text->data[0] == '<'); assert(text->data[text->length - 1] == '>'); @@ -54,10 +53,10 @@ void gumbo_tag_from_original_text(GumboStringPiece* text) { // Start tag. text->data += 1; // Move past < text->length -= 2; - // strnchr is apparently not a standard C library function, so I loop // explicitly looking for whitespace or other illegal tag characters. + // see https://html.spec.whatwg.org/multipage/syntax.html#tag-name-state for (const char* c = text->data; c != text->data + text->length; ++c) { - if (isspace(*c) || *c == '/') { + if (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\f' || *c == '/') { text->length = c - text->data; break; } @@ -91,5 +90,5 @@ GumboTag gumbo_tagn_enum(const char* tagname, unsigned int length) { } GumboTag gumbo_tag_enum(const char* tagname) { - return gumbo_tagn_enum(tagname, (unsigned)strlen(tagname)); + return gumbo_tagn_enum(tagname, strlen(tagname)); } diff --git a/libs/litehtml/src/gumbo/tag.in b/libs/litehtml/src/gumbo/tag.in index 4c25264857..5716462914 100644 --- a/libs/litehtml/src/gumbo/tag.in +++ b/libs/litehtml/src/gumbo/tag.in @@ -70,6 +70,7 @@ ins del image img +picture iframe embed object @@ -146,5 +147,6 @@ marquee multicol nobr spacer +dialog tt rtc diff --git a/libs/litehtml/src/gumbo/tokenizer.c b/libs/litehtml/src/gumbo/tokenizer.c index 0d0ea0f241..71b5d32ad4 100644 --- a/libs/litehtml/src/gumbo/tokenizer.c +++ b/libs/litehtml/src/gumbo/tokenizer.c @@ -377,7 +377,7 @@ static bool temporary_buffer_equals(GumboParser* parser, const char* text) { // TODO(jdtang): See if the extra strlen is a performance problem, and replace // it with an explicit sizeof(literal) if necessary. I don't think it will // be, as this is only used in a couple of rare states. - size_t text_len = strlen(text); + int text_len = strlen(text); return text_len == buffer->length && memcmp(buffer->data, text, text_len) == 0; } @@ -726,7 +726,8 @@ static void copy_over_original_tag_text(GumboParser* parser, original_text->data = tag_state->_original_text; original_text->length = utf8iterator_get_char_pointer(&tokenizer->_input) - tag_state->_original_text; - if (original_text->data[original_text->length - 1] == '\r') { + if (original_text->length > 0 + && original_text->data[original_text->length - 1] == '\r') { // Since \r is skipped by the UTF-8 iterator, it can sometimes end up // appended to the end of original text even when it's really the first part // of the next character. If we detect this situation, shrink the length of @@ -751,7 +752,7 @@ static void finish_tag_name(GumboParser* parser) { GumboTagState* tag_state = &tokenizer->_tag_state; tag_state->_tag = - gumbo_tagn_enum(tag_state->_buffer.data, (unsigned)tag_state->_buffer.length); + gumbo_tagn_enum(tag_state->_buffer.data, tag_state->_buffer.length); reinitialize_tag_buffer(parser); } @@ -839,7 +840,7 @@ static bool is_appropriate_end_tag(GumboParser* parser) { assert(!tag_state->_is_start_tag); return tag_state->_last_start_tag != GUMBO_TAG_LAST && tag_state->_last_start_tag == gumbo_tagn_enum(tag_state->_buffer.data, - (unsigned)tag_state->_buffer.length); + tag_state->_buffer.length); } void gumbo_tokenizer_state_init( diff --git a/libs/litehtml/src/gumbo/utf8.c b/libs/litehtml/src/gumbo/utf8.c index ad73cefa6a..fdd6f8377c 100644 --- a/libs/litehtml/src/gumbo/utf8.c +++ b/libs/litehtml/src/gumbo/utf8.c @@ -137,7 +137,7 @@ static void read_char(Utf8Iterator* iter) { for (const char* c = iter->_start; c < iter->_end; ++c) { decode(&state, &code_point, (uint32_t)(unsigned char) (*c)); if (state == UTF8_ACCEPT) { - iter->_width = (int)(c - iter->_start + 1); + iter->_width = c - iter->_start + 1; // This is the special handling for carriage returns that is mandated by // the HTML5 spec. Since we're looking for particular 7-bit literal // characters, we operate in terms of chars and only need a check for iter @@ -181,7 +181,7 @@ static void read_char(Utf8Iterator* iter) { } static void update_position(Utf8Iterator* iter) { - iter->_pos.offset += (int)iter->_width; + iter->_pos.offset += iter->_width; if (iter->_current == '\n') { ++iter->_pos.line; iter->_pos.column = 1; diff --git a/libs/litehtml/src/gumbo/vector.c b/libs/litehtml/src/gumbo/vector.c index b9aa474e67..51758dfe03 100644 --- a/libs/litehtml/src/gumbo/vector.c +++ b/libs/litehtml/src/gumbo/vector.c @@ -30,7 +30,7 @@ const GumboVector kGumboEmptyVector = {NULL, 0, 0}; void gumbo_vector_init(struct GumboInternalParser* parser, size_t initial_capacity, GumboVector* vector) { vector->length = 0; - vector->capacity = (unsigned)initial_capacity; + vector->capacity = initial_capacity; if (initial_capacity > 0) { vector->data = gumbo_parser_allocate(parser, sizeof(void*) * initial_capacity); diff --git a/libs/litehtml/src/html.cpp b/libs/litehtml/src/html.cpp index a7ea9f9032..27f1cfe55d 100644 --- a/libs/litehtml/src/html.cpp +++ b/libs/litehtml/src/html.cpp @@ -4,7 +4,7 @@ namespace litehtml { -string& trim(string &s, const string& chars_to_trim) +string& trim(string& s, const string& chars_to_trim) { string::size_type pos = s.find_first_not_of(chars_to_trim); if(pos != string::npos) @@ -24,15 +24,23 @@ string& trim(string &s, const string& chars_to_trim) return s; } -void lcase(string &s) +string trim(const string& s, const string& chars_to_trim) +{ + string str = s; + trim(str, chars_to_trim); + return str; +} + +string& lcase(string& s) { for(char & i : s) { i = (char)t_tolower(i); } + return s; } -string::size_type find_close_bracket(const string &s, string::size_type off, char open_b, char close_b) +string::size_type find_close_bracket(const string& s, string::size_type off, char open_b, char close_b) { int cnt = 0; for(string::size_type i = off; i < s.length(); i++) @@ -112,6 +120,13 @@ 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 result; + split_string(str, result, delims, delims_preserve, quote); + return result; +} + void split_string(const string& str, string_vector& tokens, const string& delims, const string& delims_preserve, const string& quote) { if(str.empty() || (delims.empty() && delims_preserve.empty())) diff --git a/libs/litehtml/src/html_tag.cpp b/libs/litehtml/src/html_tag.cpp index 02b4b23596..54a8dd404a 100644 --- a/libs/litehtml/src/html_tag.cpp +++ b/libs/litehtml/src/html_tag.cpp @@ -1,16 +1,18 @@ +#include <algorithm> + #include "html.h" #include "html_tag.h" #include "document.h" #include "iterators.h" #include "stylesheet.h" #include "table.h" -#include <algorithm> -#include <locale> -#include "el_before_after.h" #include "num_cvt.h" #include "line_box.h" -#include <stack> #include "render_item.h" +#include "internal.h" + +namespace litehtml +{ litehtml::html_tag::html_tag(const std::shared_ptr<document>& doc) : element(doc) { @@ -61,57 +63,56 @@ void litehtml::html_tag::clearRecursive() m_children.clear(); } -litehtml::string_id litehtml::html_tag::id() const +string_id html_tag::id() const { return m_id; } -litehtml::string_id litehtml::html_tag::tag() const +string_id html_tag::tag() const { return m_tag; } -const char* litehtml::html_tag::get_tagName() const +const char* html_tag::get_tagName() const { return _s(m_tag).c_str(); } -void litehtml::html_tag::set_tagName( const char* _tag ) +void html_tag::set_tagName( const char* tag ) { - string tag = _tag; - lcase(tag); - m_tag = _id(tag); + m_tag = _id(lowcase(tag)); } -void litehtml::html_tag::set_attr( const char* _name, const char* _val ) +void html_tag::set_attr( const char* _name, const char* _val ) { - if(_name && _val) + if (_name && _val) { - string name = _name; - lcase(name); + // attribute names in attribute selector are matched ASCII case-insensitively regardless of document mode + string name = lowcase(_name); + // m_attrs has all attribute values, including class and id, in their original case + // because in attribute selector values are matched case-sensitively even in quirks mode m_attrs[name] = _val; - if( name == "class" ) + if (name == "class") { string val = _val; - // class names are matched case-insensitively in quirks mode - // we match them case-insensitively in all modes (same for id) - lcase(val); - m_str_classes.resize( 0 ); - split_string( val, m_str_classes, " " ); + // class names in class selector (.xxx) are matched ASCII case-insensitively in quirks mode + if (get_document()->mode() == quirks_mode) lcase(val); + m_str_classes = split_string(val, whitespace, "", ""); m_classes.clear(); - for (auto& cls : m_str_classes) m_classes.push_back(_id(cls)); + for (auto cls : m_str_classes) m_classes.push_back(_id(cls)); } else if (name == "id") { string val = _val; - lcase(val); + // ids in id selector (#xxx) are matched ASCII case-insensitively in quirks mode + if (get_document()->mode() == quirks_mode) lcase(val); m_id = _id(val); } } } -const char* litehtml::html_tag::get_attr( const char* name, const char* def ) const +const char* html_tag::get_attr( const char* name, const char* def ) const { auto attr = m_attrs.find(name); if(attr != m_attrs.end()) @@ -124,7 +125,7 @@ const char* litehtml::html_tag::get_attr( const char* name, const char* def ) co litehtml::elements_list litehtml::html_tag::select_all(const string& selector ) { css_selector sel; - sel.parse(selector); + sel.parse(selector, get_document()->mode()); return select_all(sel); } @@ -153,7 +154,7 @@ void litehtml::html_tag::select_all(const css_selector& selector, elements_list& litehtml::element::ptr litehtml::html_tag::select_one( const string& selector ) { css_selector sel; - sel.parse(selector); + sel.parse(selector, get_document()->mode()); return select_one(sel); } @@ -189,8 +190,7 @@ void litehtml::html_tag::apply_stylesheet( const litehtml::css& stylesheet ) if (!r.m_attrs.empty()) { const auto& attr = r.m_attrs[0]; - if (attr.type == select_class && - std::find(m_classes.begin(), m_classes.end(), attr.name) == m_classes.end()) + if (attr.type == select_class && !(attr.name in m_classes)) continue; } } @@ -199,7 +199,7 @@ void litehtml::html_tag::apply_stylesheet( const litehtml::css& stylesheet ) if(apply != select_no_match) { - used_selector::ptr us = std::unique_ptr<used_selector>(new used_selector(sel, false)); + used_selector::ptr us = std::make_unique<used_selector>(sel, false); if(sel->is_media_valid()) { @@ -295,7 +295,8 @@ void litehtml::html_tag::draw(uint_ptr hdc, int x, int y, const position *clip, draw_background(hdc, x, y, clip, ri); - if(m_css.get_display() == display_list_item && m_css.get_list_style_type() != list_style_type_none) + if(m_css.get_display() == display_list_item && + (m_css.get_list_style_type() != list_style_type_none || m_css.get_list_style_image() != "")) { if(m_css.get_overflow() > overflow_visible) { @@ -320,19 +321,20 @@ void litehtml::html_tag::draw(uint_ptr hdc, int x, int y, const position *clip, } } -litehtml::string litehtml::html_tag::get_custom_property(string_id name, const string& default_value) const +bool html_tag::get_custom_property(string_id name, css_token_vector& result) const { const property_value& value = m_style.get_property(name); - if (value.is<string>()) + if (value.is<css_token_vector>()) { - return value.get<string>(); + result = value.get<css_token_vector>(); + return true; } else if (auto _parent = dynamic_cast<html_tag*>(parent().get())) { - return _parent->get_custom_property(name, default_value); + return _parent->get_custom_property(name, result); } - return default_value; + return false; } void litehtml::html_tag::compute_styles(bool recursive) @@ -363,10 +365,20 @@ bool litehtml::html_tag::is_white_space() const return false; } +int html_tag::select(const css_selector::vector& selector_list, bool apply_pseudo) +{ + for (auto sel : selector_list) + { + if (int result = select(*sel, apply_pseudo)) + return result; + } + return select_no_match; +} + int litehtml::html_tag::select(const string& selector) { css_selector sel; - sel.parse(selector); + sel.parse(selector, get_document()->mode()); return select(sel, true); } @@ -470,7 +482,7 @@ int litehtml::html_tag::select(const css_element_selector& selector, bool apply_ switch(attr.type) { case select_class: - if (std::find(m_classes.begin(), m_classes.end(), attr.name) == m_classes.end()) + if (!(attr.name in m_classes)) { return select_no_match; } @@ -523,7 +535,7 @@ int litehtml::html_tag::select(const css_element_selector& selector, bool apply_ return res; } -int litehtml::html_tag::select_pseudoclass(const css_attribute_selector& sel) +int html_tag::select_pseudoclass(const css_attribute_selector& sel) { element::ptr el_parent = parent(); @@ -579,7 +591,7 @@ int litehtml::html_tag::select_pseudoclass(const css_attribute_selector& sel) switch (sel.name) { case _nth_child_: - if (!el_parent->is_nth_child(shared_from_this(), num, off, false)) + if (!el_parent->is_nth_child(shared_from_this(), num, off, false, sel.selector_list)) { return select_no_match; } @@ -591,7 +603,7 @@ int litehtml::html_tag::select_pseudoclass(const css_attribute_selector& sel) } break; case _nth_last_child_: - if (!el_parent->is_nth_last_child(shared_from_this(), num, off, false)) + if (!el_parent->is_nth_last_child(shared_from_this(), num, off, false, sel.selector_list)) { return select_no_match; } @@ -608,20 +620,26 @@ int litehtml::html_tag::select_pseudoclass(const css_attribute_selector& sel) } break; + case _is_: + if (!select(sel.selector_list, true)) + { + return select_no_match; + } + break; case _not_: - if (select(*sel.sel, true)) + if (select(sel.selector_list, true)) { return select_no_match; } break; case _lang_: - if (!get_document()->match_lang(sel.val)) + if (!get_document()->match_lang(sel.value)) { return select_no_match; } break; default: - if (std::find(m_pseudo_classes.begin(), m_pseudo_classes.end(), sel.name) == m_pseudo_classes.end()) + if (!(sel.name in m_pseudo_classes)) { return select_no_match; } @@ -630,61 +648,69 @@ int litehtml::html_tag::select_pseudoclass(const css_attribute_selector& sel) return select_match; } -int litehtml::html_tag::select_attribute(const css_attribute_selector& sel) +// https://www.w3.org/TR/selectors-4/#attribute-selectors +int html_tag::select_attribute(const css_attribute_selector& sel) { - const char* attr_value = get_attr(_s(sel.name).c_str()); + const char* sz_attr_value = get_attr(_s(sel.name).c_str()); + + if (!sz_attr_value) return select_no_match; + + string attr_value = sel.caseless_match ? lowcase(sz_attr_value) : sz_attr_value; - switch (sel.type) + switch (sel.matcher) { - case select_exists: - if (!attr_value) + case attribute_exists: + return select_match; + + case attribute_equals: + if (attr_value == sel.value) { - return select_no_match; + return select_match; } break; - case select_equal: - if (!attr_value || strcmp(attr_value, sel.val.c_str())) + + case attribute_contains_string: // *= + if (sel.value != "" && contains(attr_value, sel.value)) { - return select_no_match; + return select_match; } break; - case select_contain_str: - if (!attr_value || !strstr(attr_value, sel.val.c_str())) + + // Attribute value is a whitespace-separated list of words, one of which is exactly sel.value + case attribute_contains_word: // ~= + if (sel.value != "" && contains(split_string(attr_value), sel.value)) { - return select_no_match; + return select_match; } break; - case select_start_str: - if (!attr_value || strncmp(attr_value, sel.val.c_str(), sel.val.length())) + + case attribute_starts_with_string: // ^= + if (sel.value != "" && match(attr_value, 0, sel.value)) { - return select_no_match; + return select_match; } break; - case select_end_str: - if (!attr_value) + + // Attribute value is either equals sel.value or begins with sel.value immediately followed by "-". + case attribute_starts_with_string_hyphen: // |= + // Note: no special treatment for sel.value == "" + if (attr_value == sel.value || match(attr_value, 0, sel.value + '-')) { - return select_no_match; + return select_match; } - else if (strncmp(attr_value, sel.val.c_str(), sel.val.length())) + break; + + case attribute_ends_with_string: // $= + if (sel.value != "" && match(attr_value, -(int)sel.value.size(), sel.value)) { - const char* s = attr_value + strlen(attr_value) - sel.val.length() - 1; - if (s < attr_value) - { - return select_no_match; - } - if (sel.val != s) - { - return select_no_match; - } + return select_match; } break; - default: - break; } - return select_match; + return select_no_match; } -litehtml::element::ptr litehtml::html_tag::find_ancestor(const css_selector& selector, bool apply_pseudo, bool* is_pseudo) +element::ptr html_tag::find_ancestor(const css_selector& selector, bool apply_pseudo, bool* is_pseudo) { element::ptr el_parent = parent(); if (!el_parent) @@ -829,17 +855,17 @@ bool litehtml::html_tag::is_break() const void litehtml::html_tag::draw_background(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr<render_item> &ri) { - position pos = ri->pos(); - pos.x += x; - pos.y += y; - - position el_pos = pos; - el_pos += ri->get_paddings(); - el_pos += ri->get_margins(); - if(m_css.get_display() != display_inline && m_css.get_display() != display_table_row) { - if(el_pos.does_intersect(clip) || is_root()) + position pos = ri->pos(); + pos.x += x; + pos.y += y; + + position border_box = pos; + border_box += ri->get_paddings(); + border_box += ri->get_borders(); + + if(border_box.does_intersect(clip) || is_root()) { auto v_offset = ri->get_draw_vertical_offset(); pos.y += v_offset; @@ -853,7 +879,7 @@ void litehtml::html_tag::draw_background(uint_ptr hdc, int x, int y, const posit { background_layer layer; if(!bg->get_layer(i, pos, this, ri, layer)) continue; - if(is_root()) + if(is_root() && (clip != nullptr)) { layer.clip_box = *clip; layer.border_box = *clip; @@ -861,9 +887,6 @@ void litehtml::html_tag::draw_background(uint_ptr hdc, int x, int y, const posit bg->draw_layer(hdc, i, layer, get_document()->container()); } } - position border_box = pos; - border_box += ri->get_paddings(); - border_box += ri->get_borders(); borders bdr = m_css.get_borders(); if(bdr.is_visible()) @@ -1162,20 +1185,21 @@ litehtml::string litehtml::html_tag::get_list_marker_text(int index) } } -bool litehtml::html_tag::is_nth_child(const element::ptr& el, int num, int off, bool of_type) const +bool html_tag::is_nth_child(const element::ptr& el, int num, int off, bool of_type, const css_selector::vector& selector_list) const { int idx = 1; for(const auto& child : m_children) { if(child->css().get_display() != display_inline_text) { - if( (!of_type) || (of_type && el->tag() == child->tag()) ) + if( (!of_type && selector_list.empty()) || + (of_type && child->tag() == el->tag()) || child->select(selector_list) ) { if(el == child) { if(num != 0) { - if((idx - off) >= 0 && (idx - off) % num == 0) + if((idx - off) * num >= 0 && (idx - off) % num == 0) { return true; } @@ -1194,20 +1218,21 @@ bool litehtml::html_tag::is_nth_child(const element::ptr& el, int num, int off, return false; } -bool litehtml::html_tag::is_nth_last_child(const element::ptr& el, int num, int off, bool of_type) const +bool html_tag::is_nth_last_child(const element::ptr& el, int num, int off, bool of_type, const css_selector::vector& selector_list) const { int idx = 1; for(auto child = m_children.rbegin(); child != m_children.rend(); child++) { if((*child)->css().get_display() != display_inline_text) { - if( !of_type || (of_type && el->tag() == (*child)->tag()) ) + if( (!of_type && selector_list.empty()) || + (of_type && (*child)->tag() == el->tag()) || (*child)->select(selector_list) ) { if(el == (*child)) { if(num != 0) { - if((idx - off) >= 0 && (idx - off) % num == 0) + if((idx - off) * num >= 0 && (idx - off) % num == 0) { return true; } @@ -1494,7 +1519,7 @@ const litehtml::background* litehtml::html_tag::get_background(bool own_only) return &m_css.get_bg(); } -litehtml::string litehtml::html_tag::dump_get_name() +string html_tag::dump_get_name() { if(m_tag == empty_id) { @@ -1502,3 +1527,59 @@ litehtml::string litehtml::html_tag::dump_get_name() } return _s(m_tag) + " [html_tag]"; } + +// https://html.spec.whatwg.org/multipage/rendering.html#maps-to-the-pixel-length-property +void html_tag::map_to_pixel_length_property(string_id prop_name, string attr_value) +{ + int n; + if (html_parse_non_negative_integer(attr_value, n)) + { + css_token tok(DIMENSION, (float)n, css_number_integer, "px"); + m_style.add_property(prop_name, {tok}); + } +} + +// https://html.spec.whatwg.org/multipage/rendering.html#tables-2:attr-table-border +void html_tag::map_to_pixel_length_property_with_default_value(string_id prop_name, string attr_value, int default_value) +{ + int n = default_value; + html_parse_non_negative_integer(attr_value, n); + css_token tok(DIMENSION, (float)n, css_number_integer, "px"); + m_style.add_property(prop_name, {tok}); +} + +// https://html.spec.whatwg.org/multipage/rendering.html#maps-to-the-dimension-property +void html_tag::map_to_dimension_property(string_id prop_name, string attr_value) +{ + float x = 0; + html_dimension_type type = html_length; + if (!html_parse_dimension_value(attr_value, x, type)) + return; + + css_token tok; + if (type == html_length) + tok = {DIMENSION, x, css_number_number, "px"}; + else + tok = {PERCENTAGE, x, css_number_number}; + + m_style.add_property(prop_name, {tok}); +} + +// https://html.spec.whatwg.org/multipage/rendering.html#maps-to-the-dimension-property-(ignoring-zero) +void html_tag::map_to_dimension_property_ignoring_zero(string_id prop_name, string attr_value) +{ + float x = 0; + html_dimension_type type = html_length; + if (!html_parse_nonzero_dimension_value(attr_value, x, type)) + return; + + css_token tok; + if (type == html_length) + tok = {DIMENSION, x, css_number_number, "px"}; + else + tok = {PERCENTAGE, x, css_number_number}; + + m_style.add_property(prop_name, {tok}); +} + +} // namespace litehtml diff --git a/libs/litehtml/src/line_box.cpp b/libs/litehtml/src/line_box.cpp index 9add2431e1..fed0aa404d 100644 --- a/libs/litehtml/src/line_box.cpp +++ b/libs/litehtml/src/line_box.cpp @@ -6,6 +6,8 @@ ////////////////////////////////////////////////////////////////////////////////////////// +litehtml::line_box_item::~line_box_item() = default; + void litehtml::line_box_item::place_to(int x, int y) { m_element->pos().x = x + m_element->content_offset_left(); @@ -51,6 +53,8 @@ litehtml::lbi_start::lbi_start(const std::shared_ptr<render_item>& element) : li m_pos.width = m_element->content_offset_left(); } +litehtml::lbi_start::~lbi_start() = default; + void litehtml::lbi_start::place_to(int x, int y) { m_pos.x = x + m_element->content_offset_left(); @@ -90,6 +94,8 @@ litehtml::lbi_end::lbi_end(const std::shared_ptr<render_item>& element) : lbi_st m_pos.width = m_element->content_offset_right(); } +litehtml::lbi_end::~lbi_end() = default; + void litehtml::lbi_end::place_to(int x, int y) { m_pos.x = x; @@ -114,6 +120,8 @@ litehtml::lbi_continue::lbi_continue(const std::shared_ptr<render_item>& element m_pos.width = 0; } +litehtml::lbi_continue::~lbi_continue() = default; + void litehtml::lbi_continue::place_to(int x, int y) { m_pos.x = x; diff --git a/libs/litehtml/src/media_query.cpp b/libs/litehtml/src/media_query.cpp index 8c2afff271..8593f89f11 100644 --- a/libs/litehtml/src/media_query.cpp +++ b/libs/litehtml/src/media_query.cpp @@ -1,430 +1,683 @@ #include "html.h" #include "media_query.h" -#include "document.h" +#include "css_parser.h" +#include <cassert> +#define UNE 8800 // u'≠' +#define ULE 10877 // u'⩽' +#define UGE 10878 // u'⩾' -litehtml::media_query::media_query() +namespace litehtml { - m_media_type = media_type_all; - m_not = false; -} -litehtml::media_query::media_query( const media_query& val ) +bool eval_op(float x, short op, float value) { - m_not = val.m_not; - m_expressions = val.m_expressions; - m_media_type = val.m_media_type; + const float epsilon = 0.00001f; + if (abs(x - value) < epsilon) + { + if (is_one_of(op, '=', u'>', u'<')) return true; + if (op == UNE) return false; + } + + switch (op) + { + case '<': return x < value; + case ULE: return x <= value; + case '>': return x > value; + case UGE: return x >= value; + case '=': return x == value; + case UNE: return x != value; + default: return false; + } } -litehtml::media_query::ptr litehtml::media_query::create_from_string(const string& str, const std::shared_ptr<document>& doc) +bool media_feature::compare(float x) const { - media_query::ptr query = std::make_shared<media_query>(); + if (!op2) return eval_op(x, op, value); + return eval_op(value, op, x) && eval_op(x, op2, value2); +} - string_vector tokens; - split_string(str, tokens, " \t\r\n", "", "("); +bool media_feature::check(const media_features& feat) const +{ + switch (_id(name)) + { + case _width_: + return compare(feat.width); + case _height_: + return compare(feat.height); + case _device_width_: + return compare(feat.device_width); + case _device_height_: + return compare(feat.device_height); + case _orientation_: + return compare(feat.height >= feat.width ? _portrait_ : _landscape_); + case _aspect_ratio_: + // The standard calls 1/0 "a degenerate ratio" https://drafts.csswg.org/css-values-4/#ratio-value, + // but it doesn't specify exactly how it behaves in this context: https://drafts.csswg.org/mediaqueries-5/#aspect-ratio. + // Chrome/Firefox work as expected with 1/0, for example when both width and height are nonzero + // (aspect-ratio < 1/0) evaluates to true. But they behave the same for 0/0, which is unexpected + // (0/0 is NaN, so any comparisons should evaluate to false). + // 0/1 is also degenerate according to the standard. + return feat.height ? compare(float(feat.width) / feat.height) : false; + case _device_aspect_ratio_: + return feat.device_height ? compare(float(feat.device_width) / feat.device_height) : false; + case _color_: + return compare(feat.color); + case _color_index_: + return compare(feat.color_index); + case _monochrome_: + return compare(feat.monochrome); + case _resolution_: + return compare(feat.resolution); + default: + assert(0); // must never happen, unknown media features are handled in parse_media_feature + return false; + } +} - for(auto & token : tokens) +trilean media_condition::check(const media_features& features) const +{ + if (op == _not_) + return !m_conditions[0].check(features); + + if (op == _and_) { - if(token == "not") + // https://drafts.csswg.org/mediaqueries/#evaluating + // The result is true if the <media-in-parens> child term and all of the <media-in-parens> + // children of the <media-and> child terms are true, false if at least one of these + // <media-in-parens> terms are false, and unknown otherwise. + trilean result = True; + for (const auto& condition : m_conditions) { - query->m_not = true; - } else if(token.at(0) == '(') - { - token.erase(0, 1); - if(!token.empty() && token.at(token.length() - 1) == ')') - { - token.erase(token.length() - 1, 1); - } - media_query_expression expr; - string_vector expr_tokens; - split_string(token, expr_tokens, ":"); - if(!expr_tokens.empty()) - { - trim(expr_tokens[0]); - expr.feature = (media_feature) value_index(expr_tokens[0], media_feature_strings, media_feature_none); - if(expr.feature != media_feature_none) - { - if(expr_tokens.size() == 1) - { - expr.check_as_bool = true; - } else - { - trim(expr_tokens[1]); - expr.check_as_bool = false; - if(expr.feature == media_feature_orientation) - { - expr.val = value_index(expr_tokens[1], media_orientation_strings, media_orientation_landscape); - } else - { - string::size_type slash_pos = expr_tokens[1].find('/'); - if( slash_pos != string::npos ) - { - string val1 = expr_tokens[1].substr(0, slash_pos); - string val2 = expr_tokens[1].substr(slash_pos + 1); - trim(val1); - trim(val2); - expr.val = atoi(val1.c_str()); - expr.val2 = atoi(val2.c_str()); - } else - { - css_length length; - length.fromString(expr_tokens[1]); - if(length.units() == css_units_dpcm || length.units() == css_units_dpi) - { - expr.val = (int) (length.val() * 2.54); - } else - { - if(doc) - { - doc->cvt_units(length, doc->container()->get_default_font_size()); - } - expr.val = (int) length.val(); - } - } - } - } - query->m_expressions.push_back(expr); - } - } - } else + result = result && condition.check(features); + if (result == False) return result; // no need to check further + } + return result; + } + if (op == _or_) + { + // The result is false if the <media-in-parens> child term and all of the <media-in-parens> + // children of the <media-or> child terms are false, true if at least one of these + // <media-in-parens> terms are true, and unknown otherwise. + trilean result = False; + for (const auto& condition : m_conditions) { - query->m_media_type = (media_type) value_index(token, media_type_strings, media_type_none); + result = result || condition.check(features); + if (result == True) return result; // no need to check further } + return result; } + return False; +} - return query; +trilean media_in_parens::check(const media_features& features) const +{ + if (is<media_condition>()) return get<media_condition>().check(features); + if (is<media_feature>()) return (trilean)get<media_feature>().check(features); + // <general-enclosed> https://drafts.csswg.org/mediaqueries/#evaluating + return Unknown; } -bool litehtml::media_query::check( const media_features& features ) const +trilean media_query::check(const media_features& features) const { - bool res = false; - if(m_media_type == media_type_all || m_media_type == features.type) + trilean result; + // https://drafts.csswg.org/mediaqueries/#media-types + // User agents must recognize the following media types as valid, but must make them match nothing. + if (m_media_type >= media_type_first_deprecated) + result = False; + else if (m_media_type == media_type_unknown) + result = False; + else if (m_media_type == media_type_all) + result = True; + else + result = (trilean)(m_media_type == features.type); + + if (result == True) { - res = true; - for(auto expression : m_expressions) + for (const auto& condition : m_conditions) { - if(!expression.check(features)) - { - res = false; - break; - } + result = result && condition.check(features); + if (result == False) break; // no need to check further } } - if(m_not) + if (m_not) result = !result; + + return result; +} + +// https://drafts.csswg.org/mediaqueries/#mq-list +bool media_query_list::check(const media_features& features) const +{ + if (empty()) return true; // An empty media query list evaluates to true. + + trilean result = False; + for (const auto& query : m_queries) { - res = !res; + result = result || query.check(features); + if (result == True) break; // no need to check further } - return res; + // https://drafts.csswg.org/mediaqueries/#evaluating + // If the result ... is used in any context that expects a two-valued boolean, “unknown” must be converted to “false”. + return result == True; } -////////////////////////////////////////////////////////////////////////// - -litehtml::media_query_list::ptr litehtml::media_query_list::create_from_string(const string& str, const std::shared_ptr<document>& doc) +// nested @media rules: https://drafts.csswg.org/css-conditional-3/#processing +// all of them must be true for style rules to apply +bool media_query_list_list::apply_media_features(const media_features& features) { - media_query_list::ptr list = std::make_shared<media_query_list>(); - - string_vector tokens; - split_string(str, tokens, ","); + bool apply = true; - for(auto & token : tokens) + for (const auto& mq_list: m_media_query_lists) { - trim(token); - lcase(token); - - litehtml::media_query::ptr query = media_query::create_from_string(token, doc); - if(query) + if (!mq_list.check(features)) { - list->m_queries.push_back(query); + apply = false; + break; } } - if(list->m_queries.empty()) + + bool ret = (apply != m_is_used); + m_is_used = apply; + return ret; +} + + +bool parse_media_query(const css_token_vector& tokens, media_query& mquery, document::ptr doc); + +// https://drafts.csswg.org/mediaqueries-5/#typedef-media-query-list +media_query_list parse_media_query_list(const css_token_vector& _tokens, document::ptr doc) +{ + auto keep_whitespace = [](auto left_token, auto right_token) { - list = nullptr; + return is_one_of(left_token.ch, '<', '>') && right_token.ch == '='; + }; + css_token_vector tokens = normalize(_tokens, f_componentize | f_remove_whitespace, keep_whitespace); + // this needs special treatment because empty media query list evaluates to true + if (tokens.empty()) return {}; + + media_query_list result; + auto list_of_lists = parse_comma_separated_list(tokens); + for (const auto& list : list_of_lists) + { + media_query query; + parse_media_query(list, query, doc); + // Note: appending even if media query failed to parse, as per standard. + result.m_queries.push_back(query); } + return result; +} - return list; +media_query_list parse_media_query_list(const string& str, shared_ptr<document> doc) +{ + auto tokens = normalize(str); + return parse_media_query_list(tokens, doc); } -bool litehtml::media_query_list::apply_media_features( const media_features& features ) +bool parse_media_condition(const css_token_vector& tokens, int& index, bool or_allowed, media_condition& condition, document::ptr doc); + +// <media-query> = <media-condition> | [ not | only ]? <media-type> [ and <media-condition-without-or> ]? +bool parse_media_query(const css_token_vector& tokens, media_query& mquery, document::ptr doc) { - bool apply = false; + if (tokens.empty()) return false; + int index = 0; + auto end = [&]() { return index == (int)tokens.size(); }; + + media_condition condition; + if (parse_media_condition(tokens, index, true, condition, doc) && end()) + { + mquery.m_not = false; + mquery.m_media_type = media_type_all; + mquery.m_conditions = {condition}; + return true; + } + + string ident = tokens[0].ident(); + bool _not = false; + if (ident == "not") index++, _not = true; + else if (ident == "only") index++; // ignored https://drafts.csswg.org/mediaqueries-5/#mq-only - for(auto & query : m_queries) + // <media-type> = <ident> except only, not, and, or, and layer + ident = at(tokens, index).ident(); + if (is_one_of(ident, "", "only", "not", "and", "or", "layer")) + return false; + int idx = value_index(ident, media_type_strings); + int media_type = idx == -1 ? media_type_unknown : idx + 1; + index++; + + if (at(tokens, index).ident() == "and") { - if(query->check(features)) - { - apply = true; - break; - } + index++; + if (!parse_media_condition(tokens, index, false, condition, doc) || !end()) + return false; + mquery.m_conditions = {condition}; } + if (!end()) return false; + mquery.m_not = _not; + mquery.m_media_type = (litehtml::media_type) media_type; + return true; +} - bool ret = (apply != m_is_used); - m_is_used = apply; - return ret; +bool parse_media_in_parens(const css_token& token, media_in_parens& media_in_parens, document::ptr doc); + +// <media-condition> = <media-not> | <media-in-parens> [ <media-and>* | <media-or>* ] +// <media-condition-without-or> = <media-not> | <media-in-parens> <media-and>* +// <media-not> = not <media-in-parens> +bool parse_media_condition(const css_token_vector& tokens, int& index, bool _or_allowed, media_condition& condition, document::ptr doc) +{ + media_in_parens media_in_parens; + if (at(tokens, index).ident() == "not") + { + if (!parse_media_in_parens(at(tokens, index + 1), media_in_parens, doc)) return false; + condition.op = _not_; + condition.m_conditions = {media_in_parens}; + index += 2; + return true; + } + + if (!parse_media_in_parens(at(tokens, index), media_in_parens, doc)) return false; + condition.m_conditions = {media_in_parens}; + index++; + + bool or_allowed = _or_allowed; + bool and_allowed = true; + while (true) + { + string ident = at(tokens, index).ident(); + if (ident == "and" && and_allowed) condition.op = _and_, or_allowed = false; + else if (ident == "or" && or_allowed) condition.op = _or_, and_allowed = false; + else return true; + index++; + + if (!parse_media_in_parens(at(tokens, index), media_in_parens, doc)) return false; + condition.m_conditions.push_back(media_in_parens); + index++; + } } -bool litehtml::media_query_expression::check( const media_features& features ) const +bool parse_media_feature(const css_token& token, media_feature& media_feature, document::ptr doc); + +// https://drafts.csswg.org/mediaqueries-5/#typedef-media-in-parens +// <media-in-parens> = ( <media-condition> ) | <media-feature> | <general-enclosed> +// <general-enclosed> = [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ] +bool parse_media_in_parens(const css_token& token, media_in_parens& media_in_parens, document::ptr doc) { - switch(feature) + if (token.type == CV_FUNCTION) { - case media_feature_width: - if(check_as_bool) - { - return (features.width != 0); - } else if(features.width == val) - { - return true; - } - break; - case media_feature_min_width: - if(features.width >= val) - { - return true; - } - break; - case media_feature_max_width: - if(features.width <= val) - { - return true; - } - break; - case media_feature_height: - if(check_as_bool) - { - return (features.height != 0); - } else if(features.height == val) - { + if (!token.value.empty() && !is_any_value(token.value)) + return false; + media_in_parens = unknown(); + return true; + } + + if (token.type != ROUND_BLOCK) return false; + const css_token_vector& tokens = token.value; + + int index = 0; + media_condition condition; + media_feature media_feature; + if (parse_media_condition(tokens, index, true, condition, doc) && index == (int)tokens.size()) + media_in_parens = condition; + else if (parse_media_feature(token, media_feature, doc)) + media_in_parens = media_feature; + else if (!tokens.empty() && !is_any_value(tokens)) + return false; + else + media_in_parens = unknown(); + return true; +} + +bool parse_mf_value(const css_token_vector& tokens, int& index, css_token val[2]); +bool parse_mf_range(const css_token_vector& tokens, media_feature& media_feature, document::ptr doc); + +// https://drafts.csswg.org/mediaqueries/#mq-ranges +// Every media feature defines its “type” as either “range” or “discrete” in its definition table. +// The only significant difference between the two types is that “range” media features can be evaluated in a range context and accept “min-” and “max-” prefixes on their name. + +// https://drafts.csswg.org/mediaqueries/#mq-min-max +// “Discrete” type properties do not accept “min-” or “max-” prefixes. Adding such a prefix to a “discrete” type media feature simply results in an unknown feature name. +// For example, (min-grid: 1) is invalid, because grid is a “discrete” media feature, and so doesn’t accept the prefixes. (Even though the grid media feature appears to be numeric, as it accepts the values 0 and 1.) + +struct mf_info +{ + string_id type = empty_id; // range, discrete + string_id value_type = empty_id; // length, ratio, resolution, integer, keyword + vector<string_id> keywords = {}; // default value is specified here to get rid of gcc warning "missing initializer for member" + + operator bool() { return type != empty_id; } +}; + +std::map<string, mf_info> supported_media_features = +{ + //////////////////////////////////////////////// + // 4. Viewport/Page Dimensions Media Features + //////////////////////////////////////////////// + + // https://drafts.csswg.org/mediaqueries/#width + // For continuous media, this is the width of the viewport. + {"width", {_range_, _length_}}, + {"height", {_range_, _length_}}, + + // https://drafts.csswg.org/mediaqueries/#aspect-ratio + // width/height + {"aspect-ratio", {_range_, _ratio_}}, + + // https://drafts.csswg.org/mediaqueries/#orientation + {"orientation", {_discrete_, _keyword_, {_portrait_, _landscape_}}}, + + //////////////////////////////////////////////// + // 5. Display Quality Media Features + //////////////////////////////////////////////// + + // https://drafts.csswg.org/mediaqueries/#resolution + // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/resolution + {"resolution", {_range_, _resolution_}}, + + //////////////////////////////////////////////// + // 6. Color Media Features + //////////////////////////////////////////////// + + // https://drafts.csswg.org/mediaqueries/#color + {"color", {_range_, _integer_}}, + + // https://drafts.csswg.org/mediaqueries/#color-index + {"color-index", {_range_, _integer_}}, + + // https://drafts.csswg.org/mediaqueries/#monochrome + {"monochrome", {_range_, _integer_}}, + + + //////////////////////////////////////////////// + // Deprecated Media Features + //////////////////////////////////////////////// + + // https://drafts.csswg.org/mediaqueries/#device-width + {"device-width", {_range_, _length_}}, + {"device-height", {_range_, _length_}}, + + // https://drafts.csswg.org/mediaqueries/#device-aspect-ratio + {"device-aspect-ratio", {_range_, _ratio_}}, +}; + +bool convert_units(mf_info mfi, css_token val[2], document::ptr doc) +{ + switch (mfi.value_type) + { + case _integer_: + // nothing to convert, just verify + return val[0].type == NUMBER && val[0].n.number_type == css_number_integer && val[1].type == 0; + + case _length_: + { + if (val[1].type != 0) return false; + css_length length; + if (!length.from_token(val[0], f_length)) return false; + font_metrics fm; + fm.x_height = fm.font_size = doc->container()->get_default_font_size(); + doc->cvt_units(length, fm, 0); + val[0].n.number = length.val(); + return true; + } + + case _resolution_: // https://drafts.csswg.org/css-values-4/#resolution + if (val[1].type != 0) return false; + if (val[0].type == DIMENSION) + { + string unit = lowcase(val[0].unit); + int idx = value_index(unit, "dpi;dpcm;dppx;x"); // x == dppx + // The allowed range of <resolution> values always excludes negative values + if (idx < 0 || val[0].n.number < 0) return false; + // dppx is the canonical unit, but we convert to dpi instead to match document_container::get_media_features + // "Note that due to the 1:96 fixed ratio of CSS in to CSS px, 1dppx is equivalent to 96dpi." + if (unit == "dppx" || unit == "x") + val[0].n.number *= 96; + else if (unit == "dpcm") + val[0].n.number *= 2.54f; // 1in = 2.54cm return true; } - break; - case media_feature_min_height: - if(features.height >= val) + // https://drafts.csswg.org/mediaqueries/#resolution + else if (val[0].ident() == "infinite") { + val[0] = css_token(NUMBER, INFINITY, css_number_number); return true; } - break; - case media_feature_max_height: - if(features.height <= val) + // Note: <resolution> doesn't allow unitless zero + return false; + + case _ratio_: // https://drafts.csswg.org/css-values-4/#ratio <ratio> = <number [0,∞]> [ / <number [0,∞]> ]? + if (val[0].type == NUMBER && val[0].n.number >= 0 && + ((val[1].type == NUMBER && val[1].n.number >= 0) || val[1].type == 0)) { + if (val[1].type == NUMBER) + val[0].n.number /= val[1].n.number; // Note: val[1].n.number may be 0, so result may be inf return true; } - break; + return false; - case media_feature_device_width: - if(check_as_bool) - { - return (features.device_width != 0); - } else if(features.device_width == val) - { - return true; - } - break; - case media_feature_min_device_width: - if(features.device_width >= val) - { - return true; - } - break; - case media_feature_max_device_width: - if(features.device_width <= val) - { + case _keyword_: + { + if (val[1].type != 0) return false; + string_id ident = _id(val[0].ident()); + if (!contains(mfi.keywords, ident)) return false; + val[0] = css_token(NUMBER, (float)ident); + return true; + } + + default: + return false; + } +} + +bool media_feature::verify_and_convert_units(string_id syntax, + css_token val[2], css_token val2[2], document::ptr doc) +{ + // https://drafts.csswg.org/mediaqueries/#mq-boolean-context + if (syntax == _boolean_) // (name) + { + // Attempting to evaluate a min/max prefixed media feature in a boolean context is invalid and a syntax error. + auto mf_info = at(supported_media_features, name); + if (!mf_info) return false; + value = mf_info.value_type == _keyword_ ? (float)_none_ : 0; + op = UNE; + return true; + } + else if (syntax == _plain_) // ({min-,max-,}name: value) + { + if (is_one_of(name.substr(0, 4), "min-", "max-")) + { + string real_name = name.substr(4); + auto mf_info = at(supported_media_features, real_name); + if (!mf_info || mf_info.type == _discrete_) + return false; + if (!convert_units(mf_info, val, doc)) + return false; + value = val[0].n.number; + op = name.substr(0, 4) == "min-" ? UGE : ULE; + name = real_name; return true; } - break; - case media_feature_device_height: - if(check_as_bool) - { - return (features.device_height != 0); - } else if(features.device_height == val) + else { + auto mf_info = at(supported_media_features, name); + if (!mf_info) return false; + if (!convert_units(mf_info, val, doc)) + return false; + value = val[0].n.number; + op = '='; return true; } - break; - case media_feature_min_device_height: - if(features.device_height >= val) + } + else // range syntax + { + auto mf_info = at(supported_media_features, name); + if (!mf_info || mf_info.type == _discrete_) + return false; + //if (val) { - return true; + if (!convert_units(mf_info, val, doc)) + return false; + value = val[0].n.number; } - break; - case media_feature_max_device_height: - if(features.device_height <= val) + if (val2) { - return true; + if (!convert_units(mf_info, val2, doc)) + return false; + value2 = val2[0].n.number; } - break; + return true; + } +} - case media_feature_orientation: - if(features.height >= features.width) - { - if(val == media_orientation_portrait) - { - return true; - } - } else - { - if(val == media_orientation_landscape) - { - return true; - } - } - break; - case media_feature_aspect_ratio: - if(features.height && val2) - { - int ratio_this = round_d( (double) val / (double) val2 * 100 ); - int ratio_feat = round_d( (double) features.width / (double) features.height * 100.0 ); - if(ratio_this == ratio_feat) - { - return true; - } - } - break; - case media_feature_min_aspect_ratio: - if(features.height && val2) - { - int ratio_this = round_d( (double) val / (double) val2 * 100 ); - int ratio_feat = round_d( (double) features.width / (double) features.height * 100.0 ); - if(ratio_feat >= ratio_this) - { - return true; - } - } - break; - case media_feature_max_aspect_ratio: - if(features.height && val2) - { - int ratio_this = round_d( (double) val / (double) val2 * 100 ); - int ratio_feat = round_d( (double) features.width / (double) features.height * 100.0 ); - if(ratio_feat <= ratio_this) - { - return true; - } - } - break; +// <media-feature> = ( [ <mf-plain> | <mf-boolean> | <mf-range> ] ) +bool parse_media_feature(const css_token& token, media_feature& result, document::ptr doc) +{ + if (token.type != ROUND_BLOCK || token.value.empty()) return false; + const css_token_vector& tokens = token.value; - case media_feature_device_aspect_ratio: - if(features.device_height && val2) - { - int ratio_this = round_d( (double) val / (double) val2 * 100 ); - int ratio_feat = round_d( (double) features.device_width / (double) features.device_height * 100.0 ); - if(ratio_feat == ratio_this) - { - return true; - } - } - break; - case media_feature_min_device_aspect_ratio: - if(features.device_height && val2) - { - int ratio_this = round_d( (double) val / (double) val2 * 100 ); - int ratio_feat = round_d( (double) features.device_width / (double) features.device_height * 100.0 ); - if(ratio_feat >= ratio_this) - { - return true; - } - } - break; - case media_feature_max_device_aspect_ratio: - if(features.device_height && val2) - { - int ratio_this = round_d( (double) val / (double) val2 * 100 ); - int ratio_feat = round_d( (double) features.device_width / (double) features.device_height * 100.0 ); - if(ratio_feat <= ratio_this) - { - return true; - } - } - break; + if (tokens.size() == 1) + { + media_feature mf = {tokens[0].ident()}; + if (!mf.verify_and_convert_units(_boolean_)) return false; + result = mf; + return true; + } - case media_feature_color: - if(check_as_bool) - { - return (features.color != 0); - } else if(features.color == val) - { - return true; - } - break; - case media_feature_min_color: - if(features.color >= val) - { - return true; - } - break; - case media_feature_max_color: - if(features.color <= val) - { - return true; - } - break; + if (tokens[0].type == IDENT && tokens[1].ch == ':') + { + css_token val[2]; + int index = 2; + if (!parse_mf_value(tokens, index, val) || index != (int)tokens.size()) + return false; + + media_feature mf = {tokens[0].ident()}; + if (!mf.verify_and_convert_units(_plain_, val, nullptr, doc)) return false; + result = mf; + return true; + } - case media_feature_color_index: - if(check_as_bool) - { - return (features.color_index != 0); - } else if(features.color_index == val) - { - return true; - } - break; - case media_feature_min_color_index: - if(features.color_index >= val) - { - return true; - } - break; - case media_feature_max_color_index: - if(features.color_index <= val) - { - return true; - } - break; + return parse_mf_range(tokens, result, doc); +} - case media_feature_monochrome: - if(check_as_bool) - { - return (features.monochrome != 0); - } else if(features.monochrome == val) - { - return true; - } - break; - case media_feature_min_monochrome: - if(features.monochrome >= val) - { - return true; - } - break; - case media_feature_max_monochrome: - if(features.monochrome <= val) - { - return true; - } - break; +// <mf-value> = <number> | <dimension> | <ident> | <ratio> +// <ratio> = <number [0,∞]> [ / <number [0,∞]> ]? +bool parse_mf_value(const css_token_vector& tokens, int& index, css_token val[2]) +{ + const css_token& a = at(tokens, index); + const css_token& b = at(tokens, index + 1); + const css_token& c = at(tokens, index + 2); + + if (!is_one_of(a.type, NUMBER, DIMENSION, IDENT)) return false; + + if (a.type == NUMBER && a.n.number >= 0 && b.ch == '/' && + c.type == NUMBER && c.n.number >= 0) + { + val[0] = a; + val[1] = c; + index += 3; + } + else + { + val[0] = a; + index++; + } + return true; +} - case media_feature_resolution: - if(features.resolution == val) - { - return true; - } - break; - case media_feature_min_resolution: - if(features.resolution >= val) - { - return true; - } - break; - case media_feature_max_resolution: - if(features.resolution <= val) +short mirror(short op) +{ + if (op == '<') return '>'; + if (op == '>') return '<'; + if (op == ULE) return UGE; + if (op == UGE) return ULE; + return op; +} + +// <mf-range> = <mf-name> <mf-comparison> <mf-value> +// | <mf-value> <mf-comparison> <mf-name> +// | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value> +// | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value> +// <mf-lt> = '<' '='? +// <mf-gt> = '>' '='? +// <mf-eq> = '=' +// <mf-comparison> = <mf-lt> | <mf-gt> | <mf-eq> +bool parse_mf_range(const css_token_vector& tokens, media_feature& result, document::ptr doc) +{ + if (tokens.size() < 3) return false; + + int index; + string name; + auto mf_name = [&]() + { + if (at(tokens, index).type != IDENT) return false; + name = at(tokens, index).ident(); + // special case for (infinite = resolution) + // resolution is the only range media feature that can accept a keyword + if (name == "infinite") return false; + index++; + return true; + }; + auto mf_value = [&](css_token _val[2]) + { + return parse_mf_value(tokens, index, _val); + }; + auto mf_lt_gt = [&](char lg, short& _op) + { + const css_token& tok = at(tokens, index); + const css_token& tok1 = at(tokens, index + 1); + + if (tok.ch != lg) return false; + + if (tok1.ch == '=') + index+=2, _op = lg == '<' ? ULE : UGE; + else + index++, _op = lg; + return true; + }; + auto mf_lt = [&](short& _op) { return mf_lt_gt('<', _op); }; + auto mf_gt = [&](short& _op) { return mf_lt_gt('>', _op); }; + auto mf_comparison = [&](short& _op) + { + const css_token& tok = at(tokens, index); + + if (tok.ch == '=') { + index++; + _op = '='; return true; } - break; - default: - return false; - } + return mf_lt(_op) || mf_gt(_op); + }; + auto start = [&]() { index = 0; return true; }; + auto end = [&]() { return index == (int)tokens.size(); }; + + short op; + css_token val[2]; + // using lambda to avoid warning "assignment within conditional expression" + auto reverse = [](short& _op) { _op = mirror(_op); return true; }; + if ((start() && mf_name() && mf_comparison(op) && mf_value(val) && end()) || + (start() && mf_value(val) && mf_comparison(op) && mf_name() && end() && reverse(op))) + { + media_feature mf = {name}; + mf.op = op; + if (!mf.verify_and_convert_units(_range_, val, nullptr, doc)) return false; + result = mf; + return true; + } + short op2; + css_token val2[2]; + if ((start() && mf_value(val) && mf_lt(op) && mf_name() && mf_lt(op2) && mf_value(val2) && end()) || + (start() && mf_value(val) && mf_gt(op) && mf_name() && mf_gt(op2) && mf_value(val2) && end())) + { + media_feature mf = {name}; + mf.op = op; + mf.op2 = op2; + if (!mf.verify_and_convert_units(_range_, val, val2, doc)) return false; + result = mf; + return true; + } return false; } + +} // namespace litehtml diff --git a/libs/litehtml/src/render_block.cpp b/libs/litehtml/src/render_block.cpp index 49e6f8fa2d..7e2867f35e 100644 --- a/libs/litehtml/src/render_block.cpp +++ b/libs/litehtml/src/render_block.cpp @@ -52,15 +52,6 @@ int litehtml::render_item_block::place_float(const std::shared_ptr<render_item> std::shared_ptr<litehtml::render_item> litehtml::render_item_block::init() { - { - css_selector sel; - sel.parse(".inline_rating"); - if(src_el()->select(sel)) - { - int i = 0; - i++; - } - } std::shared_ptr<render_item> ret; // Initialize indexes for list items @@ -204,6 +195,14 @@ int litehtml::render_item_block::_render(int x, int y, const containing_block_co int ret_width = _render_content(x, y, second_pass, self_size, fmt_ctx); //***************************************** + if (src_el()->css().get_display() == display_list_item) + { + if(m_pos.height == 0) + { + m_pos.height = css().get_line_height(); + } + } + bool requires_rerender = false; // when true, the second pass for content rendering is required // Set block width @@ -341,7 +340,6 @@ int litehtml::render_item_block::_render(int x, int y, const containing_block_co m_pos.height = sz.height; } } - } return ret_width + content_offset_width(); diff --git a/libs/litehtml/src/render_image.cpp b/libs/litehtml/src/render_image.cpp index 122344651b..23b059bd90 100644 --- a/libs/litehtml/src/render_image.cpp +++ b/libs/litehtml/src/render_image.cpp @@ -29,7 +29,7 @@ int litehtml::render_item_image::_render(int x, int y, const containing_block_co // check for max-width if(!src_el()->css().get_max_width().is_predefined()) { - int max_width = doc->to_pixels(src_el()->css().get_max_width(), src_el()->css().get_font_size(), parent_width); + int max_width = doc->to_pixels(css().get_max_width(), css().get_font_metrics(), parent_width); if(m_pos.width > max_width) { m_pos.width = max_width; @@ -90,7 +90,7 @@ int litehtml::render_item_image::_render(int x, int y, const containing_block_co // check for max-width if(!src_el()->css().get_max_width().is_predefined()) { - int max_width = doc->to_pixels(src_el()->css().get_max_width(), src_el()->css().get_font_size(), parent_width); + int max_width = doc->to_pixels(css().get_max_width(), css().get_font_metrics(), parent_width); if(m_pos.width > max_width) { m_pos.width = max_width; @@ -126,7 +126,7 @@ int litehtml::render_item_image::_render(int x, int y, const containing_block_co // check for max-height if(!src_el()->css().get_max_width().is_predefined()) { - int max_width = doc->to_pixels(src_el()->css().get_max_width(), src_el()->css().get_font_size(), parent_width); + int max_width = doc->to_pixels(css().get_max_width(), css().get_font_metrics(), parent_width); if(m_pos.width > max_width) { m_pos.width = max_width; @@ -143,6 +143,6 @@ int litehtml::render_item_image::_render(int x, int y, const containing_block_co int litehtml::render_item_image::calc_max_height(int image_height, int containing_block_height) { document::ptr doc = src_el()->get_document(); - return doc->to_pixels(src_el()->css().get_max_height(), src_el()->css().get_font_size(), + return doc->to_pixels(css().get_max_height(), css().get_font_metrics(), containing_block_height == 0 ? image_height : containing_block_height); } diff --git a/libs/litehtml/src/render_inline_context.cpp b/libs/litehtml/src/render_inline_context.cpp index b1ca1fcadc..fcf23bf733 100644 --- a/libs/litehtml/src/render_inline_context.cpp +++ b/libs/litehtml/src/render_inline_context.cpp @@ -49,20 +49,20 @@ int litehtml::render_item_inline_context::_render_content(int /*x*/, int /*y*/, } } // place element into rendering flow - place_inline(std::unique_ptr<line_box_item>(new line_box_item(el)), self_size, fmt_ctx); + place_inline(std::make_unique<line_box_item>(el), self_size, fmt_ctx); } break; case iterator_item_type_start_parent: { el->clear_inline_boxes(); - place_inline(std::unique_ptr<lbi_start>(new lbi_start(el)), self_size, fmt_ctx); + place_inline(std::make_unique<lbi_start>(el), self_size, fmt_ctx); } break; case iterator_item_type_end_parent: { - place_inline(std::unique_ptr<lbi_end>(new lbi_end(el)), self_size, fmt_ctx); + place_inline(std::make_unique<lbi_end>(el), self_size, fmt_ctx); } break; } @@ -224,12 +224,12 @@ int litehtml::render_item_inline_context::new_box(const std::unique_ptr<line_box } } - m_line_boxes.emplace_back(std::unique_ptr<line_box>(new line_box( + m_line_boxes.emplace_back(std::make_unique<line_box>( line_ctx.top, line_ctx.left + first_line_margin + text_indent, line_ctx.right, css().get_line_height(), css().get_font_metrics(), - css().get_text_align()))); + css().get_text_align())); // Add items returned by finish_last_box function into the new line for(auto& it : items) diff --git a/libs/litehtml/src/render_item.cpp b/libs/litehtml/src/render_item.cpp index fa39510e5a..5ec65ab049 100644 --- a/libs/litehtml/src/render_item.cpp +++ b/libs/litehtml/src/render_item.cpp @@ -9,22 +9,22 @@ litehtml::render_item::render_item(std::shared_ptr<element> _src_el) : m_skip(false) { document::ptr doc = src_el()->get_document(); - auto fnt_size = src_el()->css().get_font_size(); - - m_margins.left = doc->to_pixels(src_el()->css().get_margins().left, fnt_size); - m_margins.right = doc->to_pixels(src_el()->css().get_margins().right, fnt_size); - m_margins.top = doc->to_pixels(src_el()->css().get_margins().top, fnt_size); - m_margins.bottom = doc->to_pixels(src_el()->css().get_margins().bottom, fnt_size); - - m_padding.left = doc->to_pixels(src_el()->css().get_padding().left, fnt_size); - m_padding.right = doc->to_pixels(src_el()->css().get_padding().right, fnt_size); - m_padding.top = doc->to_pixels(src_el()->css().get_padding().top, fnt_size); - m_padding.bottom = doc->to_pixels(src_el()->css().get_padding().bottom, fnt_size); - - m_borders.left = doc->to_pixels(src_el()->css().get_borders().left.width, fnt_size); - m_borders.right = doc->to_pixels(src_el()->css().get_borders().right.width, fnt_size); - m_borders.top = doc->to_pixels(src_el()->css().get_borders().top.width, fnt_size); - m_borders.bottom = doc->to_pixels(src_el()->css().get_borders().bottom.width, fnt_size); + auto fm = css().get_font_metrics(); + + m_margins.left = doc->to_pixels(src_el()->css().get_margins().left, fm, 0); + m_margins.right = doc->to_pixels(src_el()->css().get_margins().right, fm, 0); + m_margins.top = doc->to_pixels(src_el()->css().get_margins().top, fm, 0); + m_margins.bottom = doc->to_pixels(src_el()->css().get_margins().bottom, fm, 0); + + m_padding.left = doc->to_pixels(src_el()->css().get_padding().left, fm, 0); + m_padding.right = doc->to_pixels(src_el()->css().get_padding().right, fm, 0); + m_padding.top = doc->to_pixels(src_el()->css().get_padding().top, fm, 0); + m_padding.bottom = doc->to_pixels(src_el()->css().get_padding().bottom, fm, 0); + + m_borders.left = doc->to_pixels(src_el()->css().get_borders().left.width, fm, 0); + m_borders.right = doc->to_pixels(src_el()->css().get_borders().right.width, fm, 0); + m_borders.top = doc->to_pixels(src_el()->css().get_borders().top.width, fm, 0); + m_borders.bottom = doc->to_pixels(src_el()->css().get_borders().bottom.width, fm, 0); } int litehtml::render_item::render(int x, int y, const containing_block_context& containing_block_size, formatting_context* fmt_ctx, bool second_pass) @@ -273,14 +273,14 @@ void litehtml::render_item::render_positioned(render_type rt) if(process) { containing_block_context containing_block_size; - if(el_position == element_position_fixed) + if(el_position == element_position_fixed || (is_root() && !src_el()->is_positioned())) { containing_block_size.height = wnd_position.height; containing_block_size.width = wnd_position.width; } else { - containing_block_size.height = m_pos.height; - containing_block_size.width = m_pos.width; + containing_block_size.height = m_pos.height + m_padding.height(); + containing_block_size.width = m_pos.width + m_padding.width(); } css_length css_left = el->src_el()->css().get_offsets().left; @@ -290,144 +290,353 @@ void litehtml::render_item::render_positioned(render_type rt) bool need_render = false; - css_length el_w = el->src_el()->css().get_width(); - css_length el_h = el->src_el()->css().get_height(); - - int new_width = -1; - int new_height = -1; - if(el_w.units() == css_units_percentage && containing_block_size.width) - { - new_width = el_w.calc_percent(containing_block_size.width); - if(el->m_pos.width != new_width) - { - need_render = true; - el->m_pos.width = new_width; - } - } + css_length el_width = el->src_el()->css().get_width(); + css_length el_height = el->src_el()->css().get_height(); - if(el_h.units() == css_units_percentage && containing_block_size.height) - { - new_height = el_h.calc_percent(containing_block_size.height); - if(el->m_pos.height != new_height) - { - need_render = true; - el->m_pos.height = new_height; - } - } + auto fix_height_min_max = [&] (int height) + { + auto max_height = el->css().get_max_height(); + auto min_height = el->css().get_max_height(); + if(!max_height.is_predefined()) + { + int max_height_value = max_height.calc_percent(containing_block_size.height); + if(height > max_height_value) + { + height = max_height_value; + } + } + if(!min_height.is_predefined()) + { + int min_height_value = min_height.calc_percent(containing_block_size.height); + if(height < min_height_value) + { + height = min_height_value; + } + } + height += el->content_offset_height(); + return height; + }; - bool cvt_x = false; - bool cvt_y = false; + auto fix_width_min_max = [&] (int width) + { + auto max_width = el->css().get_max_width(); + auto min_width = el->css().get_min_width(); + if(!max_width.is_predefined()) + { + int max_width_value = max_width.calc_percent(containing_block_size.width); + if(width > max_width_value) + { + width = max_width_value; + } + } + if(!min_width.is_predefined()) + { + int min_width_value = min_width.calc_percent(containing_block_size.width); + if(width < min_width_value) + { + width = min_width_value; + } + } + width += el->content_offset_width(); + return width; + }; + + int bottom = 0; + int top = 0; + int height = 0; + auto [el_static_offset_x, el_static_offset_y] = element_static_offset(el); + int el_static_x = el->m_pos.x + el_static_offset_x; + int el_static_y = el->m_pos.y + el_static_offset_y; + // Calculate vertical position + // https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-height + // 10.6.4 Absolutely positioned, non-replaced elements + if(css_top.is_predefined() && !css_bottom.is_predefined() && el_height.is_predefined()) + { + // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the + // content per 10.6.7, set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top' + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = 0; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = 0; + height = el->height(); + bottom = css_bottom.calc_percent(containing_block_size.height); + top = containing_block_size.height - height - bottom; + } else if(css_top.is_predefined() && css_bottom.is_predefined() && !el_height.is_predefined()) + { + // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, + // set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom' + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = 0; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = 0; + top = el_static_y - el->content_offset_top(); + height = fix_height_min_max(el_height.calc_percent(containing_block_size.height)); + } else if(!css_top.is_predefined() && css_bottom.is_predefined() && el_height.is_predefined()) + { + // 3. 'height' and 'bottom' are 'auto' and 'top' is not 'auto', then the height is based on the + // content per 10.6.7, set 'auto' values for 'margin-top' and 'margin-bottom' to 0, + // and solve for 'bottom' + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = 0; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = 0; + height = el->height(); + top = css_top.calc_percent(containing_block_size.height); + } else if(css_top.is_predefined() && !css_bottom.is_predefined() && !el_height.is_predefined()) + { + // 4. 'top' is 'auto', 'height' and 'bottom' are not 'auto', then set 'auto' values for 'margin-top' + // and 'margin-bottom' to 0, and solve for 'top' + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = 0; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = 0; + height = fix_height_min_max(el_height.calc_percent(containing_block_size.height)); + bottom = css_bottom.calc_percent(containing_block_size.height); + top = containing_block_size.height - height - bottom; + } else if(!css_top.is_predefined() && !css_bottom.is_predefined() && el_height.is_predefined()) + { + // 5. 'height' is 'auto', 'top' and 'bottom' are not 'auto', then 'auto' values for 'margin-top' and + // 'margin-bottom' are set to 0 and solve for 'height' + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = 0; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = 0; + bottom = css_bottom.calc_percent(containing_block_size.height); + top = css_top.calc_percent(containing_block_size.height); + if(el->src_el()->is_replaced()) + { + height = el->height() - el->content_offset_height(); + int reminded = (containing_block_size.height - top - bottom) - height - el->content_offset_height(); + if(reminded > 0) + { + int divider = 0; + if (el->css().get_margins().top.is_predefined()) divider++; + if (el->css().get_margins().bottom.is_predefined()) divider++; + if (divider != 0) + { + if (el->css().get_margins().top.is_predefined()) el->m_margins.top = reminded / divider; + if (el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = reminded / divider; + } + } + height += el->content_offset_height(); + } else + { + height = containing_block_size.height - top - bottom; + } + if(!el->css().get_max_height().is_predefined()) + { + int max_height = el->css().get_max_height().calc_percent(containing_block_size.height); + if(height - el->content_offset_height() > max_height) + { + int reminded = height - el->content_offset_height() - max_height; + height = max_height; + int divider = 0; + if(el->css().get_margins().top.is_predefined()) divider++; + if(el->css().get_margins().bottom.is_predefined()) divider++; + if(divider != 0) + { + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = reminded / divider; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = reminded / divider; + } + height += el->content_offset_height(); + } + } + } else if(!css_top.is_predefined() && css_bottom.is_predefined() && !el_height.is_predefined()) + { + // 6. 'bottom' is 'auto', 'top' and 'height' are not 'auto', then set 'auto' values for 'margin-top' + // and 'margin-bottom' to 0 and solve for 'bottom' + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = 0; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = 0; + height = fix_height_min_max(el_height.calc_percent(containing_block_size.height)); + top = css_top.calc_percent(containing_block_size.height); + } else if(css_top.is_predefined() && css_bottom.is_predefined() && el_height.is_predefined()) + { + // If all three of 'top', 'height', and 'bottom' are auto, set 'top' to the static position and + // apply rule number three. + if(el->css().get_margins().top.is_predefined()) el->m_margins.top = 0; + if(el->css().get_margins().bottom.is_predefined()) el->m_margins.bottom = 0; + height = el->height(); + top = el_static_y - el->content_offset_top(); + } else + { + // If none of the three are 'auto': + height = fix_height_min_max(el_height.calc_percent(containing_block_size.height)); + top = css_top.calc_percent(containing_block_size.height); + bottom = css_bottom.calc_percent(containing_block_size.height); + int remained = containing_block_size.height - height - top - bottom; - if(el_position == element_position_fixed) - { - if(!css_left.is_predefined() || !css_right.is_predefined()) - { - if(!css_left.is_predefined() && css_right.is_predefined()) - { - el->m_pos.x = css_left.calc_percent(containing_block_size.width) + el->content_offset_left(); - } else if(css_left.is_predefined() && !css_right.is_predefined()) - { - el->m_pos.x = containing_block_size.width - css_right.calc_percent(containing_block_size.width) - el->m_pos.width - - el->content_offset_right(); - } else - { - el->m_pos.x = css_left.calc_percent(containing_block_size.width) + el->content_offset_left(); - el->m_pos.width = containing_block_size.width - - css_left.calc_percent(containing_block_size.width) - - css_right.calc_percent(containing_block_size.width) - - (el->content_offset_left() + el->content_offset_right()); - need_render = true; - } - } + if(el->css().get_margins().top.is_predefined() && el->css().get_margins().bottom.is_predefined()) + { + // If both 'margin-top' and 'margin-bottom' are 'auto', solve the equation under the extra + // constraint that the two margins get equal values. + el->m_margins.top = el->m_margins.bottom = remained / 2; + height += el->m_margins.top + el->m_margins.bottom; + } else + { + // If one of 'margin-top' or 'margin-bottom' is 'auto', solve the equation for that value. + if(el->css().get_margins().top.is_predefined()) + { + el->m_margins.top = remained; + height += el->m_margins.top; + } + if(el->css().get_margins().bottom.is_predefined()) + { + el->m_margins.bottom = remained; + height += el->m_margins.bottom; + } + } + } + el->m_pos.y = top + el->content_offset_top(); + if(el->m_pos.height != height - el->content_offset_height()) + { + el->m_pos.height = height - el->content_offset_height(); + need_render = true; + } - if(!css_top.is_predefined() || !css_bottom.is_predefined()) - { - if(!css_top.is_predefined() && css_bottom.is_predefined()) - { - el->m_pos.y = css_top.calc_percent(containing_block_size.height) + el->content_offset_top(); - } else if(css_top.is_predefined() && !css_bottom.is_predefined()) - { - el->m_pos.y = containing_block_size.height - css_bottom.calc_percent(containing_block_size.height) - el->m_pos.height - - el->content_offset_bottom(); - } else - { - el->m_pos.y = css_top.calc_percent(containing_block_size.height) + el->content_offset_top(); - el->m_pos.height = containing_block_size.height - - css_top.calc_percent(containing_block_size.height) - - css_bottom.calc_percent(containing_block_size.height) - - (el->content_offset_top() + el->content_offset_bottom()); - need_render = true; - } - } - } else - { - if(!css_left.is_predefined() || !css_right.is_predefined()) - { - if(!css_left.is_predefined() && css_right.is_predefined()) - { - el->m_pos.x = css_left.calc_percent(containing_block_size.height) + el->content_offset_left() - m_padding.left; - } else if(css_left.is_predefined() && !css_right.is_predefined()) - { - el->m_pos.x = m_pos.width + m_padding.right - css_right.calc_percent(containing_block_size.height) - el->m_pos.width - - el->content_offset_right(); - } else - { - el->m_pos.x = css_left.calc_percent(containing_block_size.height) + el->content_offset_left() - m_padding.left; - el->m_pos.width = m_pos.width + m_padding.left + m_padding.right - - css_left.calc_percent(containing_block_size.height) - - css_right.calc_percent(containing_block_size.height) - - (el->content_offset_left() + el->content_offset_right()); - if (new_width != -1) - { - el->m_pos.x += (el->m_pos.width - new_width) / 2; - el->m_pos.width = new_width; - } - need_render = true; - } - cvt_x = true; - } + // Calculate horizontal position + int right = 0; + int left = 0; + int width = 0; + // https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width + // 10.3.7 Absolutely positioned, non-replaced elements + if(css_left.is_predefined() && !css_right.is_predefined() && el_width.is_predefined()) + { + // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. + // Then solve for 'left' + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = 0; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = 0; + width = el->width(); + right = css_right.calc_percent(containing_block_size.width); + left = containing_block_size.width - width - right; + } else if(css_left.is_predefined() && css_right.is_predefined() && !el_width.is_predefined()) + { + // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of + // the element establishing the static-position containing block is 'ltr' set 'left' to the + // static position, otherwise set 'right' to the static position. Then solve for 'left' + // (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr'). + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = 0; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = 0; + left = el_static_x - el->content_offset_left(); + width = fix_width_min_max(el_width.calc_percent(containing_block_size.width)); + } else if(!css_left.is_predefined() && css_right.is_predefined() && el_width.is_predefined()) + { + // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . + // Then solve for 'right' + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = 0; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = 0; + width = el->width(); + left = css_left.calc_percent(containing_block_size.width); + } else if(css_left.is_predefined() && !css_right.is_predefined() && !el_width.is_predefined()) + { + // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left' + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = 0; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = 0; + right = css_right.calc_percent(containing_block_size.width); + width = fix_width_min_max(el_width.calc_percent(containing_block_size.width)); + left = containing_block_size.width - right - width; + } else if(!css_left.is_predefined() && !css_right.is_predefined() && el_width.is_predefined()) + { + // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width' + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = 0; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = 0; + left = css_left.calc_percent(containing_block_size.width); + right = css_right.calc_percent(containing_block_size.width); + if(el->src_el()->is_replaced()) + { + width = el->width() - el->content_offset_width(); + int reminded = (containing_block_size.width - left - right) - width - el->content_offset_width(); + if(reminded) + { + int divider = 0; + if (el->css().get_margins().left.is_predefined()) divider++; + if (el->css().get_margins().right.is_predefined()) divider++; + if (divider != 0) + { + if (el->css().get_margins().left.is_predefined()) el->m_margins.left = reminded / divider; + if (el->css().get_margins().right.is_predefined()) el->m_margins.right = reminded / divider; + } + } + width += el->content_offset_width(); + } else + { + width = containing_block_size.width - left - right; + } + if(!el->css().get_max_width().is_predefined()) + { + int max_width = el->css().get_max_width().calc_percent(containing_block_size.height); + if(width - el->content_offset_width() > max_width) + { + int reminded = width - el->content_offset_width() - max_width; + width = max_width; + int divider = 0; + if(el->css().get_margins().left.is_predefined()) divider++; + if(el->css().get_margins().right.is_predefined()) divider++; + if(divider != 0) + { + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = reminded / divider; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = reminded / divider; + } + width += el->content_offset_width(); + } + } + } else if(!css_left.is_predefined() && css_right.is_predefined() && !el_width.is_predefined()) + { + // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right' + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = 0; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = 0; + left = css_left.calc_percent(containing_block_size.width); + width = fix_width_min_max(el_width.calc_percent(containing_block_size.width)); + } else if(css_left.is_predefined() && css_right.is_predefined() && el_width.is_predefined()) + { + // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for + // 'margin-left' and 'margin-right' to 0. Then, if the 'direction' property of the element + // establishing the static-position containing block is 'ltr' set 'left' to the static position + // and apply rule number three below; otherwise, set 'right' to the static position and apply + // rule number one below. + if(el->css().get_margins().left.is_predefined()) el->m_margins.left = 0; + if(el->css().get_margins().right.is_predefined()) el->m_margins.right = 0; + width = el->width(); + left = el_static_x - el->content_offset_left(); + } else + { + // If none of the three is 'auto': + width = fix_width_min_max(el_width.calc_percent(containing_block_size.width)); + left = css_left.calc_percent(containing_block_size.width); + right = css_right.calc_percent(containing_block_size.width); + int remained = containing_block_size.width - width - left - right; - if(!css_top.is_predefined() || !css_bottom.is_predefined()) - { - if(!css_top.is_predefined() && css_bottom.is_predefined()) - { - el->m_pos.y = css_top.calc_percent(containing_block_size.height) + el->content_offset_top() - m_padding.top; - } else if(css_top.is_predefined() && !css_bottom.is_predefined()) - { - el->m_pos.y = m_pos.height + m_padding.bottom - css_bottom.calc_percent(containing_block_size.height) - el->m_pos.height - - el->content_offset_bottom(); - } else - { - el->m_pos.y = css_top.calc_percent(containing_block_size.height) + el->content_offset_top() - m_padding.top; - el->m_pos.height = m_pos.height + m_padding.top + m_padding.bottom - - css_top.calc_percent(containing_block_size.height) - - css_bottom.calc_percent(containing_block_size.height) - - (el->content_offset_top() + el->content_offset_bottom()); - if (new_height != -1) - { - el->m_pos.y += (el->m_pos.height - new_height) / 2; - el->m_pos.height = new_height; - } - need_render = true; - } - cvt_y = true; - } - } + if(el->css().get_margins().left.is_predefined() && el->css().get_margins().right.is_predefined()) + { + // If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra + // constraint that the two margins get equal values, unless this would make them negative, + // in which case when direction of the containing block is 'ltr' ('rtl'), set 'margin-left' + // ('margin-right') to zero and solve for 'margin-right' ('margin-left'). + el->m_margins.left = el->m_margins.right = remained / 2; + if(el->m_margins.left < 0) + { + el->m_margins.left = 0; + el->m_margins.right = remained; + } + width += el->m_margins.left + el->m_margins.right; + } else + { + // If one of 'margin-left' or 'margin-right' is 'auto', solve the equation + // for that value. If the values are over-constrained, ignore the value for 'left' + // (in case the 'direction' property of the containing block is 'rtl') or 'right' (in case + // 'direction' is 'ltr') and solve for that value. + if(el->css().get_margins().left.is_predefined()) + { + el->m_margins.left = remained; + width += el->m_margins.left; + } + if(el->css().get_margins().right.is_predefined()) + { + el->m_margins.right = remained; + width += el->m_margins.right; + } + } + } + el->m_pos.x = left + el->content_offset_left(); + if(el->m_pos.width != width - el->content_offset_width()) + { + el->m_pos.width = width - el->content_offset_width(); + need_render = true; + } - if(cvt_x || cvt_y) + if(el_position != element_position_fixed) { - int offset_x = 0; - int offset_y = 0; - auto cur_el = el->parent(); - auto this_el = shared_from_this(); - while(cur_el && cur_el != this_el) - { - offset_x += cur_el->m_pos.x; - offset_y += cur_el->m_pos.y; - cur_el = cur_el->parent(); - } - if(cvt_x) el->m_pos.x -= offset_x; - if(cvt_y) el->m_pos.y -= offset_y; + el->m_pos.x -= el_static_offset_x; + el->m_pos.y -= el_static_offset_y; } if(need_render) @@ -501,33 +710,45 @@ void litehtml::render_item::get_redraw_box(litehtml::position& pos, int x /*= 0* void litehtml::render_item::calc_document_size( litehtml::size& sz, litehtml::size& content_size, int x /*= 0*/, int y /*= 0*/ ) { - if(is_visible() && src_el()->css().get_position() != element_position_fixed) - { - sz.width = std::max(sz.width, x + right()); - sz.height = std::max(sz.height, y + bottom()); - - if(!src_el()->is_root() && !src_el()->is_body()) + if(css().get_display() != display_inline && css().get_display() != display_table_row) + { + if (is_visible() && src_el()->css().get_position() != element_position_fixed) { - content_size.width = std::max(content_size.width, x + right()); - content_size.height = std::max(content_size.height, y + bottom()); - } + sz.width = std::max(sz.width, x + right()); + sz.height = std::max(sz.height, y + bottom()); - // All children of tables and blocks with style other than "overflow: visible" are inside element. - // We can skip calculating size of children - if(src_el()->css().get_overflow() == overflow_visible && src_el()->css().get_display() != display_table) - { - for(auto& el : m_children) - { - el->calc_document_size(sz, content_size, x + m_pos.x, y + m_pos.y); - } - } + if (!src_el()->is_root() && !src_el()->is_body()) + { + content_size.width = std::max(content_size.width, x + right()); + content_size.height = std::max(content_size.height, y + bottom()); + } + + // All children of tables and blocks with style other than "overflow: visible" are inside element. + // We can skip calculating size of children + if (src_el()->css().get_overflow() == overflow_visible && src_el()->css().get_display() != display_table) + { + for (auto &el: m_children) + { + el->calc_document_size(sz, content_size, x + m_pos.x, y + m_pos.y); + } + } - if(src_el()->is_root() || src_el()->is_body()) + if (src_el()->is_root() || src_el()->is_body()) + { + content_size.width = std::max(content_size.width, x + right()); + content_size.height = std::max(content_size.height, y + bottom()); + } + } + } else + { + position::vector boxes; + get_inline_boxes(boxes); + for(auto& box : boxes) { - content_size.width += content_offset_right(); - content_size.height += content_offset_bottom(); + content_size.width = std::max(content_size.width, x + box.x + box.width); + content_size.height = std::max(content_size.height, y + box.y + box.height); } - } + } } void litehtml::render_item::draw_stacking_context( uint_ptr hdc, int x, int y, const position* clip, bool with_positioned ) @@ -984,7 +1205,7 @@ void litehtml::render_item::calc_cb_length(const css_length& len, int percent_ba out_value.type = litehtml::containing_block_context::cbc_value_type_percentage; } else { - out_value.value = src_el()->get_document()->to_pixels(len, src_el()->css().get_font_size()); + out_value.value = src_el()->get_document()->to_pixels(len, css().get_font_metrics(), 0); out_value.type = containing_block_context::cbc_value_type_absolute; } } @@ -1094,3 +1315,29 @@ litehtml::containing_block_context litehtml::render_item::calculate_containing_b return ret; } + +std::tuple<int, int> litehtml::render_item::element_static_offset(const std::shared_ptr<litehtml::render_item>& el) +{ + int offset_x = 0; + int offset_y = 0; + auto cur_el = el->parent(); + auto this_el = el->css().get_position() != element_position_fixed ? shared_from_this() : src_el()->get_document()->root_render(); + while(cur_el && cur_el != this_el) + { + offset_x += cur_el->m_pos.x; + offset_y += cur_el->m_pos.y; + cur_el = cur_el->parent(); + } + + if(el->css().get_position() == element_position_fixed || (is_root() && !src_el()->is_positioned())) + { + offset_x += this_el->m_pos.x; + offset_y += this_el->m_pos.y; + } else + { + offset_x += m_padding.left; + offset_y += m_padding.top; + } + + return {offset_x, offset_y}; +} diff --git a/libs/litehtml/src/render_table.cpp b/libs/litehtml/src/render_table.cpp index 543749ed12..16a143eb43 100644 --- a/libs/litehtml/src/render_table.cpp +++ b/libs/litehtml/src/render_table.cpp @@ -380,7 +380,7 @@ int litehtml::render_item_table::_render(int x, int y, const containing_block_co std::shared_ptr<litehtml::render_item> litehtml::render_item_table::init() { // Initialize Grid - m_grid = std::unique_ptr<table_grid>(new table_grid()); + m_grid = std::make_unique<table_grid>(); go_inside_table table_selector; table_rows_selector row_selector; @@ -417,10 +417,10 @@ std::shared_ptr<litehtml::render_item> litehtml::render_item_table::init() if(src_el()->css().get_border_collapse() == border_collapse_separate) { - int font_size = src_el()->css().get_font_size(); + auto fm = css().get_font_metrics(); document::ptr doc = src_el()->get_document(); - m_border_spacing_x = doc->to_pixels(src_el()->css().get_border_spacing_x(), font_size); - m_border_spacing_y = doc->to_pixels(src_el()->css().get_border_spacing_y(), font_size); + m_border_spacing_x = doc->to_pixels(src_el()->css().get_border_spacing_x(), fm, 0); + m_border_spacing_y = doc->to_pixels(src_el()->css().get_border_spacing_y(), fm, 0); } else { m_border_spacing_x = 0; diff --git a/libs/litehtml/src/string_id.cpp b/libs/litehtml/src/string_id.cpp index d356bef31f..d197d24469 100644 --- a/libs/litehtml/src/string_id.cpp +++ b/libs/litehtml/src/string_id.cpp @@ -1,12 +1,11 @@ #include "html.h" #include "string_id.h" -#include <assert.h> +#include <cassert> #ifndef LITEHTML_NO_THREADS - #include <Windows.h> - #include <newpluginapi.h> - static mir_cs mutex; - #define lock_guard mir_cslock lock(mutex) + #include <mutex> + static std::mutex mutex; + #define lock_guard std::lock_guard<std::mutex> lock(mutex) #else #define lock_guard #endif 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()); } } } diff --git a/libs/litehtml/src/stylesheet.cpp b/libs/litehtml/src/stylesheet.cpp index a7b6fb6a7e..bb428b1328 100644 --- a/libs/litehtml/src/stylesheet.cpp +++ b/libs/litehtml/src/stylesheet.cpp @@ -1,178 +1,134 @@ #include "html.h" #include "stylesheet.h" -#include <algorithm> -#include "document.h" -#include "gradient.h" +#include "css_parser.h" -#ifndef M_PI -# define M_PI 3.14159265358979323846 -#endif +namespace litehtml +{ -void litehtml::css::parse_stylesheet(const char* str, const char* baseurl, const std::shared_ptr<document>& doc, const media_query_list::ptr& media) +// https://www.w3.org/TR/css-syntax-3/#parse-a-css-stylesheet +template<class Input> // Input == string or css_token_vector +void css::parse_css_stylesheet(const Input& input, string baseurl, document::ptr doc, media_query_list_list::ptr media, bool top_level) { - string text = str; + if (doc && media) + doc->add_media_list(media); - // remove comments - string::size_type c_start = text.find("/*"); - while(c_start != string::npos) - { - string::size_type c_end = text.find("*/", c_start + 2); - if(c_end == string::npos) - { - text.erase(c_start); - break; - } - text.erase(c_start, c_end - c_start + 2); - c_start = text.find("/*"); - } + // To parse a CSS stylesheet, first parse a stylesheet. + auto rules = css_parser::parse_stylesheet(input, top_level); + bool import_allowed = top_level; - string::size_type pos = text.find_first_not_of(" \n\r\t"); - while(pos != string::npos) + // Interpret all of the resulting top-level qualified rules as style rules, defined below. + // If any style rule is invalid, or any at-rule is not recognized or is invalid according + // to its grammar or context, it's a parse error. Discard that rule. + for (auto rule : rules) { - while(pos != string::npos && text[pos] == '@') + if (rule->type == raw_rule::qualified) { - string::size_type sPos = pos; - pos = text.find_first_of("{;", pos); - if(pos != string::npos && text[pos] == '{') - { - pos = find_close_bracket(text, pos, '{', '}'); - } - if(pos != string::npos) - { - parse_atrule(text.substr(sPos, pos - sPos + 1), baseurl, doc, media); - } else - { - parse_atrule(text.substr(sPos), baseurl, doc, media); - } - - if(pos != string::npos) - { - pos = text.find_first_not_of(" \n\r\t", pos + 1); - } + if (parse_style_rule(rule, baseurl, doc, media)) + import_allowed = false; + continue; } - if(pos == string::npos) + // Otherwise: at-rule + switch (_id(lowcase(rule->name))) { + case _charset_: // ignored https://www.w3.org/TR/css-syntax-3/#charset-rule break; - } - - string::size_type style_start = text.find('{', pos); - string::size_type style_end = text.find('}', pos); - if(style_start != string::npos && style_end != string::npos) - { - auto str_style = text.substr(style_start + 1, style_end - style_start - 1); - style::ptr style = std::make_shared<litehtml::style>(); - style->add(str_style, baseurl ? baseurl : "", doc->container()); - - parse_selectors(text.substr(pos, style_start - pos), style, media); - if(media && doc) + case _import_: + if (import_allowed) + parse_import_rule(rule, baseurl, doc, media); + else + css_parse_error("incorrect placement of @import rule"); + break; + + // https://www.w3.org/TR/css-conditional-3/#at-media + // @media <media-query-list> { <stylesheet> } + case _media_: + { + if (rule->block.type != CURLY_BLOCK) break; + auto new_media = media; + auto mq_list = parse_media_query_list(rule->prelude, doc); + // An empty media query list evaluates to true. https://drafts.csswg.org/mediaqueries-5/#example-6f06ee45 + if (!mq_list.empty()) { - doc->add_media_list(media); + new_media = make_shared<media_query_list_list>(media ? *media : media_query_list_list()); + new_media->add(mq_list); } - - pos = style_end + 1; - } else - { - pos = string::npos; + parse_css_stylesheet(rule->block.value, baseurl, doc, new_media, false); + import_allowed = false; + break; } - - if(pos != string::npos) - { - pos = text.find_first_not_of(" \n\r\t", pos); + + default: + css_parse_error("unrecognized rule @" + rule->name); } } } -void litehtml::css::parse_gradient(const string &token, document_container *container, background_gradient& grad) +// https://drafts.csswg.org/css-cascade-5/#at-import +// `layer` and `supports` are not supported +// @import [ <url> | <string> ] <media-query-list>? +void css::parse_import_rule(raw_rule::ptr rule, string baseurl, document::ptr doc, media_query_list_list::ptr media) { - size_t pos1 = token.find('('); - size_t pos2 = token.find_last_of(')'); - std::string grad_str; - if(pos1 != std::string::npos) + auto tokens = rule->prelude; + int index = 0; + skip_whitespace(tokens, index); + auto tok = at(tokens, index); + string url; + auto parse_string = [](const css_token& tok, string& str) { - auto gradient_type_str = token.substr(0, pos1); - trim(gradient_type_str); - background_gradient::gradient_type gradient_type = (background_gradient::gradient_type) (value_index( - gradient_type_str, - "linear-gradient;repeating-linear-gradient;radial-gradient;repeating-radial-gradient;conic-gradient;repeating-conic-gradient", -2) + 1); - - if(pos2 != std::string::npos) - { - grad_str = token.substr(pos1 + 1, pos2 - pos1 - 1); - } else - { - grad_str = token.substr(pos1); - } - - if(gradient_type == background_gradient::linear_gradient || gradient_type == background_gradient::repeating_linear_gradient) - { - parse_linear_gradient(grad_str, container, grad); - } else if(gradient_type == background_gradient::radial_gradient || gradient_type == background_gradient::repeating_radial_gradient) - { - parse_radial_gradient(grad_str, container, grad); - } else if(gradient_type == background_gradient::conic_gradient || gradient_type == background_gradient::repeating_conic_gradient) - { - parse_conic_gradient(grad_str, container, grad); - } - if(grad.m_colors.size() >= 2) - { - grad.m_type = gradient_type; - } + if (tok.type != STRING) return false; + str = tok.str; + return true; + }; + bool ok = parse_url(tok, url) || parse_string(tok, url); + if (!ok) { + css_parse_error("invalid @import rule"); + return; } -} - -void litehtml::css::parse_css_url( const string& str, string& url ) -{ - url = ""; - size_t pos1 = str.find('('); - size_t pos2 = str.find(')'); - if(pos1 != string::npos && pos2 != string::npos) + document_container* container = doc->container(); + string css_text; + string css_baseurl = baseurl; + container->import_css(css_text, url, css_baseurl); + + auto new_media = media; + tokens = slice(tokens, index + 1); + auto mq_list = parse_media_query_list(tokens, doc); + if (!mq_list.empty()) { - url = str.substr(pos1 + 1, pos2 - pos1 - 1); - if(url.length()) - { - if(url[0] == '\'' || url[0] == '"') - { - url.erase(0, 1); - } - } - if(url.length()) - { - if(url[url.length() - 1] == '\'' || url[url.length() - 1] == '"') - { - url.erase(url.length() - 1, 1); - } - } + new_media = make_shared<media_query_list_list>(media ? *media : media_query_list_list()); + new_media->add(mq_list); } + + parse_css_stylesheet(css_text, css_baseurl, doc, new_media, true); } -bool litehtml::css::parse_selectors( const string& txt, const style::ptr& styles, const media_query_list::ptr& media ) +// https://www.w3.org/TR/css-syntax-3/#style-rules +bool css::parse_style_rule(raw_rule::ptr rule, string baseurl, document::ptr doc, media_query_list_list::ptr media) { - string selector = txt; - trim(selector); - string_vector tokens; - split_string(selector, tokens, ","); + // The prelude of the qualified rule is parsed as a <selector-list>. If this returns failure, the entire style rule is invalid. + auto list = parse_selector_list(rule->prelude, strict_mode, doc->mode()); + if (list.empty()) + { + css_parse_error("invalid selector"); + return false; + } - bool added_something = false; + style::ptr style = make_shared<litehtml::style>(); // style block + // The content of the qualified rule's block is parsed as a style block's contents. + style->add(rule->block.value, baseurl, doc->container()); - for(auto & token : tokens) + for (auto sel : list) { - css_selector::ptr new_selector = std::make_shared<css_selector>(media); - new_selector->m_style = styles; - trim(token); - if(new_selector->parse(token)) - { - new_selector->calc_specificity(); - add_selector(new_selector); - added_something = true; - } + sel->m_style = style; + sel->m_media_query = media; + sel->calc_specificity(); + add_selector(sel); } - - return added_something; + return true; } -void litehtml::css::sort_selectors() +void css::sort_selectors() { std::sort(m_selectors.begin(), m_selectors.end(), [](const css_selector::ptr& v1, const css_selector::ptr& v2) @@ -182,87 +138,4 @@ void litehtml::css::sort_selectors() ); } -void litehtml::css::parse_atrule(const string& text, const char* baseurl, const std::shared_ptr<document>& doc, const media_query_list::ptr& media) -{ - if(text.substr(0, 7) == "@import") - { - int sPos = 7; - string iStr; - iStr = text.substr(sPos); - if(iStr[iStr.length() - 1] == ';') - { - iStr.erase(iStr.length() - 1); - } - trim(iStr); - string_vector tokens; - split_string(iStr, tokens, " ", "", "(\""); - if(!tokens.empty()) - { - string url; - parse_css_url(tokens.front(), url); - if(url.empty()) - { - url = tokens.front(); - trim(url, "\""); - } - tokens.erase(tokens.begin()); - if(doc) - { - document_container* doc_cont = doc->container(); - if(doc_cont) - { - string css_text; - string css_baseurl; - if(baseurl) - { - css_baseurl = baseurl; - } - doc_cont->import_css(css_text, url, css_baseurl); - if(!css_text.empty()) - { - media_query_list::ptr new_media = media; - if(!tokens.empty()) - { - string media_str; - for(auto iter = tokens.begin(); iter != tokens.end(); iter++) - { - if(iter != tokens.begin()) - { - media_str += " "; - } - media_str += (*iter); - } - new_media = media_query_list::create_from_string(media_str, doc); - if(!new_media) - { - new_media = media; - } - } - parse_stylesheet(css_text.c_str(), css_baseurl.c_str(), doc, new_media); - } - } - } - } - } else if(text.substr(0, 6) == "@media") - { - string::size_type b1 = text.find_first_of('{'); - string::size_type b2 = text.find_last_of('}'); - if(b1 != string::npos) - { - string media_type = text.substr(6, b1 - 6); - trim(media_type); - media_query_list::ptr new_media = media_query_list::create_from_string(media_type, doc); - - string media_style; - if(b2 != string::npos) - { - media_style = text.substr(b1 + 1, b2 - b1 - 1); - } else - { - media_style = text.substr(b1 + 1); - } - - parse_stylesheet(media_style.c_str(), baseurl, doc, new_media); - } - } -} +} // namespace litehtml diff --git a/libs/litehtml/src/table.cpp b/libs/litehtml/src/table.cpp index 08c167b222..f91326726b 100644 --- a/libs/litehtml/src/table.cpp +++ b/libs/litehtml/src/table.cpp @@ -13,7 +13,7 @@ void litehtml::table_grid::add_cell(const std::shared_ptr<render_item>& el) while( is_rowspanned( (int) m_cells.size() - 1, (int) m_cells.back().size() ) ) { - m_cells.back().push_back(table_cell()); + m_cells.back().emplace_back(); } m_cells.back().push_back(cell); @@ -30,7 +30,7 @@ void litehtml::table_grid::begin_row(const std::shared_ptr<render_item>& row) std::vector<table_cell> r; m_cells.push_back(r); - m_rows.push_back(table_row(0, row)); + m_rows.emplace_back(0, row); } @@ -72,7 +72,7 @@ void litehtml::table_grid::finish() m_columns.clear(); for(int i = 0; i < m_cols_count; i++) { - m_columns.push_back(table_column(0, 0)); + m_columns.emplace_back(0, 0); } for(int col = 0; col < m_cols_count; col++) @@ -256,12 +256,12 @@ void litehtml::table_grid::distribute_width( int width, int start, int end ) add = round_f( (float) width * ((float) (column->max_width - column->min_width) / (float) cols_width) ); if(column->width + add >= column->min_width) { - column->width += add; + column->width += add; added_width += add; } else { added_width += (column->width - column->min_width) * (add / abs(add)); - column->width = column->min_width; + column->width = column->min_width; } } if(added_width < width && step) @@ -595,15 +595,15 @@ int& litehtml::table_column_accessor_width::get( table_column& col ) litehtml::table_row::table_row(int h, const std::shared_ptr<render_item>& row) { - min_height = 0; - height = h; - el_row = row; - border_bottom = 0; - border_top = 0; - top = 0; - bottom = 0; - if (row) - { - css_height = row->src_el()->css().get_height(); - } + min_height = 0; + height = h; + el_row = row; + border_bottom = 0; + border_top = 0; + top = 0; + bottom = 0; + if (row) + { + css_height = row->src_el()->css().get_height(); + } } diff --git a/libs/litehtml/src/url.cpp b/libs/litehtml/src/url.cpp index 13076e338b..caa8fbb9d3 100644 --- a/libs/litehtml/src/url.cpp +++ b/libs/litehtml/src/url.cpp @@ -136,7 +136,7 @@ url resolve(const url& b, const url& r) if (r.has_scheme()) { return r; } else if (r.has_authority()) { - return url(b.scheme(), r.authority(), r.path(), r.query(), r.fragment()); + return {b.scheme(), r.authority(), r.path(), r.query(), r.fragment()}; } else if (r.has_path()) { // The relative URL path is either an absolute path or a relative @@ -145,18 +145,18 @@ url resolve(const url& b, const url& r) // against the base path and build the URL using the resolved path. if (is_url_path_absolute(r.path())) { - return url(b.scheme(), b.authority(), r.path(), r.query(), r.fragment()); + return {b.scheme(), b.authority(), r.path(), r.query(), r.fragment()}; } else { string path = url_path_resolve(b.path(), r.path()); - return url(b.scheme(), b.authority(), path, r.query(), r.fragment()); + return {b.scheme(), b.authority(), path, r.query(), r.fragment()}; } } else if (r.has_query()) { - return url(b.scheme(), b.authority(), b.path(), r.query(), r.fragment()); + return {b.scheme(), b.authority(), b.path(), r.query(), r.fragment()}; } else { // The resolved URL never includes the base URL fragment (i.e., it // always includes the reference URL fragment). - return url(b.scheme(), b.authority(), b.path(), b.query(), r.fragment()); + return {b.scheme(), b.authority(), b.path(), b.query(), r.fragment()}; } } diff --git a/libs/litehtml/src/utf8_strings.cpp b/libs/litehtml/src/utf8_strings.cpp index 864a4d0147..f720f6d26c 100644 --- a/libs/litehtml/src/utf8_strings.cpp +++ b/libs/litehtml/src/utf8_strings.cpp @@ -4,27 +4,17 @@ namespace litehtml { -utf8_to_utf32::utf8_to_utf32(const char* val) +// consume one utf-8 char and increment index accordingly +// if str[index] == 0 index is not incremented +char32_t read_utf8_char(const string& str, int& index) { - m_utf8 = (const byte*) val; - if (!m_utf8) return; - - while (true) - { - char32_t wch = get_char(); - if (!wch) break; - m_str += wch; - } -} - -char32_t utf8_to_utf32::get_char() -{ - char32_t b1 = getb(); - - if (!b1) + auto getb = [&]() -> byte { - return 0; - } + if (!str[index]) return 0; + return str[index++]; + }; + + byte b1 = getb(); // Determine whether we are dealing // with a one-, two-, three-, or four- @@ -38,31 +28,34 @@ char32_t utf8_to_utf32::get_char() { // 2-byte sequence: 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx char32_t r = (b1 & 0x1f) << 6; - r |= get_next_utf8(getb()); + r |= getb() & 0x3f; return r; } else if ((b1 & 0xf0) == 0xe0) { // 3-byte sequence: zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx char32_t r = (b1 & 0x0f) << 12; - r |= get_next_utf8(getb()) << 6; - r |= get_next_utf8(getb()); + r |= (getb() & 0x3f) << 6; + r |= getb() & 0x3f; return r; } else if ((b1 & 0xf8) == 0xf0) { - // 4-byte sequence: 11101110wwwwzzzzyy + 110111yyyyxxxxxx - // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx - // (uuuuu = wwww + 1) - char32_t b2 = get_next_utf8(getb()); - char32_t b3 = get_next_utf8(getb()); - char32_t b4 = get_next_utf8(getb()); - return ((b1 & 7) << 18) | ((b2 & 0x3f) << 12) | - ((b3 & 0x3f) << 6) | (b4 & 0x3f); + // 4-byte sequence: uuuzzzzzzyyyyyyxxxxxx = 11110uuu 10zzzzzz 10yyyyyy 10xxxxxx + byte b2 = getb() & 0x3f; + byte b3 = getb() & 0x3f; + byte b4 = getb() & 0x3f; + return ((b1 & 7) << 18) | (b2 << 12) | (b3 << 6) | b4; } - //bad start for UTF-8 multi-byte sequence - return '?'; + return 0xFFFD; +} + +// No error handling, str must be valid UTF-8 (it is ensured by document::parse_html and css_parser::parse_stylesheet). +// Currently used only in css parser, where actual char value is not needed, so it returns void. +void prev_utf8_char(const string& str, int& index) +{ + while (index && ((byte)str[--index] >> 6) == 0b10); // skip continuation bytes } void append_char(string& str, char32_t code) @@ -78,7 +71,7 @@ void append_char(string& str, char32_t code) } else if (0xd800 <= code && code <= 0xdfff) { - // error: surrogate + // error: unexpected surrogate (code is UTF-32, not UTF-16) } else if (code <= 0xFFFF) { @@ -95,10 +88,17 @@ void append_char(string& str, char32_t code) } } -utf32_to_utf8::utf32_to_utf8(const std::u32string& wstr) +utf8_to_utf32::utf8_to_utf32(const string& val) +{ + int index = 0; + while (char32_t ch = read_utf8_char(val, index)) + m_str += ch; +} + +utf32_to_utf8::utf32_to_utf8(const std::u32string& val) { - for (auto ch: wstr) + for (auto ch : val) append_char(m_str, ch); } -} // namespace litehtml
\ No newline at end of file +} // namespace litehtml diff --git a/libs/litehtml/src/web_color.cpp b/libs/litehtml/src/web_color.cpp index a2e74ff58d..51c6e762f1 100644 --- a/libs/litehtml/src/web_color.cpp +++ b/libs/litehtml/src/web_color.cpp @@ -1,14 +1,24 @@ #include "html.h" #include "web_color.h" -#include <cstring> +#include "css_parser.h" -const litehtml::web_color litehtml::web_color::transparent = web_color(0, 0, 0, 0); -const litehtml::web_color litehtml::web_color::black = web_color(0, 0, 0, 255); -const litehtml::web_color litehtml::web_color::white = web_color(255, 255, 255, 255); +namespace litehtml +{ + +const web_color web_color::transparent = web_color(0, 0, 0, 0); +const web_color web_color::black = web_color(0, 0, 0, 255); +const web_color web_color::white = web_color(255, 255, 255, 255); +const web_color web_color::current_color = web_color(true); + +gradient gradient::transparent; -litehtml::background_gradient litehtml::background_gradient::transparent; +struct def_color +{ + const char* name; + const char* rgb; +}; -litehtml::def_color litehtml::g_def_colors[] = +def_color g_def_colors[] = { {"transparent","rgba(0, 0, 0, 0)"}, {"AliceBlue","#F0F8FF"}, @@ -156,121 +166,280 @@ litehtml::def_color litehtml::g_def_colors[] = {"WhiteSmoke","#F5F5F5"}, {"Yellow","#FFFF00"}, {"YellowGreen","#9ACD32"}, - {nullptr,nullptr} }; - -litehtml::web_color litehtml::web_color::from_string(const string& _str, document_container* callback) +// <hex-color> https://drafts.csswg.org/css-color-4/#typedef-hex-color +bool parse_hash_color(const css_token& tok, web_color& color) { - auto str = _str.c_str(); - if(!str[0]) + if (tok.type != HASH) return false; + + string s = tok.str; + int len = (int)s.size(); + if (!is_one_of(len, 3, 4, 6, 8)) return false; + for (auto ch : s) if (!is_hex_digit(ch)) return false; + + string r, g, b, a = "ff"; + if (len == 3 || len == 4) { - return web_color(0, 0, 0); + r = {s[0], s[0]}; + g = {s[1], s[1]}; + b = {s[2], s[2]}; + if (len == 4) + a = {s[3], s[3]}; } - if(str[0] == '#') + else // 6 or 8 { - string red; - string green; - string blue; - if(strlen(str + 1) == 3) - { - red += str[1]; - red += str[1]; - green += str[2]; - green += str[2]; - blue += str[3]; - blue += str[3]; - } else if(strlen(str + 1) == 6) - { - red += str[1]; - red += str[2]; - green += str[3]; - green += str[4]; - blue += str[5]; - blue += str[6]; - } - char* sss = nullptr; - web_color clr; - clr.red = (byte) strtol(red.c_str(), &sss, 16); - clr.green = (byte) strtol(green.c_str(), &sss, 16); - clr.blue = (byte) strtol(blue.c_str(), &sss, 16); - return clr; - } else if(!strncmp(str, "rgb", 3)) + r = {s[0], s[1]}; + g = {s[2], s[3]}; + b = {s[4], s[5]}; + if (len == 8) + a = {s[6], s[7]}; + } + auto read_two_hex_digits = [](auto str) { - string s = str; + return byte(16 * digit_value(str[0]) + digit_value(str[1])); + }; - string::size_type pos = s.find_first_of('('); - if(pos != string::npos) - { - s.erase(s.begin(), s.begin() + pos + 1); - } - pos = s.find_last_of(')'); - if(pos != string::npos) + color = web_color( + read_two_hex_digits(r), + read_two_hex_digits(g), + read_two_hex_digits(b), + read_two_hex_digits(a)); + return true; +} + +float clamp(float x, float min, float max) +{ + if (x < min) return min; + if (x > max) return max; + return x; +} + +// [ <number> | <percentage> | none ]{3} [ / [<alpha-value> | none] ]? +bool parse_modern_syntax(const css_token_vector& tokens, bool is_hsl, + css_length& x, css_length& y, css_length& z, css_length& a) +{ + auto n = tokens.size(); + if (!(n == 3 || n == 5)) return false; + if (is_hsl) + { + // [<hue> | none] [<number> | <percentage> | none]{2} [ / [<alpha-value> | none] ]? + // <hue> = <number> | <angle> + if (!x.from_token(tokens[0], f_number, "none")) { - s.erase(s.begin() + pos, s.end()); + float hue; + if (!parse_angle(tokens[0], hue)) return false; + x.set_value(hue, css_units_none); } + } + else if (!x.from_token(tokens[0], f_number | f_percentage, "none")) return false; + if (!y.from_token(tokens[1], f_number | f_percentage, "none")) return false; + if (!z.from_token(tokens[2], f_number | f_percentage, "none")) return false; + if (n == 5) + { + if (tokens[3].ch != '/') return false; + // <alpha-value> = <number> | <percentage> + if (!a.from_token(tokens[4], f_number | f_percentage, "none")) return false; + } + // convert nones to zeros + // https://drafts.csswg.org/css-color-4/#missing + // For all other purposes, a missing component behaves as a zero value... + for (auto t : {&x,&y,&z,&a}) if (t->is_predefined()) t->set_value(0, css_units_none); + return true; +} - std::vector<string> tokens; - split_string(s, tokens, ", \t"); - - web_color clr; +// https://drafts.csswg.org/css-color-4/#rgb-functions +// Values outside these ranges are not invalid, but are clamped to the ranges defined here at parsed-value time. +byte calc_percent_and_clamp(const css_length& val, float max = 255) +{ + float x = val.val(); + if (val.units() == css_units_percentage) x = (x / 100) * max; + x = clamp(x, 0, max); + return (byte)round(max == 1 ? x * 255 : x); +} - if(tokens.size() >= 1) clr.red = (byte) atoi(tokens[0].c_str()); - if(tokens.size() >= 2) clr.green = (byte) atoi(tokens[1].c_str()); - if(tokens.size() >= 3) clr.blue = (byte) atoi(tokens[2].c_str()); - if(tokens.size() >= 4) clr.alpha = (byte) (t_strtod(tokens[3].c_str(), nullptr) * 255.0); +// https://drafts.csswg.org/css-color-4/#rgb-functions +bool parse_rgb_func(const css_token& tok, web_color& color) +{ + if (tok.type != CV_FUNCTION || !is_one_of(lowcase(tok.name), "rgb", "rgba")) + return false; - return clr; - } else + auto list = parse_comma_separated_list(tok.value); + int n = (int)list.size(); + if (!is_one_of(n, 1, 3, 4)) + return false; + + css_length r, g, b, a(1, css_units_none); + // legacy syntax: <percentage>#{3} , <alpha-value>? | <number>#{3} , <alpha-value>? + if (n != 1) { - string rgb = resolve_name(str, callback); - if(!rgb.empty()) - { - return from_string(rgb.c_str(), callback); - } + for (const auto& item : list) if (item.size() != 1) return false; + auto type = list[0][0].type; + if (!is_one_of(type, PERCENTAGE, NUMBER)) return false; + int options = type == PERCENTAGE ? f_percentage : f_number; + if (!r.from_token(list[0][0], options)) return false; + if (!g.from_token(list[1][0], options)) return false; + if (!b.from_token(list[2][0], options)) return false; + // <alpha-value> = <number> | <percentage> + if (n == 4 && !a.from_token(list[3][0], f_number | f_percentage)) return false; } - return web_color(0, 0, 0); + // modern syntax: [ <number> | <percentage> | none ]{3} [ / [<alpha-value> | none] ]? + else if (!parse_modern_syntax(tok.value, false, r, g, b, a)) return false; + + color = web_color( + calc_percent_and_clamp(r), + calc_percent_and_clamp(g), + calc_percent_and_clamp(b), + calc_percent_and_clamp(a, 1)); + return true; +} + +// https://drafts.csswg.org/css-color-4/#hsl-to-rgb +void hsl_to_rgb(float hue, float sat, float light, float& r, float& g, float& b) +{ + hue = fmod(hue, 360.f); + + if (hue < 0) + hue += 360; + + sat /= 100; + light /= 100; + + auto f = [=](float n) + { + float k = fmod(n + hue / 30, 12.f); + float a = sat * min(light, 1 - light); + return light - a * max(-1.f, min({k - 3, 9 - k, 1.f})); + }; + + r = f(0); + g = f(8); + b = f(4); } -litehtml::string litehtml::web_color::resolve_name(const string& name, document_container* callback) +// https://drafts.csswg.org/css-color-4/#the-hsl-notation +bool parse_hsl_func(const css_token& tok, web_color& color) { - for(int i=0; g_def_colors[i].name; i++) + if (tok.type != CV_FUNCTION || !is_one_of(lowcase(tok.name), "hsl", "hsla")) + return false; + + auto list = parse_comma_separated_list(tok.value); + int n = (int)list.size(); + if (!is_one_of(n, 1, 3, 4)) + return false; + + css_length h, s, l, a(1, css_units_none); + // legacy syntax: <hue>, <percentage>, <percentage>, <alpha-value>? + if (n != 1) { - if(!t_strcasecmp(name.c_str(), g_def_colors[i].name)) - { - return g_def_colors[i].rgb; - } + for (const auto& item : list) if (item.size() != 1) return false; + const auto& tok0 = list[0][0]; + float hue; + // <hue> = <number> | <angle> + // number is interpreted as a number of degrees https://drafts.csswg.org/css-color-4/#typedef-hue + if (tok0.type == NUMBER) hue = tok0.n.number; + else if (!parse_angle(tok0, hue)) return false; + h.set_value(hue, css_units_none); + + if (!s.from_token(list[1][0], f_percentage)) return false; + if (!l.from_token(list[2][0], f_percentage)) return false; + if (n == 4 && !a.from_token(list[3][0], f_number | f_percentage)) return false; } - if (callback) - { - string clr = callback->resolve_color(name); - return clr; - } - return ""; + // modern syntax: [<hue> | none] [<percentage> | <number> | none]{2} [ / [<alpha-value> | none] ]? + else if (!parse_modern_syntax(tok.value, true, h, s, l, a)) return false; + + float hue = h.val(); + // no percent calculation needed for sat and lit because 0% ~ 0, 100% ~ 100 + float sat = s.val(); + float lit = l.val(); + // For historical reasons, if the saturation is less than 0% it is clamped to 0% at parsed-value time, before being converted to an sRGB color. + if (sat < 0) sat = 0; + + // Note: Chrome and Firefox treat invalid hsl values differently. + // + // Note: at this point, sat is not clamped at 100, and lit is not clamped at all. The standard + // mentions only clamping sat at 0. As a result, returning rgb values may not be inside [0,1]. + float r, g, b; + hsl_to_rgb(hue, sat, lit, r, g, b); + + r = clamp(r, 0, 1); + g = clamp(g, 0, 1); + b = clamp(b, 0, 1); + + color = web_color( + (byte)round(r * 255), + (byte)round(g * 255), + (byte)round(b * 255), + calc_percent_and_clamp(a, 1)); + return true; } -bool litehtml::web_color::is_color(const string& str, document_container* callback) +// https://drafts.csswg.org/css-color-5/#typedef-color-function +bool parse_func_color(const css_token& tok, web_color& color) { - if (!t_strncasecmp(str.c_str(), "rgb", 3) || str[0] == '#') + return parse_rgb_func(tok, color) || parse_hsl_func(tok, color); +} + +string resolve_name(const string& name, document_container* container) +{ + for (auto clr : g_def_colors) { - return true; + if (equal_i(name, clr.name)) + return clr.rgb; } - if (t_isalpha(str[0]) && resolve_name(str, callback) != "") + + if (container) + return container->resolve_color(name); + + return ""; +} + +bool parse_name_color(const css_token& tok, web_color& color, document_container* container) +{ + if (tok.type != IDENT) return false; + if (tok.ident() == "currentcolor") { + color = web_color::current_color; return true; } - return false; + string str = resolve_name(tok.name, container); + auto tokens = normalize(str, f_componentize | f_remove_whitespace); + if (tokens.size() != 1) return false; + return parse_color(tokens[0], color, container); } -litehtml::string litehtml::web_color::to_string() const +// https://drafts.csswg.org/css-color-5/#typedef-color +bool parse_color(const css_token& tok, web_color& color, document_container* container) { - char str[9]; - if(alpha) - { + return + parse_hash_color(tok, color) || + parse_func_color(tok, color) || + parse_name_color(tok, color, container); +} + +web_color web_color::darken(double fraction) const +{ + int v_red = (int)red; + int v_blue = (int)blue; + int v_green = (int)green; + v_red = (int)max(v_red - (v_red * fraction), 0.0); + v_blue = (int)max(v_blue - (v_blue * fraction), 0.0); + v_green = (int)max(v_green - (v_green * fraction), 0.0); + return {(byte)v_red, (byte)v_green, (byte)v_blue, alpha}; +} + + +string web_color::to_string() const +{ + char str[9]; + if(alpha) + { t_snprintf(str, 9, "%02X%02X%02X%02X", red, green, blue, alpha); - } else - { + } else + { t_snprintf(str, 9, "%02X%02X%02X", red, green, blue); - } - return str; + } + return str; } + +} // namespace litehtml
\ No newline at end of file diff --git a/plugins/NewStory/src/history_control.h b/plugins/NewStory/src/history_control.h index 3db27132d4..ff2e8cd31e 100644 --- a/plugins/NewStory/src/history_control.h +++ b/plugins/NewStory/src/history_control.h @@ -70,6 +70,8 @@ class NSWebPage : public document_container void release_clip(HDC hdc);
void set_clip(const position &pos, const border_radiuses &bdr_radius) override;
+ void on_mouse_event(const litehtml::element::ptr &, litehtml::mouse_event) override {}
+
void clear_images();
static int CALLBACK EnumFontsProc(const LOGFONT *lplf, const TEXTMETRIC *lptm, DWORD dwType, LPARAM lpData);
diff --git a/plugins/NewStory/src/templates.cpp b/plugins/NewStory/src/templates.cpp index dc0c97e928..4a48c1b404 100644 --- a/plugins/NewStory/src/templates.cpp +++ b/plugins/NewStory/src/templates.cpp @@ -54,8 +54,8 @@ static void AppendImage(CMStringW &buf, const CMStringW &wszUrl, const CMStringW if (auto *pImage = pItem->pOwner->webPage.find_image(wszUrl)) {
if (pImage->GetHeight() < uMaxHeight)
iHeight = pImage->GetHeight();
-
- buf.AppendFormat(L"<img style=\"height: %d;\" src=\"%s\"/><br>", iHeight, wszUrl.c_str());
+
+ buf.AppendFormat(L"<img style=\"height: %dpx;\" src=\"%s\"/><br>", iHeight, wszUrl.c_str());
}
else buf.AppendFormat(L"<img src=\"%s\"/><br>", wszUrl.c_str());
}
|