#include "html.h"
#include "html_tag.h"
#include "document.h"
#include "iterators.h"
#include "stylesheet.h"
#include "table.h"
#include
#include
#include "el_before_after.h"
#include "num_cvt.h"
#include "line_box.h"
#include
#include "render_item.h"
litehtml::html_tag::html_tag(const std::shared_ptr& doc) : element(doc)
{
m_tag = empty_id;
m_id = empty_id;
}
litehtml::html_tag::html_tag(const element::ptr& parent, const string& style) : element(parent->get_document()),
m_tag(empty_id),
m_id(empty_id)
{
litehtml::style st;
st.add(style);
add_style(st);
this->parent(parent);
compute_styles();
}
bool litehtml::html_tag::appendChild(const element::ptr &el)
{
if(el)
{
el->parent(shared_from_this());
m_children.push_back(el);
return true;
}
return false;
}
bool litehtml::html_tag::removeChild(const element::ptr &el)
{
if(el && el->parent() == shared_from_this())
{
el->parent(nullptr);
m_children.erase(std::remove(m_children.begin(), m_children.end(), el), m_children.end());
return true;
}
return false;
}
void litehtml::html_tag::clearRecursive()
{
for(auto& el : m_children)
{
el->clearRecursive();
el->parent(nullptr);
}
m_children.clear();
}
litehtml::string_id litehtml::html_tag::id() const
{
return m_id;
}
litehtml::string_id litehtml::html_tag::tag() const
{
return m_tag;
}
const char* litehtml::html_tag::get_tagName() const
{
return _s(m_tag).c_str();
}
void litehtml::html_tag::set_tagName( const char* _tag )
{
string tag = _tag;
lcase(tag);
m_tag = _id(tag);
}
void litehtml::html_tag::set_attr( const char* _name, const char* _val )
{
if(_name && _val)
{
string name = _name;
lcase(name);
m_attrs[name] = _val;
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, " " );
m_classes.clear();
for (auto& cls : m_str_classes) m_classes.push_back(_id(cls));
}
else if (name == "id")
{
string val = _val;
lcase(val);
m_id = _id(val);
}
}
}
const char* litehtml::html_tag::get_attr( const char* name, const char* def ) const
{
auto attr = m_attrs.find(name);
if(attr != m_attrs.end())
{
return attr->second.c_str();
}
return def;
}
litehtml::elements_list litehtml::html_tag::select_all(const string& selector )
{
css_selector sel;
sel.parse(selector);
return select_all(sel);
}
litehtml::elements_list litehtml::html_tag::select_all(const css_selector& selector )
{
litehtml::elements_list res;
select_all(selector, res);
return res;
}
void litehtml::html_tag::select_all(const css_selector& selector, elements_list& res)
{
if(select(selector))
{
res.push_back(shared_from_this());
}
for(auto& el : m_children)
{
el->select_all(selector, res);
}
}
litehtml::element::ptr litehtml::html_tag::select_one( const string& selector )
{
css_selector sel;
sel.parse(selector);
return select_one(sel);
}
litehtml::element::ptr litehtml::html_tag::select_one( const css_selector& selector )
{
if(select(selector))
{
return shared_from_this();
}
for(auto& el : m_children)
{
element::ptr res = el->select_one(selector);
if(res)
{
return res;
}
}
return nullptr;
}
void litehtml::html_tag::apply_stylesheet( const litehtml::css& stylesheet )
{
if(is_root())
{
int i = 0;
i++;
}
for(const auto& sel : stylesheet.selectors())
{
// optimization
{
const auto& r = sel->m_right;
if (r.m_tag != star_id && r.m_tag != m_tag)
continue;
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())
continue;
}
}
int apply = select(*sel, false);
if(apply != select_no_match)
{
used_selector::ptr us = std::unique_ptr(new used_selector(sel, false));
if(sel->is_media_valid())
{
auto apply_before_after = [&]()
{
const auto& content_property = sel->m_style->get_property(_content_);
bool content_none = content_property.m_type == prop_type_string && content_property.m_string == "none";
bool create = !content_none && (sel->m_right.m_attrs.size() > 1 || sel->m_right.m_tag != star_id);
element::ptr el;
if(apply & select_match_with_after)
{
el = get_element_after(*sel->m_style, create);
} else if(apply & select_match_with_before)
{
el = get_element_before(*sel->m_style, create);
} else
{
return;
}
if(el)
{
if(!content_none)
{
el->add_style(*sel->m_style);
} else
{
el->parent()->removeChild(el);
}
} else
{
if(!content_none)
{
add_style(*sel->m_style);
}
}
us->m_used = true;
};
if(apply & select_match_pseudo_class)
{
if(select(*sel, true))
{
if((apply & (select_match_with_after | select_match_with_before)))
{
apply_before_after();
} else
{
add_style(*sel->m_style);
us->m_used = true;
}
}
} else if((apply & (select_match_with_after | select_match_with_before)))
{
apply_before_after();
} else
{
add_style(*sel->m_style);
us->m_used = true;
}
}
m_used_styles.push_back(std::move(us));
}
}
for(auto& el : m_children)
{
if(el->css().get_display() != display_inline_text)
{
el->apply_stylesheet(stylesheet);
}
}
}
void litehtml::html_tag::get_content_size( size& sz, int max_width )
{
sz.height = 0;
if(m_css.get_display() == display_block)
{
sz.width = max_width;
} else
{
sz.width = 0;
}
}
void litehtml::html_tag::draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr &ri)
{
position pos = ri->pos();
pos.x += x;
pos.y += y;
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_overflow() > overflow_visible)
{
position border_box = pos;
border_box += ri->get_paddings();
border_box += ri->get_borders();
border_radiuses bdr_radius = m_css.get_borders().radius.calc_percents(border_box.width, border_box.height);
bdr_radius -= ri->get_borders();
bdr_radius -= ri->get_paddings();
get_document()->container()->set_clip(pos, bdr_radius);
}
draw_list_marker(hdc, pos);
if(m_css.get_overflow() > overflow_visible)
{
get_document()->container()->del_clip();
}
}
}
litehtml::string litehtml::html_tag::get_custom_property(string_id name, const string& default_value) const
{
const property_value& value = m_style.get_property(name);
if (value.m_type == prop_type_string)
{
return value.m_string;
}
else if (auto _parent = parent())
{
return _parent->get_custom_property(name, default_value);
}
return default_value;
}
template
const Type& litehtml::html_tag::get_property_impl(string_id name, bool inherited, const Type& default_value, uint_ptr css_properties_member_offset) const
{
const property_value& value = m_style.get_property(name);
if (value.m_type == property_value_type)
{
return value.*property_value_member;
}
else if (inherited || value.m_type == prop_type_inherit)
{
if (auto _parent = parent())
{
return *(Type*)((byte*)&_parent->css() + css_properties_member_offset);
}
return default_value;
}
// value must be invalid here
//assert(value.m_type == prop_type_invalid);
return default_value;
}
int litehtml::html_tag::get_enum_property(string_id name, bool inherited, int default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
int litehtml::html_tag::get_int_property(string_id name, bool inherited, int default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
litehtml::css_length litehtml::html_tag::get_length_property(string_id name, bool inherited, css_length default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
litehtml::web_color litehtml::html_tag::get_color_property(string_id name, bool inherited, web_color default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
litehtml::string litehtml::html_tag::get_string_property(string_id name, bool inherited, const string& default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
float litehtml::html_tag::get_number_property(string_id name, bool inherited, float default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
litehtml::string_vector litehtml::html_tag::get_string_vector_property(string_id name, bool inherited, const string_vector& default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
litehtml::int_vector litehtml::html_tag::get_int_vector_property(string_id name, bool inherited, const int_vector& default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
litehtml::length_vector litehtml::html_tag::get_length_vector_property(string_id name, bool inherited, const length_vector& default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
litehtml::size_vector litehtml::html_tag::get_size_vector_property(string_id name, bool inherited, const size_vector& default_value, uint_ptr css_properties_member_offset) const
{
return get_property_impl(name, inherited, default_value, css_properties_member_offset);
}
void litehtml::html_tag::compute_styles(bool recursive)
{
const char* style = get_attr("style");
document::ptr doc = get_document();
if (style)
{
m_style.add(style, "", doc->container());
}
m_style.subst_vars(this);
m_css.compute(this, doc);
if (recursive)
{
for (const auto& el : m_children)
{
el->compute_styles();
}
}
}
bool litehtml::html_tag::is_white_space() const
{
return false;
}
int litehtml::html_tag::select(const string& selector)
{
css_selector sel;
sel.parse(selector);
return select(sel, true);
}
int litehtml::html_tag::select(const css_selector& selector, bool apply_pseudo)
{
int right_res = select(selector.m_right, apply_pseudo);
if(right_res == select_no_match)
{
return select_no_match;
}
element::ptr el_parent = parent();
if(selector.m_left)
{
if (!el_parent)
{
return select_no_match;
}
switch(selector.m_combinator)
{
case combinator_descendant:
{
bool is_pseudo = false;
element::ptr res = find_ancestor(*selector.m_left, apply_pseudo, &is_pseudo);
if(!res)
{
return select_no_match;
} else
{
if(is_pseudo)
{
right_res |= select_match_pseudo_class;
}
}
}
break;
case combinator_child:
{
int res = el_parent->select(*selector.m_left, apply_pseudo);
if(res == select_no_match)
{
return select_no_match;
} else
{
if(right_res != select_match_pseudo_class)
{
right_res |= res;
}
}
}
break;
case combinator_adjacent_sibling:
{
bool is_pseudo = false;
element::ptr res = el_parent->find_adjacent_sibling(shared_from_this(), *selector.m_left, apply_pseudo, &is_pseudo);
if(!res)
{
return select_no_match;
} else
{
if(is_pseudo)
{
right_res |= select_match_pseudo_class;
}
}
}
break;
case combinator_general_sibling:
{
bool is_pseudo = false;
element::ptr res = el_parent->find_sibling(shared_from_this(), *selector.m_left, apply_pseudo, &is_pseudo);
if(!res)
{
return select_no_match;
} else
{
if(is_pseudo)
{
right_res |= select_match_pseudo_class;
}
}
}
break;
default:
right_res = select_no_match;
}
}
return right_res;
}
int litehtml::html_tag::select(const css_element_selector& selector, bool apply_pseudo)
{
if(selector.m_tag != star_id && selector.m_tag != m_tag)
{
return select_no_match;
}
int res = select_match;
for(const auto& attr : selector.m_attrs)
{
switch(attr.type)
{
case select_class:
if (std::find(m_classes.begin(), m_classes.end(), attr.name) == m_classes.end())
{
return select_no_match;
}
break;
case select_id:
if (attr.name != m_id)
{
return select_no_match;
}
break;
case select_pseudo_element:
if(attr.name == _after_)
{
if(selector.m_attrs.size() == 1 && selector.m_tag == star_id && m_tag != __tag_after_)
{
return select_no_match;
}
res |= select_match_with_after;
} else if(attr.name == _before_)
{
if(selector.m_attrs.size() == 1 && selector.m_tag == star_id && m_tag != __tag_before_)
{
return select_no_match;
}
res |= select_match_with_before;
} else
{
return select_no_match;
}
break;
case select_pseudo_class:
if(apply_pseudo)
{
if (select_pseudoclass(attr) == select_no_match)
{
return select_no_match;
}
} else
{
res |= select_match_pseudo_class;
}
break;
default:
if (select_attribute(attr) == select_no_match)
{
return select_no_match;
}
}
}
return res;
}
int litehtml::html_tag::select_pseudoclass(const css_attribute_selector& sel)
{
element::ptr el_parent = parent();
switch (sel.name)
{
case _only_child_:
if (!el_parent || !el_parent->is_only_child(shared_from_this(), false))
{
return select_no_match;
}
break;
case _only_of_type_:
if (!el_parent || !el_parent->is_only_child(shared_from_this(), true))
{
return select_no_match;
}
break;
case _first_child_:
if (!el_parent || !el_parent->is_nth_child(shared_from_this(), 0, 1, false))
{
return select_no_match;
}
break;
case _first_of_type_:
if (!el_parent || !el_parent->is_nth_child(shared_from_this(), 0, 1, true))
{
return select_no_match;
}
break;
case _last_child_:
if (!el_parent || !el_parent->is_nth_last_child(shared_from_this(), 0, 1, false))
{
return select_no_match;
}
break;
case _last_of_type_:
if (!el_parent || !el_parent->is_nth_last_child(shared_from_this(), 0, 1, true))
{
return select_no_match;
}
break;
case _nth_child_:
case _nth_of_type_:
case _nth_last_child_:
case _nth_last_of_type_:
{
if (!el_parent) return select_no_match;
int num = sel.a;
int off = sel.b;
if (!num && !off) return select_no_match;
switch (sel.name)
{
case _nth_child_:
if (!el_parent->is_nth_child(shared_from_this(), num, off, false))
{
return select_no_match;
}
break;
case _nth_of_type_:
if (!el_parent->is_nth_child(shared_from_this(), num, off, true))
{
return select_no_match;
}
break;
case _nth_last_child_:
if (!el_parent->is_nth_last_child(shared_from_this(), num, off, false))
{
return select_no_match;
}
break;
case _nth_last_of_type_:
if (!el_parent->is_nth_last_child(shared_from_this(), num, off, true))
{
return select_no_match;
}
break;
}
}
break;
case _not_:
if (select(*sel.sel, true))
{
return select_no_match;
}
break;
case _lang_:
if (!get_document()->match_lang(sel.val))
{
return select_no_match;
}
break;
default:
if (std::find(m_pseudo_classes.begin(), m_pseudo_classes.end(), sel.name) == m_pseudo_classes.end())
{
return select_no_match;
}
break;
}
return select_match;
}
int litehtml::html_tag::select_attribute(const css_attribute_selector& sel)
{
const char* attr_value = get_attr(_s(sel.name).c_str());
switch (sel.type)
{
case select_exists:
if (!attr_value)
{
return select_no_match;
}
break;
case select_equal:
if (!attr_value || strcmp(attr_value, sel.val.c_str()))
{
return select_no_match;
}
break;
case select_contain_str:
if (!attr_value || !strstr(attr_value, sel.val.c_str()))
{
return select_no_match;
}
break;
case select_start_str:
if (!attr_value || strncmp(attr_value, sel.val.c_str(), sel.val.length()))
{
return select_no_match;
}
break;
case select_end_str:
if (!attr_value)
{
return select_no_match;
}
else if (strncmp(attr_value, sel.val.c_str(), sel.val.length()))
{
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;
}
}
break;
}
return select_match;
}
litehtml::element::ptr litehtml::html_tag::find_ancestor(const css_selector& selector, bool apply_pseudo, bool* is_pseudo)
{
element::ptr el_parent = parent();
if (!el_parent)
{
return nullptr;
}
int res = el_parent->select(selector, apply_pseudo);
if(res != select_no_match)
{
if(is_pseudo)
{
if(res & select_match_pseudo_class)
{
*is_pseudo = true;
} else
{
*is_pseudo = false;
}
}
return el_parent;
}
return el_parent->find_ancestor(selector, apply_pseudo, is_pseudo);
}
void litehtml::html_tag::parse_attributes()
{
for(auto& el : m_children)
{
el->parse_attributes();
}
}
void litehtml::html_tag::get_text( string& text )
{
for (auto& el : m_children)
{
el->get_text(text);
}
}
bool litehtml::html_tag::is_body() const
{
return false;
}
void litehtml::html_tag::set_data( const char* /*data*/ )
{
}
bool litehtml::html_tag::on_mouse_over()
{
bool ret = false;
element::ptr el = shared_from_this();
while(el)
{
if(el->set_pseudo_class(_hover_, true))
{
ret = true;
}
el = el->parent();
}
return ret;
}
bool litehtml::html_tag::on_mouse_leave()
{
bool ret = false;
element::ptr el = shared_from_this();
while(el)
{
if(el->set_pseudo_class(_hover_, false))
{
ret = true;
}
if(el->set_pseudo_class(_active_, false))
{
ret = true;
}
el = el->parent();
}
return ret;
}
bool litehtml::html_tag::on_lbutton_down()
{
bool ret = false;
element::ptr el = shared_from_this();
while (el)
{
if (el->set_pseudo_class(_active_, true))
{
ret = true;
}
el = el->parent();
}
return ret;
}
bool litehtml::html_tag::on_lbutton_up()
{
bool ret = false;
element::ptr el = shared_from_this();
while (el)
{
if (el->set_pseudo_class(_active_, false))
{
ret = true;
}
el = el->parent();
}
on_click();
return ret;
}
void litehtml::html_tag::on_click()
{
if (!is_root())
{
element::ptr el_parent = parent();
if (el_parent)
{
el_parent->on_click();
}
}
}
bool litehtml::html_tag::is_break() const
{
return false;
}
void litehtml::html_tag::draw_background(uint_ptr hdc, int x, int y, const position *clip,
const std::shared_ptr &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())
{
auto v_offset = ri->get_draw_vertical_offset();
pos.y += v_offset;
pos.height -= v_offset;
const background* bg = get_background();
if(bg)
{
std::vector bg_paint;
init_background_paint(pos, bg_paint, bg, ri);
if(is_root())
{
for(auto& b : bg_paint)
{
b.clip_box = *clip;
b.border_box = *clip;
}
}
get_document()->container()->draw_background(hdc, bg_paint);
}
position border_box = pos;
border_box += ri->get_paddings();
border_box += ri->get_borders();
borders bdr = m_css.get_borders();
if(bdr.is_visible())
{
bdr.radius = m_css.get_borders().radius.calc_percents(border_box.width, border_box.height);
get_document()->container()->draw_borders(hdc, bdr, border_box, is_root());
}
}
} else
{
const background* bg = get_background();
position::vector boxes;
ri->get_inline_boxes(boxes);
std::vector bg_paint;
position content_box;
for(auto box = boxes.begin(); box != boxes.end(); box++)
{
box->x += x;
box->y += y;
if(box->does_intersect(clip))
{
content_box = *box;
content_box -= ri->get_borders();
content_box -= ri->get_paddings();
if(bg)
{
init_background_paint(content_box, bg_paint, bg, ri);
}
css_borders bdr;
// set left borders radius for the first box
if(box == boxes.begin())
{
bdr.radius.bottom_left_x = m_css.get_borders().radius.bottom_left_x;
bdr.radius.bottom_left_y = m_css.get_borders().radius.bottom_left_y;
bdr.radius.top_left_x = m_css.get_borders().radius.top_left_x;
bdr.radius.top_left_y = m_css.get_borders().radius.top_left_y;
}
// set right borders radius for the last box
if(box == boxes.end() - 1)
{
bdr.radius.bottom_right_x = m_css.get_borders().radius.bottom_right_x;
bdr.radius.bottom_right_y = m_css.get_borders().radius.bottom_right_y;
bdr.radius.top_right_x = m_css.get_borders().radius.top_right_x;
bdr.radius.top_right_y = m_css.get_borders().radius.top_right_y;
}
bdr.top = m_css.get_borders().top;
bdr.bottom = m_css.get_borders().bottom;
if(box == boxes.begin())
{
bdr.left = m_css.get_borders().left;
}
if(box == boxes.end() - 1)
{
bdr.right = m_css.get_borders().right;
}
if(bg)
{
for (auto& bgp : bg_paint)
{
bgp.border_radius = bdr.radius.calc_percents(bgp.border_box.width, bgp.border_box.width);
}
get_document()->container()->draw_background(hdc, bg_paint);
}
if(bdr.is_visible())
{
borders b = bdr;
b.radius = bdr.radius.calc_percents(box->width, box->height);
get_document()->container()->draw_borders(hdc, b, *box, false);
}
}
}
}
}
bool litehtml::html_tag::set_pseudo_class( string_id cls, bool add )
{
bool ret = false;
if(add)
{
if(std::find(m_pseudo_classes.begin(), m_pseudo_classes.end(), cls) == m_pseudo_classes.end())
{
m_pseudo_classes.push_back(cls);
ret = true;
}
} else
{
auto pi = std::find(m_pseudo_classes.begin(), m_pseudo_classes.end(), cls);
if(pi != m_pseudo_classes.end())
{
m_pseudo_classes.erase(pi);
ret = true;
}
}
return ret;
}
bool litehtml::html_tag::set_class( const char* pclass, bool add )
{
string_vector classes;
bool changed = false;
split_string( pclass, classes, " " );
if(add)
{
for( auto & _class : classes )
{
if(std::find(m_str_classes.begin(), m_str_classes.end(), _class) == m_str_classes.end())
{
m_str_classes.push_back( std::move( _class ) );
changed = true;
}
}
} else
{
for( const auto & _class : classes )
{
auto end = std::remove(m_str_classes.begin(), m_str_classes.end(), _class);
if(end != m_str_classes.end())
{
m_str_classes.erase(end, m_str_classes.end());
changed = true;
}
}
}
if( changed )
{
string class_string;
join_string(class_string, m_str_classes, " ");
set_attr("class", class_string.c_str());
return true;
}
else
{
return false;
}
}
bool litehtml::html_tag::is_replaced() const
{
return false;
}
void litehtml::html_tag::init_background_paint(position pos, std::vector& bg_paint, const background* bg, const std::shared_ptr& ri)
{
bg_paint = { background_paint() };
if (!bg) return;
int bg_count = std::max((int)bg->m_image.size(), 1);
bg_paint.resize(bg_count);
for (int i = 0; i < bg_count; i++)
{
init_one_background_paint(i, pos, bg_paint[i], bg, ri);
}
bg_paint.back().color = bg->m_color;
}
void litehtml::html_tag::init_one_background_paint(int i, position pos, background_paint& bg_paint, const background* bg, const std::shared_ptr& ri)
{
bg_paint.image = i < bg->m_image.size() ? bg->m_image[i] : "";
bg_paint.baseurl = bg->m_baseurl;
bg_paint.attachment = i < bg->m_attachment.size() ? (background_attachment)bg->m_attachment[i] : background_attachment_scroll;
bg_paint.repeat = i < bg->m_repeat.size() ? (background_repeat)bg->m_repeat[i] : background_repeat_repeat;
int clip = i < bg->m_clip.size() ? bg->m_clip[i] : background_box_border;
int origin = i < bg->m_origin.size() ? bg->m_origin[i] : background_box_padding;
const css_size auto_auto(css_length::predef_value(background_size_auto), css_length::predef_value(background_size_auto));
css_size size = i < bg->m_size.size() ? bg->m_size[i] : auto_auto;
css_length position_x = i < bg->m_position_x.size() ? bg->m_position_x[i] : css_length(0, css_units_percentage);
css_length position_y = i < bg->m_position_y.size() ? bg->m_position_y[i] : css_length(0, css_units_percentage);
position content_box = pos;
position padding_box = pos;
padding_box += ri->get_paddings();
position border_box = padding_box;
border_box += ri->get_borders();
switch(clip)
{
case background_box_padding:
bg_paint.clip_box = padding_box;
break;
case background_box_content:
bg_paint.clip_box = content_box;
break;
default:
bg_paint.clip_box = border_box;
break;
}
switch(origin)
{
case background_box_border:
bg_paint.origin_box = border_box;
break;
case background_box_content:
bg_paint.origin_box = content_box;
break;
default:
bg_paint.origin_box = padding_box;
break;
}
if(!bg_paint.image.empty())
{
get_document()->container()->get_image_size(bg_paint.image.c_str(), bg_paint.baseurl.c_str(), bg_paint.image_size);
if(bg_paint.image_size.width && bg_paint.image_size.height)
{
litehtml::size img_new_sz = bg_paint.image_size;
double img_ar_width = (double) bg_paint.image_size.width / (double) bg_paint.image_size.height;
double img_ar_height = (double) bg_paint.image_size.height / (double) bg_paint.image_size.width;
if(size.width.is_predefined())
{
switch(size.width.predef())
{
case background_size_contain:
if( (int) ((double) bg_paint.origin_box.width * img_ar_height) <= bg_paint.origin_box.height )
{
img_new_sz.width = bg_paint.origin_box.width;
img_new_sz.height = (int) ((double) bg_paint.origin_box.width * img_ar_height);
} else
{
img_new_sz.height = bg_paint.origin_box.height;
img_new_sz.width = (int) ((double) bg_paint.origin_box.height * img_ar_width);
}
break;
case background_size_cover:
if( (int) ((double) bg_paint.origin_box.width * img_ar_height) >= bg_paint.origin_box.height )
{
img_new_sz.width = bg_paint.origin_box.width;
img_new_sz.height = (int) ((double) bg_paint.origin_box.width * img_ar_height);
} else
{
img_new_sz.height = bg_paint.origin_box.height;
img_new_sz.width = (int) ((double) bg_paint.origin_box.height * img_ar_width);
}
break;
break;
case background_size_auto:
if(!size.height.is_predefined())
{
img_new_sz.height = size.height.calc_percent(bg_paint.origin_box.height);
img_new_sz.width = (int) ((double) img_new_sz.height * img_ar_width);
}
break;
}
} else
{
img_new_sz.width = size.width.calc_percent(bg_paint.origin_box.width);
if(size.height.is_predefined())
{
img_new_sz.height = (int) ((double) img_new_sz.width * img_ar_height);
} else
{
img_new_sz.height = size.height.calc_percent(bg_paint.origin_box.height);
}
}
bg_paint.image_size = img_new_sz;
bg_paint.position_x = bg_paint.origin_box.x + (int) position_x.calc_percent(bg_paint.origin_box.width - bg_paint.image_size.width);
bg_paint.position_y = bg_paint.origin_box.y + (int) position_y.calc_percent(bg_paint.origin_box.height - bg_paint.image_size.height);
}
}
bg_paint.border_radius = m_css.get_borders().radius.calc_percents(border_box.width, border_box.height);
bg_paint.border_box = border_box;
bg_paint.is_root = is_root();
}
void litehtml::html_tag::draw_list_marker( uint_ptr hdc, const position& pos )
{
list_marker lm;
size img_size;
if (css().get_list_style_image() != "")
{
lm.image = css().get_list_style_image();
lm.baseurl = css().get_list_style_image_baseurl().c_str();
get_document()->container()->get_image_size(lm.image.c_str(), lm.baseurl, img_size);
} else
{
lm.baseurl = nullptr;
}
int ln_height = css().get_line_height();
int sz_font = css().get_font_size();
lm.pos.x = pos.x;
lm.pos.width = sz_font - sz_font * 2 / 3;
lm.color = css().get_color();
lm.marker_type = css().get_list_style_type();
lm.font = css().get_font();
if (css().get_list_style_type() >= list_style_type_armenian)
{
lm.pos.y = pos.y;
lm.pos.height = pos.height;
lm.index = atoi(get_attr("list_index", "0"));
}
else
{
lm.pos.height = sz_font - sz_font * 2 / 3;
lm.pos.y = pos.y + ln_height / 2 - lm.pos.height / 2;
lm.index = -1;
}
if(img_size.width && img_size.height)
{
if(lm.pos.y + img_size.height > pos.y + pos.height)
{
lm.pos.y = pos.y + pos.height - img_size.height;
}
if(img_size.width > lm.pos.width)
{
lm.pos.x -= img_size.width - lm.pos.width;
}
lm.pos.width = img_size.width;
lm.pos.height = img_size.height;
}
if (m_css.get_list_style_position() == list_style_position_outside)
{
if (m_css.get_list_style_type() >= list_style_type_armenian)
{
if(lm.font)
{
auto tw_space = get_document()->container()->text_width(" ", lm.font);
lm.pos.x = pos.x - tw_space * 2;
lm.pos.width = tw_space;
} else
{
lm.pos.width = 0;
}
}
else
{
lm.pos.x -= sz_font;
}
}
if (m_css.get_list_style_type() >= list_style_type_armenian)
{
auto marker_text = get_list_marker_text(lm.index);
lm.pos.height = ln_height;
if (marker_text.empty())
{
get_document()->container()->draw_list_marker(hdc, lm);
}
else
{
if(lm.font)
{
marker_text += ".";
auto tw = get_document()->container()->text_width(marker_text.c_str(), lm.font);
auto text_pos = lm.pos;
text_pos.move_to(text_pos.right() - tw, text_pos.y);
text_pos.width = tw;
get_document()->container()->draw_text(hdc, marker_text.c_str(), lm.font, lm.color, text_pos);
}
}
}
else
{
get_document()->container()->draw_list_marker(hdc, lm);
}
}
litehtml::string litehtml::html_tag::get_list_marker_text(int index)
{
switch (m_css.get_list_style_type())
{
case litehtml::list_style_type_decimal:
return std::to_string(index);
case litehtml::list_style_type_decimal_leading_zero:
{
auto txt = std::to_string(index);
if (txt.length() == 1)
{
txt = "0" + txt;
}
return txt;
}
case litehtml::list_style_type_lower_latin:
case litehtml::list_style_type_lower_alpha:
return num_cvt::to_latin_lower(index);
case litehtml::list_style_type_lower_greek:
return num_cvt::to_greek_lower(index);
case litehtml::list_style_type_upper_alpha:
case litehtml::list_style_type_upper_latin:
return num_cvt::to_latin_upper(index);
case litehtml::list_style_type_lower_roman:
return num_cvt::to_roman_lower(index);
case litehtml::list_style_type_upper_roman:
return num_cvt::to_roman_upper(index);
default:
return "";
// case litehtml::list_style_type_armenian:
// case litehtml::list_style_type_georgian:
// case litehtml::list_style_type_hebrew:
// case litehtml::list_style_type_hiragana:
// case litehtml::list_style_type_hiragana_iroha:
// case litehtml::list_style_type_katakana:
// case litehtml::list_style_type_katakana_iroha:
// case litehtml::list_style_type_none:
// case litehtml::list_style_type_circle:
// case litehtml::list_style_type_disc:
// case litehtml::list_style_type_square:
// case litehtml::list_style_type_cjk_ideographic:
// break;
}
}
bool litehtml::html_tag::is_nth_child(const element::ptr& el, int num, int off, bool of_type) 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(el == child)
{
if(num != 0)
{
if((idx - off) >= 0 && (idx - off) % num == 0)
{
return true;
}
} else if(idx == off)
{
return true;
}
return false;
}
idx++;
}
if(el == child) break;
}
}
return false;
}
bool litehtml::html_tag::is_nth_last_child(const element::ptr& el, int num, int off, bool of_type) 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(el == (*child))
{
if(num != 0)
{
if((idx - off) >= 0 && (idx - off) % num == 0)
{
return true;
}
} else if(idx == off)
{
return true;
}
return false;
}
idx++;
}
if(el == (*child)) break;
}
}
return false;
}
litehtml::element::ptr litehtml::html_tag::find_adjacent_sibling( const element::ptr& el, const css_selector& selector, bool apply_pseudo /*= true*/, bool* is_pseudo /*= 0*/ )
{
element::ptr ret;
for(auto& e : m_children)
{
if(e->css().get_display() != display_inline_text)
{
if(e == el)
{
if(ret)
{
int res = ret->select(selector, apply_pseudo);
if(res != select_no_match)
{
if(is_pseudo)
{
if(res & select_match_pseudo_class)
{
*is_pseudo = true;
} else
{
*is_pseudo = false;
}
}
return ret;
}
}
return nullptr;
} else
{
ret = e;
}
}
}
return nullptr;
}
litehtml::element::ptr litehtml::html_tag::find_sibling(const element::ptr& el, const css_selector& selector, bool apply_pseudo /*= true*/, bool* is_pseudo /*= 0*/)
{
element::ptr ret = nullptr;
for(auto& e : m_children)
{
if(e->css().get_display() != display_inline_text)
{
if(e == el)
{
return ret;
} else if(!ret)
{
int res = e->select(selector, apply_pseudo);
if(res != select_no_match)
{
if(is_pseudo)
{
if(res & select_match_pseudo_class)
{
*is_pseudo = true;
} else
{
*is_pseudo = false;
}
}
ret = e;
}
}
}
}
return nullptr;
}
bool litehtml::html_tag::is_only_child(const element::ptr& el, bool of_type) const
{
int child_count = 0;
for(const auto& child : m_children)
{
if(child->css().get_display() != display_inline_text)
{
if( !of_type || (of_type && el->tag() == child->tag()) )
{
child_count++;
}
if(child_count > 1) break;
}
}
if(child_count > 1)
{
return false;
}
return true;
}
litehtml::element::ptr litehtml::html_tag::get_element_before(const style& style, bool create)
{
if(!m_children.empty())
{
if( m_children.front()->tag() == __tag_before_ )
{
return m_children.front();
}
}
if(create)
{
return add_pseudo_before(style);
}
return nullptr;
}
litehtml::element::ptr litehtml::html_tag::get_element_after(const style& style, bool create)
{
if(!m_children.empty())
{
if( m_children.back()->tag() == __tag_after_ )
{
return m_children.back();
}
}
if(create)
{
return add_pseudo_after(style);
}
return nullptr;
}
void litehtml::html_tag::handle_counter_properties()
{
const auto& reset_property = m_style.get_property(string_id::_counter_reset_);
if (reset_property.m_type == prop_type_string_vector) {
auto reset_function = [&](const string_id&name_id, const int value) {
reset_counter(name_id, value);
};
parse_counter_tokens(reset_property.m_string_vector, 0, reset_function);
return;
}
const auto& inc_property = m_style.get_property(string_id::_counter_increment_);
if (inc_property.m_type == prop_type_string_vector) {
auto inc_function = [&](const string_id&name_id, const int value) {
increment_counter(name_id, value);
};
parse_counter_tokens(inc_property.m_string_vector, 1, inc_function);
return;
}
}
void litehtml::html_tag::add_style(const style& style)
{
m_style.combine(style);
handle_counter_properties();
}
void litehtml::html_tag::refresh_styles()
{
for (auto& el : m_children)
{
if(el->css().get_display() != display_inline_text)
{
el->refresh_styles();
}
}
m_style.clear();
for (auto& usel : m_used_styles)
{
usel->m_used = false;
if(usel->m_selector->is_media_valid())
{
int apply = select(*usel->m_selector, false);
if(apply != select_no_match)
{
if(apply & select_match_pseudo_class)
{
if(select(*usel->m_selector, true))
{
if(apply & select_match_with_after)
{
element::ptr el = get_element_after(*usel->m_selector->m_style, false);
if(el)
{
el->add_style(*usel->m_selector->m_style);
}
} else if(apply & select_match_with_before)
{
element::ptr el = get_element_before(*usel->m_selector->m_style, false);
if(el)
{
el->add_style(*usel->m_selector->m_style);
}
}
else
{
add_style(*usel->m_selector->m_style);
usel->m_used = true;
}
}
} else if(apply & select_match_with_after)
{
element::ptr el = get_element_after(*usel->m_selector->m_style, false);
if(el)
{
el->add_style(*usel->m_selector->m_style);
}
} else if(apply & select_match_with_before)
{
element::ptr el = get_element_before(*usel->m_selector->m_style, false);
if(el)
{
el->add_style(*usel->m_selector->m_style);
}
} else
{
add_style(*usel->m_selector->m_style);
usel->m_used = true;
}
}
}
}
}
const litehtml::background* litehtml::html_tag::get_background(bool own_only)
{
if(own_only)
{
// return own background with check for empty one
if(m_css.get_bg().is_empty())
{
return nullptr;
}
return &m_css.get_bg();
}
if(m_css.get_bg().is_empty())
{
// if this is root element () try to get background from body
if (is_root())
{
for (const auto& el : m_children)
{
if( el->is_body() )
{
// return own body background
return el->get_background(true);
}
}
}
return nullptr;
}
if(is_body())
{
element::ptr el_parent = parent();
if (el_parent)
{
if (!el_parent->get_background(true))
{
// parent of body will draw background for body
return nullptr;
}
}
}
return &m_css.get_bg();
}
litehtml::string litehtml::html_tag::dump_get_name()
{
if(m_tag == empty_id)
{
return "anon [html_tag]";
}
return _s(m_tag) + " [html_tag]";
}