#include "html.h" #include "types.h" #include "render_flex.h" int litehtml::render_item_flex::_render_content(int x, int y, bool /*second_pass*/, const containing_block_context &self_size, formatting_context* fmt_ctx) { bool is_row_direction = true; bool reverse = false; int container_main_size = self_size.render_width; switch (css().get_flex_direction()) { case flex_direction_column: is_row_direction = false; reverse = false; break; case flex_direction_column_reverse: is_row_direction = false; reverse = true; break; case flex_direction_row: is_row_direction = true; reverse = false; break; case flex_direction_row_reverse: is_row_direction = true; reverse = true; break; } bool single_line = css().get_flex_wrap() == flex_wrap_nowrap; bool fit_container = false; if(!is_row_direction) { if(self_size.height.type != containing_block_context::cbc_value_type_auto) { container_main_size = self_size.height; if (css().get_box_sizing() == box_sizing_border_box) { container_main_size -= box_sizing_height(); } } else { // Direction columns, height is auto - always in single line container_main_size = 0; single_line = true; fit_container = true; } if(self_size.min_height.type != containing_block_context::cbc_value_type_auto && self_size.min_height > container_main_size) { container_main_size = self_size.min_height; } if(self_size.max_height.type != containing_block_context::cbc_value_type_auto && self_size.max_height > container_main_size) { container_main_size = self_size.max_height; single_line = false; } } ///////////////////////////////////////////////////////////////// /// Split flex items to lines ///////////////////////////////////////////////////////////////// m_lines = get_lines(self_size, fmt_ctx, is_row_direction, container_main_size, single_line); int sum_cross_size = 0; int sum_main_size = 0; int ret_width = 0; ///////////////////////////////////////////////////////////////// /// Resolving Flexible Lengths /// REF: https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths ///////////////////////////////////////////////////////////////// for(auto& ln : m_lines) { if(is_row_direction) { ret_width += ln.base_size; } ln.init(container_main_size, fit_container, is_row_direction, self_size, fmt_ctx); sum_cross_size += ln.cross_size; sum_main_size = std::max(sum_main_size, ln.main_size); if(reverse) { ln.items.reverse(); } } int free_cross_size = 0; bool is_wrap_reverse = css().get_flex_wrap() == flex_wrap_wrap_reverse; if(container_main_size == 0) { container_main_size = sum_main_size; } ///////////////////////////////////////////////////////////////// /// Calculate free cross size ///////////////////////////////////////////////////////////////// if (is_row_direction) { if (self_size.height.type != containing_block_context::cbc_value_type_auto) { int height = self_size.height; if (src_el()->css().get_box_sizing() == box_sizing_border_box) { height -= box_sizing_height(); } free_cross_size = height - sum_cross_size; } } else { free_cross_size = self_size.render_width - sum_cross_size; ret_width = sum_cross_size; } ///////////////////////////////////////////////////////////////// /// Fix align-content property ///////////////////////////////////////////////////////////////// flex_align_content align_content = css().get_flex_align_content(); if(align_content == flex_align_content_space_between) { // If the leftover free-space is negative or there is only a single flex line in the flex // container, this value is identical to flex-start. if (m_lines.size() == 1 || free_cross_size < 0) align_content = flex_align_content_flex_start; } if(align_content == flex_align_content_space_around) { // If the leftover free-space is negative or there is only a single flex line in the flex // container, this value is identical to flex-start. if (m_lines.size() == 1 || free_cross_size < 0) align_content = flex_align_content_center; } ///////////////////////////////////////////////////////////////// /// Distribute free cross size for align-content: stretch ///////////////////////////////////////////////////////////////// if(css().get_flex_align_content() == flex_align_content_stretch && free_cross_size > 0) { int add = (int)((double) free_cross_size / (double) m_lines.size()); if(add > 0) { for (auto &ln: m_lines) { ln.cross_size += add; free_cross_size -= add; } } if(!m_lines.empty()) { while (free_cross_size > 0) { for (auto &ln: m_lines) { ln.cross_size++; free_cross_size--; } } } } /// Reverse lines for flex-wrap: wrap-reverse if(css().get_flex_wrap() == flex_wrap_wrap_reverse) { m_lines.reverse(); } ///////////////////////////////////////////////////////////////// /// Align flex lines ///////////////////////////////////////////////////////////////// int line_pos = 0; int add_before_line = 0; int add_after_line = 0; switch (align_content) { case flex_align_content_flex_start: if(is_wrap_reverse) { line_pos = free_cross_size; } break; case flex_align_content_flex_end: if(!is_wrap_reverse) { line_pos = free_cross_size; } break; case flex_align_content_end: line_pos = free_cross_size; break; case flex_align_content_center: line_pos = free_cross_size / 2; break; case flex_align_content_space_between: add_after_line = free_cross_size / ((int) m_lines.size() - 1); break; case flex_align_content_space_around: add_before_line = add_after_line = free_cross_size / ((int) m_lines.size() * 2); break; default: if(is_wrap_reverse) { line_pos = free_cross_size; } break; } for(auto &ln : m_lines) { line_pos += add_before_line; ln.cross_start = line_pos; line_pos += ln.cross_size + add_after_line; } /// Fix justify-content property flex_justify_content justify_content = css().get_flex_justify_content(); if((justify_content == flex_justify_content_right || justify_content == flex_justify_content_left) && !is_row_direction) { justify_content = flex_justify_content_start; } ///////////////////////////////////////////////////////////////// /// Align flex items in flex lines ///////////////////////////////////////////////////////////////// for(auto &ln : m_lines) { int height = ln.calculate_items_position(container_main_size, justify_content, is_row_direction, self_size, fmt_ctx); m_pos.height = std::max(m_pos.height, height); } // calculate the final position m_pos.move_to(x, y); m_pos.x += content_offset_left(); m_pos.y += content_offset_top(); return ret_width; } std::list litehtml::render_item_flex::get_lines(const litehtml::containing_block_context &self_size, litehtml::formatting_context *fmt_ctx, bool is_row_direction, int container_main_size, bool single_line) { bool reverse_main; bool reverse_cross = css().get_flex_wrap() == flex_wrap_wrap_reverse; if(is_row_direction) { reverse_main = css().get_flex_direction() == flex_direction_row_reverse; } else { reverse_main = css().get_flex_direction() == flex_direction_column_reverse; } std::list lines; flex_line line(reverse_main, reverse_cross); std::list> items; int src_order = 0; bool sort_required = false; def_value prev_order(0); for( auto& el : m_children) { std::shared_ptr item = nullptr; if(is_row_direction) { item = std::make_shared(el); } else { item = std::make_shared(el); } item->init(self_size, fmt_ctx, css().get_flex_align_items()); item->src_order = src_order++; if(prev_order.is_default()) { prev_order = item->order; } else if(!sort_required && item->order != prev_order) { sort_required = true; } items.emplace_back(item); } if(sort_required) { items.sort([](const std::shared_ptr& item1, const std::shared_ptr& item2) { if(item1->order < item2->order) return true; if(item1->order == item2->order) { return item1->src_order < item2->src_order; } return false; }); } // Add flex items to lines for(auto& item : items) { if(!line.items.empty() && !single_line && line.base_size + item->base_size > container_main_size) { lines.emplace_back(line); line = flex_line(reverse_main, reverse_cross); } line.base_size += item->base_size; line.total_grow += item->grow; line.total_shrink += item->shrink; if(!item->auto_margin_main_start.is_default()) line.num_auto_margin_main_start++; if(!item->auto_margin_main_end.is_default()) line.num_auto_margin_main_end++; line.items.push_back(item); } // Add the last line to the lines list if(!line.items.empty()) { lines.emplace_back(line); } return lines; } std::shared_ptr litehtml::render_item_flex::init() { auto doc = src_el()->get_document(); decltype(m_children) new_children; decltype(m_children) inlines; auto convert_inlines = [&]() { if(!inlines.empty()) { // Find last not space auto not_space = std::find_if(inlines.rbegin(), inlines.rend(), [&](const std::shared_ptr& el) { return !el->src_el()->is_space(); }); if(not_space != inlines.rend()) { // Erase all spaces at the end inlines.erase((not_space.base()), inlines.end()); } auto anon_el = std::make_shared(src_el()); auto anon_ri = std::make_shared(anon_el); for(const auto& inl : inlines) { anon_ri->add_child(inl); } anon_ri->parent(shared_from_this()); new_children.push_back(anon_ri->init()); inlines.clear(); } }; for (const auto& el : m_children) { if(el->src_el()->css().get_display() == display_inline_text) { if(!inlines.empty()) { inlines.push_back(el); } else { if (!el->src_el()->is_white_space()) { inlines.push_back(el); } } } else { convert_inlines(); if(el->src_el()->is_block_box()) { // Add block boxes as is el->parent(shared_from_this()); new_children.push_back(el->init()); } else { // Wrap inlines with anonymous block box auto anon_el = std::make_shared(el->src_el()); auto anon_ri = std::make_shared(anon_el); anon_ri->add_child(el->init()); anon_ri->parent(shared_from_this()); new_children.push_back(anon_ri->init()); } } } convert_inlines(); children() = new_children; return shared_from_this(); } int litehtml::render_item_flex::get_first_baseline() { if(css().get_flex_direction() == flex_direction_row || css().get_flex_direction() == flex_direction_row_reverse) { if(!m_lines.empty()) { const auto &first_line = m_lines.front(); if(first_line.first_baseline.type() != baseline::baseline_type_none) { return first_line.cross_start + first_line.first_baseline.get_offset_from_top(first_line.cross_size) + content_offset_top(); } if(first_line.last_baseline.type() != baseline::baseline_type_none) { return first_line.cross_start + first_line.last_baseline.get_offset_from_top(first_line.cross_size) + content_offset_top(); } } } if(!m_lines.empty()) { if(!m_lines.front().items.empty()) { return m_lines.front().items.front()->el->get_first_baseline() + content_offset_top(); } } return height(); } int litehtml::render_item_flex::get_last_baseline() { if(css().get_flex_direction() == flex_direction_row || css().get_flex_direction() == flex_direction_row_reverse) { if(!m_lines.empty()) { const auto &first_line = m_lines.front(); if(first_line.last_baseline.type() != baseline::baseline_type_none) { return first_line.cross_start + first_line.last_baseline.get_offset_from_top(first_line.cross_size) + content_offset_top(); } if(first_line.first_baseline.type() != baseline::baseline_type_none) { return first_line.cross_start + first_line.first_baseline.get_offset_from_top(first_line.cross_size) + content_offset_top(); } } } if(!m_lines.empty()) { if(!m_lines.front().items.empty()) { return m_lines.front().items.front()->el->get_last_baseline() + content_offset_top(); } } return height(); }