#include "html.h" #include "render_item.h" #include "document.h" #include #include litehtml::render_item::render_item(std::shared_ptr _src_el) : m_element(std::move(_src_el)), m_skip(false) { document::ptr doc = src_el()->get_document(); 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) { int ret; calc_outlines(containing_block_size.width); m_pos.clear(); m_pos.move_to(x, y); int content_left = content_offset_left(); int content_top = content_offset_top(); m_pos.x += content_left; m_pos.y += content_top; if(src_el()->is_block_formatting_context() || ! fmt_ctx) { formatting_context fmt; fmt.push_position(content_left, content_top); ret = _render(x, y, containing_block_size, &fmt, second_pass); fmt.apply_relative_shift(containing_block_size); } else { fmt_ctx->push_position(x + content_left, y + content_top); ret = _render(x, y, containing_block_size, fmt_ctx, second_pass); fmt_ctx->pop_position(x + content_left, y + content_top); } return ret; } void litehtml::render_item::calc_outlines( int parent_width ) { m_padding.left = m_element->css().get_padding().left.calc_percent(parent_width); m_padding.right = m_element->css().get_padding().right.calc_percent(parent_width); m_borders.left = m_element->css().get_borders().left.width.calc_percent(parent_width); m_borders.right = m_element->css().get_borders().right.width.calc_percent(parent_width); m_margins.left = m_element->css().get_margins().left.calc_percent(parent_width); m_margins.right = m_element->css().get_margins().right.calc_percent(parent_width); m_margins.top = m_element->css().get_margins().top.calc_percent(parent_width); m_margins.bottom = m_element->css().get_margins().bottom.calc_percent(parent_width); m_padding.top = m_element->css().get_padding().top.calc_percent(parent_width); m_padding.bottom = m_element->css().get_padding().bottom.calc_percent(parent_width); } int litehtml::render_item::calc_auto_margins(int parent_width) { if ((src_el()->css().get_display() == display_block || src_el()->css().get_display() == display_table) && src_el()->css().get_position() != element_position_absolute && src_el()->css().get_float() == float_none) { if (src_el()->css().get_margins().left.is_predefined() && src_el()->css().get_margins().right.is_predefined()) { int el_width = m_pos.width + m_borders.left + m_borders.right + m_padding.left + m_padding.right; if (el_width <= parent_width) { m_margins.left = (parent_width - el_width) / 2; m_margins.right = (parent_width - el_width) - m_margins.left; } else { m_margins.left = 0; m_margins.right = 0; } return m_margins.left; } else if (src_el()->css().get_margins().left.is_predefined() && !src_el()->css().get_margins().right.is_predefined()) { int el_width = m_pos.width + m_borders.left + m_borders.right + m_padding.left + m_padding.right + m_margins.right; m_margins.left = parent_width - el_width; if (m_margins.left < 0) m_margins.left = 0; return m_margins.left; } else if (!src_el()->css().get_margins().left.is_predefined() && src_el()->css().get_margins().right.is_predefined()) { int el_width = m_pos.width + m_borders.left + m_borders.right + m_padding.left + m_padding.right + m_margins.left; m_margins.right = parent_width - el_width; if (m_margins.right < 0) m_margins.right = 0; } } return 0; } void litehtml::render_item::apply_relative_shift(const containing_block_context &containing_block_size) { if (src_el()->css().get_position() == element_position_relative) { css_offsets offsets = src_el()->css().get_offsets(); if (!offsets.left.is_predefined()) { m_pos.x += offsets.left.calc_percent(containing_block_size.width); } else if (!offsets.right.is_predefined()) { m_pos.x -= offsets.right.calc_percent(containing_block_size.width); } if (!offsets.top.is_predefined()) { m_pos.y += offsets.top.calc_percent(containing_block_size.height); } else if (!offsets.bottom.is_predefined()) { m_pos.y -= offsets.bottom.calc_percent(containing_block_size.height); } } } std::tuple< std::shared_ptr, std::shared_ptr, std::shared_ptr > litehtml::render_item::split_inlines() { std::tuple< std::shared_ptr, std::shared_ptr, std::shared_ptr > ret; for(const auto& child: m_children) { if(child->src_el()->is_block_box() && child->src_el()->css().get_float() == float_none) { std::get<0>(ret) = clone(); std::get<1>(ret) = child; std::get<2>(ret) = clone(); std::get<1>(ret)->parent(std::get<0>(ret)); std::get<2>(ret)->parent(std::get<0>(ret)); bool found = false; for(const auto& ch: m_children) { if(ch == child) { found = true; continue; } if(!found) { std::get<0>(ret)->add_child(ch); } else { std::get<2>(ret)->add_child(ch); } } break; } if(!child->children().empty()) { auto child_split = child->split_inlines(); if(std::get<0>(child_split)) { std::get<0>(ret) = clone(); std::get<1>(ret) = std::get<1>(child_split); std::get<2>(ret) = clone(); std::get<2>(ret)->parent(std::get<0>(ret)); bool found = false; for(const auto& ch: m_children) { if(ch == child) { found = true; continue; } if(!found) { std::get<0>(ret)->add_child(ch); } else { std::get<2>(ret)->add_child(ch); } } std::get<0>(ret)->add_child(std::get<0>(child_split)); std::get<2>(ret)->add_child(std::get<2>(child_split)); break; } } } return ret; } bool litehtml::render_item::fetch_positioned() { bool ret = false; m_positioned.clear(); litehtml::element_position el_pos; for(auto& el : m_children) { el_pos = el->src_el()->css().get_position(); if (el_pos != element_position_static) { add_positioned(el); } if (!ret && (el_pos == element_position_absolute || el_pos == element_position_fixed)) { ret = true; } if(el->fetch_positioned()) { ret = true; } } return ret; } void litehtml::render_item::render_positioned(render_type rt) { position wnd_position; src_el()->get_document()->container()->get_client_rect(wnd_position); element_position el_position; bool process; for (auto& el : m_positioned) { el_position = el->src_el()->css().get_position(); process = false; if(el->src_el()->css().get_display() != display_none) { if(el_position == element_position_absolute) { if(rt != render_fixed_only) { process = true; } } else if(el_position == element_position_fixed) { if(rt != render_no_fixed) { process = true; } } } if(process) { containing_block_context containing_block_size; 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 + m_padding.height(); containing_block_size.width = m_pos.width + m_padding.width(); } css_length css_left = el->src_el()->css().get_offsets().left; css_length css_right = el->src_el()->css().get_offsets().right; css_length css_top = el->src_el()->css().get_offsets().top; css_length css_bottom = el->src_el()->css().get_offsets().bottom; bool need_render = false; css_length el_width = el->src_el()->css().get_width(); css_length el_height = el->src_el()->css().get_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; }; 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->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; } // 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(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(el_position != element_position_fixed) { el->m_pos.x -= el_static_offset_x; el->m_pos.y -= el_static_offset_y; } if(need_render) { position pos = el->m_pos; el->render(el->left(), el->top(), containing_block_size.new_width(el->width()), nullptr, true); el->m_pos = pos; } if(el_position == element_position_fixed) { position fixed_pos; el->get_redraw_box(fixed_pos); src_el()->get_document()->add_fixed_box(fixed_pos); } } el->render_positioned(); } if(!m_positioned.empty()) { std::stable_sort(m_positioned.begin(), m_positioned.end(), [](const std::shared_ptr& Left, const std::shared_ptr& Right) { return (Left->src_el()->css().get_z_index() < Right->src_el()->css().get_z_index()); }); } } void litehtml::render_item::add_positioned(const std::shared_ptr &el) { if (src_el()->css().get_position() != element_position_static || is_root()) { m_positioned.push_back(el); } else { auto el_parent = parent(); if (el_parent) { el_parent->add_positioned(el); } } } void litehtml::render_item::get_redraw_box(litehtml::position& pos, int x /*= 0*/, int y /*= 0*/) { if(is_visible()) { int p_left = std::min(pos.left(), x + m_pos.left() - m_padding.left - m_borders.left); int p_right = std::max(pos.right(), x + m_pos.right() + m_padding.left + m_borders.left); int p_top = std::min(pos.top(), y + m_pos.top() - m_padding.top - m_borders.top); int p_bottom = std::max(pos.bottom(), y + m_pos.bottom() + m_padding.bottom + m_borders.bottom); pos.x = p_left; pos.y = p_top; pos.width = p_right - p_left; pos.height = p_bottom - p_top; if(src_el()->css().get_overflow() == overflow_visible) { for(auto& el : m_children) { if(el->src_el()->css().get_position() != element_position_fixed) { el->get_redraw_box(pos, x + m_pos.x, y + m_pos.y); } } } } } void litehtml::render_item::calc_document_size( litehtml::size& sz, litehtml::size& content_size, int x /*= 0*/, int y /*= 0*/ ) { if(css().get_display() != display_inline && css().get_display() != display_table_row) { 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()) { 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()) { 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 = 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 ) { if(!is_visible()) return; std::map z_indexes; if(with_positioned) { for(const auto& idx : m_positioned) { z_indexes[idx->src_el()->css().get_z_index()]; } for(const auto& idx : z_indexes) { if(idx.first < 0) { draw_children(hdc, x, y, clip, draw_positioned, idx.first); } } } draw_children(hdc, x, y, clip, draw_block, 0); draw_children(hdc, x, y, clip, draw_floats, 0); draw_children(hdc, x, y, clip, draw_inlines, 0); if(with_positioned) { for(auto& z_index : z_indexes) { if(z_index.first == 0) { draw_children(hdc, x, y, clip, draw_positioned, z_index.first); } } for(auto& z_index : z_indexes) { if(z_index.first > 0) { draw_children(hdc, x, y, clip, draw_positioned, z_index.first); } } } } void litehtml::render_item::draw_children(uint_ptr hdc, int x, int y, const position* clip, draw_flag flag, int zindex) { position pos = m_pos; pos.x += x; pos.y += y; document::ptr doc = src_el()->get_document(); if (src_el()->css().get_overflow() > overflow_visible) { // TODO: Process overflow for inline elements if(src_el()->css().get_display() != display_inline) { position border_box = pos; border_box += m_padding; border_box += m_borders; border_radiuses bdr_radius = src_el()->css().get_borders().radius.calc_percents(border_box.width, border_box.height); bdr_radius -= m_borders; bdr_radius -= m_padding; doc->container()->set_clip(pos, bdr_radius); } } for (const auto& el : m_children) { if (el->is_visible()) { bool process = true; switch (flag) { case draw_positioned: if (el->src_el()->is_positioned() && el->src_el()->css().get_z_index() == zindex) { if (el->src_el()->css().get_position() == element_position_fixed) { position browser_wnd; doc->container()->get_client_rect(browser_wnd); el->src_el()->draw(hdc, browser_wnd.x, browser_wnd.y, clip, el); el->draw_stacking_context(hdc, browser_wnd.x, browser_wnd.y, clip, true); } else { el->src_el()->draw(hdc, pos.x, pos.y, clip, el); el->draw_stacking_context(hdc, pos.x, pos.y, clip, true); } process = false; } break; case draw_block: if (!el->src_el()->is_inline() && el->src_el()->css().get_float() == float_none && !el->src_el()->is_positioned()) { el->src_el()->draw(hdc, pos.x, pos.y, clip, el); } break; case draw_floats: if (el->src_el()->css().get_float() != float_none && !el->src_el()->is_positioned()) { el->src_el()->draw(hdc, pos.x, pos.y, clip, el); el->draw_stacking_context(hdc, pos.x, pos.y, clip, false); process = false; } break; case draw_inlines: if (el->src_el()->is_inline() && el->src_el()->css().get_float() == float_none && !el->src_el()->is_positioned()) { el->src_el()->draw(hdc, pos.x, pos.y, clip, el); if (el->src_el()->css().get_display() == display_inline_block || el->src_el()->css().get_display() == display_inline_flex) { el->draw_stacking_context(hdc, pos.x, pos.y, clip, false); process = false; } } break; default: break; } if (process) { if (flag == draw_positioned) { if (!el->src_el()->is_positioned()) { el->draw_children(hdc, pos.x, pos.y, clip, flag, zindex); } } else { if (el->src_el()->css().get_float() == float_none && el->src_el()->css().get_display() != display_inline_block && !el->src_el()->is_positioned()) { el->draw_children(hdc, pos.x, pos.y, clip, flag, zindex); } } } } } if (src_el()->css().get_overflow() > overflow_visible) { doc->container()->del_clip(); } } std::shared_ptr litehtml::render_item::get_child_by_point(int x, int y, int client_x, int client_y, draw_flag flag, int zindex) { element::ptr ret = nullptr; if(src_el()->css().get_overflow() > overflow_visible) { if(!m_pos.is_point_inside(x, y)) { return ret; } } position el_pos = m_pos; el_pos.x = x - el_pos.x; el_pos.y = y - el_pos.y; for(auto i = m_children.rbegin(); i != m_children.rend() && !ret; std::advance(i, 1)) { auto el = (*i); if(el->is_visible() && el->src_el()->css().get_display() != display_inline_text) { switch(flag) { case draw_positioned: if(el->src_el()->is_positioned() && el->src_el()->css().get_z_index() == zindex) { if(el->src_el()->css().get_position() == element_position_fixed) { ret = el->get_element_by_point(client_x, client_y, client_x, client_y); if(!ret && (*i)->is_point_inside(client_x, client_y)) { ret = (*i)->src_el(); } } else { ret = el->get_element_by_point(el_pos.x, el_pos.y, client_x, client_y); if(!ret && (*i)->is_point_inside(el_pos.x, el_pos.y)) { ret = (*i)->src_el(); } } el = nullptr; } break; case draw_block: if(!el->src_el()->is_inline() && el->src_el()->css().get_float() == float_none && !el->src_el()->is_positioned()) { if(el->is_point_inside(el_pos.x, el_pos.y)) { ret = el->src_el(); } } break; case draw_floats: if(el->src_el()->css().get_float() != float_none && !el->src_el()->is_positioned()) { ret = el->get_element_by_point(el_pos.x, el_pos.y, client_x, client_y); if(!ret && (*i)->is_point_inside(el_pos.x, el_pos.y)) { ret = (*i)->src_el(); } el = nullptr; } break; case draw_inlines: if(el->src_el()->is_inline() && el->src_el()->css().get_float() == float_none && !el->src_el()->is_positioned()) { if(el->src_el()->css().get_display() == display_inline_block || el->src_el()->css().get_display() == display_inline_table || el->src_el()->css().get_display() == display_inline_flex) { ret = el->get_element_by_point(el_pos.x, el_pos.y, client_x, client_y); el = nullptr; } if(!ret && (*i)->is_point_inside(el_pos.x, el_pos.y)) { ret = (*i)->src_el(); } } break; default: break; } if(el && !el->src_el()->is_positioned()) { if(flag == draw_positioned) { element::ptr child = el->get_child_by_point(el_pos.x, el_pos.y, client_x, client_y, flag, zindex); if(child) { ret = child; } } else { if( el->src_el()->css().get_float() == float_none && el->src_el()->css().get_display() != display_inline_block && el->src_el()->css().get_display() != display_inline_flex) { element::ptr child = el->get_child_by_point(el_pos.x, el_pos.y, client_x, client_y, flag, zindex); if(child) { ret = child; } } } } } } return ret; } std::shared_ptr litehtml::render_item::get_element_by_point(int x, int y, int client_x, int client_y) { if(!is_visible()) return nullptr; element::ptr ret; std::map z_indexes; for(const auto& i : m_positioned) { z_indexes[i->src_el()->css().get_z_index()]; } for(auto iter = z_indexes.rbegin(); iter != z_indexes.rend(); iter++) { if(iter->first > 0) { ret = get_child_by_point(x, y, client_x, client_y, draw_positioned, iter->first); if(ret) return ret; } } for(const auto& z_index : z_indexes) { if(z_index.first == 0) { ret = get_child_by_point(x, y, client_x, client_y, draw_positioned, z_index.first); if(ret) return ret; } } ret = get_child_by_point(x, y, client_x, client_y, draw_inlines, 0); if(ret) return ret; ret = get_child_by_point(x, y, client_x, client_y, draw_floats, 0); if(ret) return ret; ret = get_child_by_point(x, y, client_x, client_y, draw_block, 0); if(ret) return ret; for(auto iter = z_indexes.rbegin(); iter != z_indexes.rend(); iter++) { if(iter->first < 0) { ret = get_child_by_point(x, y, client_x, client_y, draw_positioned, iter->first); if(ret) return ret; } } if(src_el()->css().get_position() == element_position_fixed) { if(is_point_inside(client_x, client_y)) { ret = src_el(); } } else { if(is_point_inside(x, y)) { ret = src_el(); } } return ret; } bool litehtml::render_item::is_point_inside( int x, int y ) { if(src_el()->css().get_display() != display_inline && src_el()->css().get_display() != display_table_row) { position pos = m_pos; pos += m_padding; pos += m_borders; if(pos.is_point_inside(x, y)) { return true; } else { return false; } } else { position::vector boxes; get_inline_boxes(boxes); for(auto & box : boxes) { if(box.is_point_inside(x, y)) { return true; } } } return false; } void litehtml::render_item::get_rendering_boxes( position::vector& redraw_boxes) { if(src_el()->css().get_display() == display_inline || src_el()->css().get_display() == display_table_row) { get_inline_boxes(redraw_boxes); } else { position pos = m_pos; pos += m_padding; pos += m_borders; redraw_boxes.push_back(pos); } if(src_el()->css().get_position() != element_position_fixed) { auto cur_el = parent(); while(cur_el) { for(auto& box : redraw_boxes) { box.x += cur_el->m_pos.x; box.y += cur_el->m_pos.y; } cur_el = cur_el->parent(); } } } void litehtml::render_item::dump(litehtml::dumper& cout) { cout.begin_node(src_el()->dump_get_name() + "{" + string(typeid(*this).name()) + "}"); auto attrs = src_el()->dump_get_attrs(); if(!attrs.empty()) { cout.begin_attrs_group("attributes"); for (const auto &attr: attrs) { cout.add_attr(std::get<0>(attr), std::get<1>(attr)); } cout.end_attrs_group(); } if(!m_children.empty()) { cout.begin_attrs_group("children"); for (const auto &el: m_children) { el->dump(cout); } cout.end_attrs_group(); } cout.end_node(); } litehtml::position litehtml::render_item::get_placement() const { litehtml::position pos = m_pos; auto cur_el = parent(); while(cur_el) { pos.x += cur_el->m_pos.x; pos.y += cur_el->m_pos.y; cur_el = cur_el->parent(); } return pos; } std::shared_ptr litehtml::render_item::init() { src_el()->add_render(shared_from_this()); for(auto& el : children()) { el = el->init(); } return shared_from_this(); } void litehtml::render_item::calc_cb_length(const css_length& len, int percent_base, containing_block_context::typed_int& out_value) const { if (!len.is_predefined()) { if(len.units() == litehtml::css_units_percentage) { out_value.value = len.calc_percent(percent_base); out_value.type = litehtml::containing_block_context::cbc_value_type_percentage; } else { 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; } } } litehtml::containing_block_context litehtml::render_item::calculate_containing_block_context(const containing_block_context& cb_context) { containing_block_context ret; ret.context_idx = cb_context.context_idx + 1; ret.width.value = ret.max_width.value = cb_context.width.value - content_offset_width(); if(src_el()->css().get_position() != element_position_absolute && src_el()->css().get_position() != element_position_fixed) { ret.height.value = cb_context.height.value - content_offset_height(); } // Calculate width if css property is not auto // We have to use aut value for display_table_cell also. if (src_el()->css().get_display() != display_table_cell) { auto par = parent(); if(cb_context.size_mode & containing_block_context::size_mode_exact_width) { ret.width.value = cb_context.width; ret.width.type = containing_block_context::cbc_value_type_absolute; } else { auto *width = &css().get_width(); if(par && (par->css().get_display() == display_flex || par->css().get_display() == display_inline_flex)) { if(!css().get_flex_basis().is_predefined() && css().get_flex_basis().val() >= 0) { if(par->css().get_flex_direction() == flex_direction_row || par->css().get_flex_direction() == flex_direction_row_reverse) { ret.width.type = containing_block_context::cbc_value_type_auto; ret.width.value = 0; width = nullptr; } } } if(width) { calc_cb_length(*width, cb_context.width, ret.width); } } if(cb_context.size_mode & containing_block_context::size_mode_exact_height) { ret.height.value = cb_context.height; ret.height.type = containing_block_context::cbc_value_type_absolute; } else { auto *height = &css().get_height(); if(par && (par->css().get_display() == display_flex || par->css().get_display() == display_inline_flex)) { if(!css().get_flex_basis().is_predefined() && css().get_flex_basis().val() >= 0) { if(par->css().get_flex_direction() == flex_direction_column || par->css().get_flex_direction() == flex_direction_column_reverse) { ret.height.type = containing_block_context::cbc_value_type_auto; ret.height.value = 0; height = nullptr; } } } if(height) { calc_cb_length(*height, cb_context.height, ret.height); } } if (ret.width.type != containing_block_context::cbc_value_type_auto && (src_el()->css().get_display() == display_table || src_el()->is_root())) { ret.width.value -= content_offset_width(); } if (ret.height.type != containing_block_context::cbc_value_type_auto && (src_el()->css().get_display() == display_table || src_el()->is_root())) { ret.height.value -= content_offset_height(); } } ret.render_width = ret.width; calc_cb_length(src_el()->css().get_min_width(), cb_context.width, ret.min_width); calc_cb_length(src_el()->css().get_max_width(), cb_context.width, ret.max_width); calc_cb_length(src_el()->css().get_min_height(), cb_context.height, ret.min_height); calc_cb_length(src_el()->css().get_max_height(), cb_context.height, ret.max_height); // Fix box sizing if(ret.width.type != containing_block_context::cbc_value_type_auto) { ret.render_width = ret.width - box_sizing_width(); } if(ret.min_width.type != containing_block_context::cbc_value_type_none) { ret.min_width.value -= box_sizing_width(); } if(ret.max_width.type != containing_block_context::cbc_value_type_none) { ret.max_width.value -= box_sizing_width(); } if(ret.min_height.type != containing_block_context::cbc_value_type_none) { ret.min_height.value -= box_sizing_height(); } if(ret.max_height.type != containing_block_context::cbc_value_type_none) { ret.max_height.value -= box_sizing_height(); } return ret; } std::tuple litehtml::render_item::element_static_offset(const std::shared_ptr& 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}; }