diff --git a/include/litehtml/flex_item.h b/include/litehtml/flex_item.h index 48582988..192b421a 100644 --- a/include/litehtml/flex_item.h +++ b/include/litehtml/flex_item.h @@ -8,6 +8,14 @@ namespace litehtml { class flex_line; + enum flex_clamp_state + { + flex_clamp_state_unclamped, + flex_clamp_state_inflexible, + flex_clamp_state_min_violation, + flex_clamp_state_max_violation + }; + /** * Base class for flex item */ @@ -15,20 +23,28 @@ namespace litehtml { public: std::shared_ptr el; + + // All sizes should be interpreted as outer/margin-box sizes. pixel_t base_size; pixel_t min_size; def_value max_size; - pixel_t main_size; + pixel_t main_size; // Holds the outer hypothetical main size before distribute_free_space, and the used outer main size after. + int grow; int shrink; pixel_t scaled_flex_shrink_factor; + bool frozen; + flex_clamp_state clamp_state; + int order; int src_order; + def_value auto_margin_main_start; def_value auto_margin_main_end; bool auto_margin_cross_start; bool auto_margin_cross_end; + flex_align_items align; explicit flex_item(std::shared_ptr &_el) : @@ -41,6 +57,7 @@ namespace litehtml shrink(0), scaled_flex_shrink_factor(0), frozen(false), + clamp_state(flex_clamp_state_unclamped), order(0), src_order(0), auto_margin_main_start(0), diff --git a/include/litehtml/flex_line.h b/include/litehtml/flex_line.h index 06cf1bf2..a5057aed 100644 --- a/include/litehtml/flex_line.h +++ b/include/litehtml/flex_line.h @@ -12,11 +12,9 @@ namespace litehtml public: std::list> items; pixel_t cross_start; // for row direction: top. for column direction: left - pixel_t main_size; // sum of all items main size + pixel_t main_size; // sum of all items main size, initially the sum of hypothetical main sizes pixel_t cross_size; // sum of all items cross size pixel_t base_size; - int total_grow; - int total_shrink; int num_auto_margin_main_start; // number of items with auto margin left/top int num_auto_margin_main_end; // number of items with auto margin right/bottom baseline first_baseline; @@ -29,8 +27,6 @@ namespace litehtml main_size(0), cross_size(0), base_size(0), - total_grow(0), - total_shrink(0), num_auto_margin_main_start(0), num_auto_margin_main_end(0), first_baseline(), @@ -50,6 +46,9 @@ namespace litehtml formatting_context *fmt_ctx); protected: void distribute_free_space(pixel_t container_main_size); + void distribute_free_space_grow(pixel_t container_main_size); + void distribute_free_space_shrink(pixel_t container_main_size); + bool fix_min_max_violations(); }; } diff --git a/src/flex_item.cpp b/src/flex_item.cpp index 8281b534..32be8475 100644 --- a/src/flex_item.cpp +++ b/src/flex_item.cpp @@ -28,8 +28,18 @@ void litehtml::flex_item::init(const litehtml::containing_block_context &self_si { align = el->css().get_flex_align_self(); } - main_size = base_size; - scaled_flex_shrink_factor = base_size * shrink; + + if (base_size < min_size) + { + main_size = min_size; + } else if (!max_size.is_default() && base_size > max_size) + { + main_size = max_size; + } else + { + main_size = base_size; + } + frozen = false; } @@ -137,12 +147,12 @@ void litehtml::flex_item_row_direction::direction_specific_init(const litehtml:: } else { min_size = el->css().get_min_width().calc_percent(self_size.render_width) + - el->content_offset_width(); + el->render_offset_width(); } if (!el->css().get_max_width().is_predefined()) { max_size = el->css().get_max_width().calc_percent(self_size.render_width) + - el->content_offset_width(); + el->render_offset_width(); } bool flex_basis_predefined = el->css().get_flex_basis().is_predefined(); int predef = flex_basis_auto; @@ -199,9 +209,10 @@ void litehtml::flex_item_row_direction::direction_specific_init(const litehtml:: } else { base_size = el->css().get_flex_basis().calc_percent(self_size.render_width) + - el->content_offset_width(); - base_size = std::max(base_size, min_size); + el->render_offset_width(); } + + scaled_flex_shrink_factor = (base_size - el->render_offset_width()) * shrink; } void litehtml::flex_item_row_direction::apply_main_auto_margins() @@ -320,12 +331,12 @@ void litehtml::flex_item_column_direction::direction_specific_init(const litehtm } else { min_size = el->css().get_min_height().calc_percent(self_size.height) + - el->content_offset_height(); + el->render_offset_height(); } if (!el->css().get_max_height().is_predefined()) { max_size = el->css().get_max_height().calc_percent(self_size.height) + - el->content_offset_width(); + el->render_offset_height(); } bool flex_basis_predefined = el->css().get_flex_basis().is_predefined(); @@ -351,7 +362,7 @@ void litehtml::flex_item_column_direction::direction_specific_init(const litehtm { case flex_basis_auto: base_size = el->css().get_height().calc_percent(self_size.height) + - el->content_offset_height(); + el->render_offset_height(); break; case flex_basis_max_content: case flex_basis_fit_content: @@ -371,17 +382,18 @@ void litehtml::flex_item_column_direction::direction_specific_init(const litehtm if(self_size.height.type == containing_block_context::cbc_value_type_absolute) { base_size = el->css().get_flex_basis().calc_percent(self_size.height) + - el->content_offset_height(); + el->render_offset_height(); } else { base_size = 0; } } else { - base_size = (pixel_t) el->css().get_flex_basis().val() + el->content_offset_height(); + base_size = (pixel_t) el->css().get_flex_basis().val() + el->render_offset_height(); } - base_size = std::max(base_size, min_size); } + + scaled_flex_shrink_factor = (base_size - el->render_offset_height()) * shrink; } void litehtml::flex_item_column_direction::apply_main_auto_margins() diff --git a/src/flex_line.cpp b/src/flex_line.cpp index 4674623d..12b7866d 100644 --- a/src/flex_line.cpp +++ b/src/flex_line.cpp @@ -4,161 +4,266 @@ void litehtml::flex_line::distribute_free_space(pixel_t container_main_size) { - // Determine the used flex factor. Sum the outer hypothetical main sizes of all items on the line. + // 1 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. - pixel_t initial_free_space = container_main_size - base_size; - bool grow; - int total_flex_factor; - if(initial_free_space < 0) + + if (main_size < container_main_size) + { + distribute_free_space_grow(container_main_size); + } else + { + distribute_free_space_shrink(container_main_size); + } +} + +void litehtml::flex_line::distribute_free_space_grow(pixel_t container_main_size) +{ + pixel_t initial_free_space = container_main_size; + + bool all_inflexible = true; + + for (auto& item : items) { - 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) + // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size + // any item that has a flex factor of zero + // if using the flex grow factor: any item that has a flex base size greater than its hypothetical main size + + // 3. Calculate initial free space. Sum the outer sizes of all items on the line, and subtract this + // from the flex container’s inner main size. For frozen items, use their outer target main size; for + // other items, use their outer flex base size. + + if (item->grow == 0 || item->base_size > item->main_size) + { + item->frozen = true; + item->clamp_state = flex_clamp_state_inflexible; + initial_free_space -= item->main_size; + } else { - for(auto &item : items) + initial_free_space -= item->base_size; + all_inflexible = false; + } + } + + // 4. Loop: + + // 4.a Check for flexible items. If all the flex items on the line are frozen, free space has been + // distributed; exit this loop. + + if (all_inflexible) return; + + while (true) + { + // 4.b Calculate the remaining free space as for initial free space, above. If the sum of the + // unfrozen flex items’ flex factors is less than one, multiply the initial free space by this sum. + // If the magnitude of this value is less than the magnitude of the remaining free space, use + // this as the remaining free space. + + int sum_flex_grow_factor = 0; + pixel_t remaining_free_space = container_main_size; + + for (auto& item : items) + { + if (item->frozen) + { + remaining_free_space -= item->main_size; + } else { - item->main_size += initial_free_space * item->shrink / 1000; + remaining_free_space -= item->base_size; + sum_flex_grow_factor += item->grow; } - 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) + + if (sum_flex_grow_factor < 1000) { - for(auto &item : items) + pixel_t adjusted_free_space = initial_free_space * (pixel_t) sum_flex_grow_factor / (pixel_t) 1000; + if (adjusted_free_space < remaining_free_space) { - item->main_size += initial_free_space * item->grow / 1000; + remaining_free_space = adjusted_free_space; } - return; } - } - if(total_flex_factor > 0) - { - bool processed = true; - while (processed) + // 4.c Distribute free space proportional to the flex factors. + + // If the remaining free space is zero + // Do nothing. + + if (remaining_free_space != 0) { - pixel_t sum_scaled_flex_shrink_factor = 0; - pixel_t remaining_free_space = container_main_size; - int total_not_frozen = 0; - for (auto &item: items) + for (auto& item: items) { if (!item->frozen) { - sum_scaled_flex_shrink_factor += item->scaled_flex_shrink_factor; - remaining_free_space -= item->base_size; - total_not_frozen++; - } else - { - remaining_free_space -= item->main_size; + // 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 = item->base_size + remaining_free_space * (pixel_t) item->grow / (pixel_t) sum_flex_grow_factor; } } - // 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 == 0) + } + + if (fix_min_max_violations()) break; + } +} + +void litehtml::flex_line::distribute_free_space_shrink(pixel_t container_main_size) +{ + pixel_t initial_free_space = container_main_size; + + bool all_inflexible = true; + + for (auto& item : items) + { + // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size + // any item that has a flex factor of zero + // if using the flex shrink factor: any item that has a flex base size smaller than its hypothetical main size + + // 3. Calculate initial free space. Sum the outer sizes of all items on the line, and subtract this + // from the flex container’s inner main size. For frozen items, use their outer target main size; for + // other items, use their outer flex base size. + + if (item->shrink == 0 || item->base_size < item->main_size) + { + item->frozen = true; + item->clamp_state = flex_clamp_state_inflexible; + initial_free_space -= item->main_size; + } else + { + initial_free_space -= item->base_size; + all_inflexible = false; + } + } + + // 4. Loop: + + // 4.a Check for flexible items. If all the flex items on the line are frozen, free space has been + // distributed; exit this loop. + + if (all_inflexible) return; + + while (true) + { + // 4.b Calculate the remaining free space as for initial free space, above. If the sum of the + // unfrozen flex items’ flex factors is less than one, multiply the initial free space by this sum. + // If the magnitude of this value is less than the magnitude of the remaining free space, use + // this as the remaining free space. + + int sum_flex_shrink_factor = 0; + pixel_t sum_scaled_flex_shrink_factor = 0; + pixel_t remaining_free_space = container_main_size; + + for (auto& item : items) + { + if (item->frozen) { - processed = false; + remaining_free_space -= item->main_size; } else { - int total_clamped = 0; - for (auto &item: items) + remaining_free_space -= item->base_size; + sum_flex_shrink_factor += item->shrink; + sum_scaled_flex_shrink_factor += item->scaled_flex_shrink_factor; + } + } + + if (sum_flex_shrink_factor < 1000) + { + pixel_t adjusted_free_space = initial_free_space * (pixel_t) sum_flex_shrink_factor / (pixel_t) 1000; + if (adjusted_free_space > remaining_free_space) + { + remaining_free_space = adjusted_free_space; + } + } + + // 4.c Distribute free space proportional to the flex factors. + + // If the remaining free space is zero + // Do nothing. + + if (remaining_free_space != 0) + { + for (auto& item: items) + { + if (!item->frozen) { - 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. - pixel_t scaled_flex_shrink_factor = item->base_size * item->shrink; - item->main_size = item->base_size - remaining_free_space * scaled_flex_shrink_factor / 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 = item->base_size + remaining_free_space * (pixel_t) item->grow / (pixel_t) 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 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. + + item->main_size = item->base_size + remaining_free_space * item->scaled_flex_shrink_factor / sum_scaled_flex_shrink_factor; } - if (total_clamped == 0) processed = false; } } - // Distribute remaining after algorithm space - pixel_t sum_main_size = 0; - for(auto &item : items) + + if (fix_min_max_violations()) break; + } +} + +bool litehtml::flex_line::fix_min_max_violations() +{ + // 4.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. + + pixel_t total_violation = 0; + + for (auto& item : items) + { + if (!item->frozen) { - sum_main_size += item->main_size; + if (item->main_size < item->min_size) + { + total_violation += item->min_size - item->main_size; + item->main_size = item->min_size; + item->clamp_state = flex_clamp_state_min_violation; + } else if (!item->max_size.is_default() && item->main_size > item->max_size) + { + total_violation += item->max_size - item->main_size; + item->main_size = item->max_size; + item->clamp_state = flex_clamp_state_max_violation; + } } + } - pixel_t free_space = container_main_size - sum_main_size; + // 4.e Freeze over-flexed items. The total violation is the sum of the adjustments from the + // previous step ∑(clamped size - unclamped size). If the total violation is: + // Zero: Freeze all items. + // Positive: Freeze all the items with min violations. + // Negative: Freeze all the items with max violations. - pixel_t ditribute_step = 1; - if(free_space > 0) + if (total_violation == 0) + { + return true; + } + + bool all_frozen = true; + + flex_clamp_state state_to_freeze = + total_violation > 0 + ? flex_clamp_state_min_violation + : flex_clamp_state_max_violation; + + for (auto& item : items) + { + if (!item->frozen) { - for(auto &item : items) + if (item->clamp_state == state_to_freeze) + { + item->frozen = true; + } else { - if(free_space < ditribute_step) break; - item->main_size += ditribute_step; - free_space -= ditribute_step; + all_frozen = false; + item->clamp_state = flex_clamp_state_unclamped; } } } + + return all_frozen; } bool litehtml::flex_line::distribute_main_auto_margins(pixel_t free_main_size) @@ -212,16 +317,16 @@ void litehtml::flex_line::init(pixel_t container_main_size, bool fit_container, 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); } + cross_size = 0; + main_size = 0; + first_baseline.set(0, baseline::baseline_type_none); + last_baseline.set(0, baseline::baseline_type_none); + if(is_row_direction) { def_value first_baseline_top = 0; diff --git a/src/render_block.cpp b/src/render_block.cpp index 9eba62ad..359c0832 100644 --- a/src/render_block.cpp +++ b/src/render_block.cpp @@ -279,10 +279,7 @@ litehtml::pixel_t litehtml::render_item_block::_render(pixel_t x, pixel_t y, con { m_pos.height = self_size.height; } - if (src_el()->css().get_box_sizing() == box_sizing_border_box) - { - m_pos.height -= box_sizing_height(); - } + m_pos.height -= box_sizing_height(); } else if (src_el()->is_block_formatting_context()) { // add the floats' height to the block height diff --git a/src/render_flex.cpp b/src/render_flex.cpp index 29291208..9f1eb7fd 100644 --- a/src/render_flex.cpp +++ b/src/render_flex.cpp @@ -35,11 +35,7 @@ litehtml::pixel_t litehtml::render_item_flex::_render_content(pixel_t x, pixel_t { 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(); - } + container_main_size = self_size.height - box_sizing_height(); } else { // Direction columns, height is auto - always in single line @@ -100,11 +96,7 @@ litehtml::pixel_t litehtml::render_item_flex::_render_content(pixel_t x, pixel_t { if (self_size.height.type != containing_block_context::cbc_value_type_auto) { - pixel_t height = self_size.height; - if (src_el()->css().get_box_sizing() == box_sizing_border_box) - { - height -= box_sizing_height(); - } + pixel_t height = self_size.height - box_sizing_height(); free_cross_size = height - sum_cross_size; } } else @@ -301,14 +293,13 @@ std::list litehtml::render_item_flex::get_lines(const liteh // Add flex items to lines for(auto& item : items) { - if(!line.items.empty() && !single_line && line.base_size + item->base_size > container_main_size) + if(!line.items.empty() && !single_line && line.main_size + item->main_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; + line.main_size += item->main_size; 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);