summaryrefslogtreecommitdiff
path: root/libs/litehtml/src
diff options
context:
space:
mode:
Diffstat (limited to 'libs/litehtml/src')
-rw-r--r--libs/litehtml/src/background.cpp304
-rw-r--r--libs/litehtml/src/codepoint.cpp2
-rw-r--r--libs/litehtml/src/css_borders.cpp2
-rw-r--r--libs/litehtml/src/css_length.cpp111
-rw-r--r--libs/litehtml/src/css_properties.cpp183
-rw-r--r--libs/litehtml/src/css_selector.cpp901
-rw-r--r--libs/litehtml/src/document.cpp168
-rw-r--r--libs/litehtml/src/el_before_after.cpp11
-rw-r--r--libs/litehtml/src/el_image.cpp26
-rw-r--r--libs/litehtml/src/el_space.cpp4
-rw-r--r--libs/litehtml/src/el_table.cpp43
-rw-r--r--libs/litehtml/src/el_td.cpp35
-rw-r--r--libs/litehtml/src/el_text.cpp58
-rw-r--r--libs/litehtml/src/el_tr.cpp8
-rw-r--r--libs/litehtml/src/element.cpp15
-rw-r--r--libs/litehtml/src/encodings.cpp734
-rw-r--r--libs/litehtml/src/flex_line.cpp8
-rw-r--r--libs/litehtml/src/gradient.cpp1020
-rw-r--r--libs/litehtml/src/gumbo/CMakeLists.txt8
-rw-r--r--libs/litehtml/src/gumbo/char_ref.c2
-rw-r--r--libs/litehtml/src/gumbo/char_ref.rl2
-rw-r--r--libs/litehtml/src/gumbo/error.c13
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo.h4
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo/error.h6
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo/tag_enum.h2
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo/tag_gperf.h418
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo/tag_sizes.h2
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo/tag_strings.h2
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo/utf8.h2
-rw-r--r--libs/litehtml/src/gumbo/include/gumbo/util.h6
-rw-r--r--libs/litehtml/src/gumbo/parser.c25
-rw-r--r--libs/litehtml/src/gumbo/tag.c7
-rw-r--r--libs/litehtml/src/gumbo/tag.in2
-rw-r--r--libs/litehtml/src/gumbo/tokenizer.c9
-rw-r--r--libs/litehtml/src/gumbo/utf8.c4
-rw-r--r--libs/litehtml/src/gumbo/vector.c2
-rw-r--r--libs/litehtml/src/html.cpp21
-rw-r--r--libs/litehtml/src/html_tag.cpp271
-rw-r--r--libs/litehtml/src/line_box.cpp8
-rw-r--r--libs/litehtml/src/media_query.cpp951
-rw-r--r--libs/litehtml/src/render_block.cpp18
-rw-r--r--libs/litehtml/src/render_image.cpp8
-rw-r--r--libs/litehtml/src/render_inline_context.cpp10
-rw-r--r--libs/litehtml/src/render_item.cpp593
-rw-r--r--libs/litehtml/src/render_table.cpp8
-rw-r--r--libs/litehtml/src/string_id.cpp9
-rw-r--r--libs/litehtml/src/style.cpp1907
-rw-r--r--libs/litehtml/src/stylesheet.cpp321
-rw-r--r--libs/litehtml/src/table.cpp32
-rw-r--r--libs/litehtml/src/url.cpp10
-rw-r--r--libs/litehtml/src/utf8_strings.cpp72
-rw-r--r--libs/litehtml/src/web_color.cpp349
52 files changed, 5072 insertions, 3665 deletions
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