#include "html.h" #include "flex_line.h" #include "flex_item.h" #include "render_item.h" void litehtml::flex_line::distribute_free_space(int container_main_size) { // Determine the used flex factor. Sum the outer hypothetical main sizes of all items on the line. // If the sum is less than the flex container’s inner main size, use the flex grow factor for the // rest of this algorithm; otherwise, use the flex shrink factor. int initial_free_space = container_main_size - base_size; bool grow; int total_flex_factor; if(initial_free_space < 0) { grow = false; total_flex_factor = total_shrink; // Flex values between 0 and 1 have a somewhat special behavior: when the sum of the flex values on the line // is less than 1, they will take up less than 100% of the free space. // https://www.w3.org/TR/css-flexbox-1/#valdef-flex-flex-grow if(total_flex_factor < 1000) { for(auto &item : items) { item->main_size += initial_free_space * item->shrink / 1000; } return; } } else { grow = true; total_flex_factor = total_grow; // Flex values between 0 and 1 have a somewhat special behavior: when the sum of the flex values on the line // is less than 1, they will take up less than 100% of the free space. // https://www.w3.org/TR/css-flexbox-1/#valdef-flex-flex-grow if(total_flex_factor < 1000) { for(auto &item : items) { item->main_size += initial_free_space * item->grow / 1000; } return; } } if(total_flex_factor > 0) { bool processed = true; 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) { 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 { remaining_free_space -= item->main_size; } } // Check for flexible items. If all the flex items on the line are frozen, free space has // been distributed; exit this loop. if (!total_not_frozen) break; remaining_free_space = abs(remaining_free_space); // c. Distribute free space proportional to the flex factors. // If the remaining free space is zero // Do nothing. if (!remaining_free_space) { processed = false; } else { int total_clamped = 0; for (auto &item: items) { if (!item->frozen) { if(!grow) { // If using the flex shrink factor // For every unfrozen item on the line, multiply its flex shrink factor by its // inner flex base size, and note this as its scaled flex shrink factor. Find // the ratio of the item’s scaled flex shrink factor to the sum of the scaled // flex shrink factors of all unfrozen items on the line. Set the item’s target // main size to its flex base size minus a fraction of the absolute value of the // remaining free space proportional to the ratio. int scaled_flex_shrink_factor = item->base_size * item->shrink; item->main_size = (int) ((float) item->base_size - (float) remaining_free_space * (float) scaled_flex_shrink_factor / (float) sum_scaled_flex_shrink_factor); // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its used // min and max main sizes and floor its content-box size at zero. If the item’s target // main size was made smaller by this, it’s a max violation. If the item’s target main // size was made larger by this, it’s a min violation. if (item->main_size <= item->min_size) { total_clamped++; item->main_size = item->min_size; item->frozen = true; } if(!item->max_size.is_default() && item->main_size >= item->max_size) { total_clamped++; item->main_size = item->max_size; item->frozen = true; } } else { // If using the flex grow factor // Find the ratio of the item’s flex grow factor to the sum of the flex grow // factors of all unfrozen items on the line. Set the item’s target main size to // its flex base size plus a fraction of the remaining free space proportional // to the ratio. item->main_size = (int) ((float) item->base_size + (float) remaining_free_space * (float) item->grow / (float) total_flex_factor); // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its used // min and max main sizes and floor its content-box size at zero. If the item’s target // main size was made smaller by this, it’s a max violation. If the item’s target main // size was made larger by this, it’s a min violation. if (item->main_size >= container_main_size) { total_clamped++; item->main_size = container_main_size; item->frozen = true; } if(!item->max_size.is_default() && item->main_size >= item->max_size) { total_clamped++; item->main_size = item->max_size; item->frozen = true; } } } } if (total_clamped == 0) processed = false; } } // Distribute remaining after algorithm space int sum_main_size = 0; for(auto &item : items) { sum_main_size += item->main_size; } int free_space = container_main_size - sum_main_size; if(free_space > 0) { for(auto &item : items) { if(free_space == 0) break; item->main_size++; free_space--; } } } } bool litehtml::flex_line::distribute_main_auto_margins(int free_main_size) { if(free_main_size > 0 && (num_auto_margin_main_start || num_auto_margin_main_end)) { int add = (int) (free_main_size / (items.size() * 2)); for (auto &item: items) { if(!item->auto_margin_main_start.is_default()) { item->auto_margin_main_start = add; item->main_size += add; main_size += add; free_main_size -= add; } if(!item->auto_margin_main_end.is_default()) { item->auto_margin_main_end = add; item->main_size += add; main_size += add; free_main_size -= add; } } while (free_main_size > 0) { for (auto &item: items) { if(!item->auto_margin_main_start.is_default()) { item->auto_margin_main_start = item->auto_margin_main_start + 1; free_main_size--; if(!free_main_size) break; } if(!item->auto_margin_main_end.is_default()) { item->auto_margin_main_end = item->auto_margin_main_end + 1; free_main_size--; if(!free_main_size) break; } } } return true; } return false; } void litehtml::flex_line::init(int container_main_size, bool fit_container, bool is_row_direction, const litehtml::containing_block_context &self_size, litehtml::formatting_context *fmt_ctx) { cross_size = 0; main_size = 0; first_baseline.set(0, baseline::baseline_type_none); last_baseline.set(0, baseline::baseline_type_none); if(!fit_container) { distribute_free_space(container_main_size); } if(is_row_direction) { def_value first_baseline_top = 0; def_value first_baseline_bottom = 0; def_value last_baseline_top = 0; def_value last_baseline_bottom = 0; int non_baseline_height = 0; // Calculate maximum cross size def_value max_cross_size(0); if(self_size.height.type != containing_block_context::cbc_value_type_auto) { max_cross_size = self_size.height; } if(self_size.max_height.type != containing_block_context::cbc_value_type_none) { if(max_cross_size.is_default()) { max_cross_size = self_size.max_height; } else { max_cross_size = std::max((int) max_cross_size, (int) self_size.max_height); } } /// Render items into new size /// Find line cross_size /// Find line first/last baseline for (auto &item: items) { item->el->render(0, 0, self_size.new_width(item->main_size - item->el->render_offset_width(), containing_block_context::size_mode_exact_width), fmt_ctx, false); if((item->align & 0xFF) == flex_align_items_baseline) { if(item->align & flex_align_items_last) { last_baseline.type(reverse_cross ? baseline::baseline_type_top : baseline::baseline_type_bottom); int top = -item->el->get_last_baseline(); int bottom = top + item->el->height(); if(last_baseline_top.is_default()) last_baseline_top = top; else last_baseline_top = std::min((int) last_baseline_top, top); if(last_baseline_bottom.is_default()) last_baseline_bottom = bottom; else last_baseline_bottom = std::max((int)last_baseline_bottom, bottom); } else { first_baseline.type(reverse_cross ? baseline::baseline_type_bottom : baseline::baseline_type_top); int top = -item->el->get_first_baseline(); int bottom = top + item->el->height(); if(first_baseline_top.is_default()) first_baseline_top = top; else first_baseline_top = std::min((int) first_baseline_top, top); if(first_baseline_bottom.is_default()) first_baseline_bottom = bottom; else first_baseline_bottom = std::max((int) first_baseline_bottom, bottom); } } else { non_baseline_height = std::max(non_baseline_height, item->el->height()); } main_size += item->el->width(); } cross_size = std::max(first_baseline_bottom - first_baseline_top,last_baseline_bottom - last_baseline_top); cross_size = std::max(cross_size, non_baseline_height); if(!max_cross_size.is_default() && cross_size > max_cross_size) { cross_size = max_cross_size; } first_baseline.calc(first_baseline_top, first_baseline_bottom); last_baseline.calc(last_baseline_top, last_baseline_bottom); } else { // Calculate maximum cross size def_value max_cross_size(0); if(self_size.width.type != containing_block_context::cbc_value_type_auto) { max_cross_size = self_size.width; } if(self_size.max_width.type != containing_block_context::cbc_value_type_none) { if(max_cross_size.is_default()) { max_cross_size = self_size.max_width; } else { max_cross_size = std::max((int) max_cross_size, (int) self_size.max_width); } } for (auto &item: items) { int el_ret_width = item->el->render(0, 0, self_size, fmt_ctx, false); item->el->render(0, 0, self_size.new_width_height(el_ret_width - item->el->content_offset_width(), item->main_size - item->el->content_offset_height(), containing_block_context::size_mode_exact_width | containing_block_context::size_mode_exact_height), fmt_ctx, false); main_size += item->el->height(); cross_size = std::max(cross_size, item->el->width()); } if(!max_cross_size.is_default() && cross_size > max_cross_size) { cross_size = max_cross_size; } } } int litehtml::flex_line::calculate_items_position(int container_main_size, flex_justify_content justify_content, bool is_row_direction, const containing_block_context &self_size, formatting_context *fmt_ctx) { /// Distribute main axis free space for auto-margins int free_main_size = container_main_size - main_size; distribute_main_auto_margins(free_main_size); free_main_size = container_main_size - main_size; /// Fix justify-content property switch (justify_content) { case flex_justify_content_left: case flex_justify_content_right: if(!is_row_direction) { justify_content = flex_justify_content_start; } break; case flex_justify_content_space_between: // If the leftover free-space is negative or there is only a single flex item on the line, this // value is identical to flex-start. if(items.size() == 1 || free_main_size < 0) justify_content = flex_justify_content_flex_start; break; case flex_justify_content_space_around: case flex_justify_content_space_evenly: // If the leftover free-space is negative or there is only a single flex item on the line, this // value is identical to center if(items.size() == 1 || free_main_size < 0) justify_content = flex_justify_content_center; break; default: break; } /// Distribute free main size using justify-content property int main_pos = 0; int add_before_item = 0; int add_after_item = 0; int item_remainder = 0; /// find initial main position and spaces between items switch (justify_content) { case flex_justify_content_right: main_pos = free_main_size; break; case flex_justify_content_left: case flex_justify_content_start: main_pos = 0; break; case flex_justify_content_end: main_pos = free_main_size; break; case flex_justify_content_flex_end: if(!reverse_main) { main_pos = free_main_size; } break; case flex_justify_content_center: main_pos = free_main_size / 2; break; case flex_justify_content_space_between: add_after_item = free_main_size / ((int) items.size() - 1); item_remainder = free_main_size - (add_after_item * ((int) items.size() - 1)); break; case flex_justify_content_space_around: add_after_item = add_before_item = free_main_size / ((int) items.size() * 2); item_remainder = free_main_size - (add_after_item * (int) items.size() * 2); break; case flex_justify_content_space_evenly: add_before_item = free_main_size / ((int) items.size() + 1); item_remainder = free_main_size - add_before_item * ((int) items.size() + 1); break; default: if(reverse_main) { main_pos = free_main_size; } break; } /// Place all items in main and cross positions int height = 0; for(auto &item : items) { main_pos += add_before_item; if(add_before_item > 0 && item_remainder > 0) { main_pos++; item_remainder--; } item->place(*this, main_pos, self_size, fmt_ctx); main_pos += item->get_el_main_size() + add_after_item; if(add_after_item > 0 && item_remainder > 0) { main_pos++; item_remainder--; } height = std::max(height, item->el->bottom()); } return height; }