diff options
Diffstat (limited to 'content/handlers/html/layout_flex.c')
-rw-r--r-- | content/handlers/html/layout_flex.c | 1117 |
1 files changed, 1117 insertions, 0 deletions
diff --git a/content/handlers/html/layout_flex.c b/content/handlers/html/layout_flex.c new file mode 100644 index 000000000..bde3c5bd1 --- /dev/null +++ b/content/handlers/html/layout_flex.c @@ -0,0 +1,1117 @@ +/* + * Copyright 2022 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML layout implementation: display: flex. + * + * Layout is carried out in two stages: + * + * 1. + calculation of minimum / maximum box widths, and + * + determination of whether block level boxes will have >zero height + * + * 2. + layout (position and dimensions) + * + * In most cases the functions for the two stages are a corresponding pair + * layout_minmax_X() and layout_X(). + */ + +#include <string.h> + +#include "utils/log.h" +#include "utils/utils.h" + +#include "html/box.h" +#include "html/html.h" +#include "html/private.h" +#include "html/box_inspect.h" +#include "html/layout_internal.h" + +/** + * Flex item data + */ +struct flex_item_data { + enum css_flex_basis_e basis; + css_fixed basis_length; + css_unit basis_unit; + struct box *box; + + css_fixed shrink; + css_fixed grow; + + int min_main; + int max_main; + int min_cross; + int max_cross; + + int target_main_size; + int base_size; + int main_size; + size_t line; + + bool freeze; + bool min_violation; + bool max_violation; +}; + +/** + * Flex line data + */ +struct flex_line_data { + int main_size; + int cross_size; + + int used_main_size; + int main_auto_margin_count; + + int pos; + + size_t first; + size_t count; + size_t frozen; +}; + +/** + * Flex layout context + */ +struct flex_ctx { + html_content *content; + const struct box *flex; + const css_unit_ctx *unit_len_ctx; + + int main_size; + int cross_size; + + int available_main; + int available_cross; + + bool horizontal; + bool main_reversed; + enum css_flex_wrap_e wrap; + + struct flex_items { + size_t count; + struct flex_item_data *data; + } item; + + struct flex_lines { + size_t count; + size_t alloc; + struct flex_line_data *data; + } line; +}; + +/** + * Destroy a flex layout context + * + * \param[in] ctx Flex layout context + */ +static void layout_flex_ctx__destroy(struct flex_ctx *ctx) +{ + if (ctx != NULL) { + free(ctx->item.data); + free(ctx->line.data); + free(ctx); + } +} + +/** + * Create a flex layout context + * + * \param[in] content HTML content containing flex box + * \param[in] flex Box to create layout context for + * \return flex layout context or NULL on error + */ +static struct flex_ctx *layout_flex_ctx__create( + html_content *content, + const struct box *flex) +{ + struct flex_ctx *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return NULL; + } + ctx->line.alloc = 1; + + ctx->item.count = box_count_children(flex); + ctx->item.data = calloc(ctx->item.count, sizeof(*ctx->item.data)); + if (ctx->item.data == NULL) { + layout_flex_ctx__destroy(ctx); + return NULL; + } + + ctx->line.alloc = 1; + ctx->line.data = calloc(ctx->line.alloc, sizeof(*ctx->line.data)); + if (ctx->line.data == NULL) { + layout_flex_ctx__destroy(ctx); + return NULL; + } + + ctx->flex = flex; + ctx->content = content; + ctx->unit_len_ctx = &content->unit_len_ctx; + + ctx->wrap = css_computed_flex_wrap(flex->style); + ctx->horizontal = lh__flex_main_is_horizontal(flex); + ctx->main_reversed = lh__flex_direction_reversed(flex); + + return ctx; +} + +/** + * Find box side representing the start of flex container in main direction. + * + * \param[in] ctx Flex layout context. + * \return the start side. + */ +static enum box_side layout_flex__main_start_side( + const struct flex_ctx *ctx) +{ + if (ctx->horizontal) { + return (ctx->main_reversed) ? RIGHT : LEFT; + } else { + return (ctx->main_reversed) ? BOTTOM : TOP; + } +} + +/** + * Find box side representing the end of flex container in main direction. + * + * \param[in] ctx Flex layout context. + * \return the end side. + */ +static enum box_side layout_flex__main_end_side( + const struct flex_ctx *ctx) +{ + if (ctx->horizontal) { + return (ctx->main_reversed) ? LEFT : RIGHT; + } else { + return (ctx->main_reversed) ? TOP : BOTTOM; + } +} + +/** + * Perform layout on a flex item + * + * \param[in] ctx Flex layout context + * \param[in] item Item to lay out + * \param[in] available_width Available width for item in pixels + * \return true on success false on failure + */ +static bool layout_flex_item( + const struct flex_ctx *ctx, + const struct flex_item_data *item, + int available_width) +{ + bool success; + struct box *b = item->box; + + switch (b->type) { + case BOX_BLOCK: + success = layout_block_context(b, -1, ctx->content); + break; + case BOX_TABLE: + b->float_container = b->parent; + success = layout_table(b, available_width, ctx->content); + b->float_container = NULL; + break; + case BOX_FLEX: + b->float_container = b->parent; + success = layout_flex(b, available_width, ctx->content); + b->float_container = NULL; + break; + default: + assert(0 && "Bad flex item back type"); + success = false; + break; + } + + if (!success) { + NSLOG(flex, ERROR, "box %p: layout failed", b); + } + + return success; +} + +/** + * Calculate an item's base and target main sizes. + * + * \param[in] ctx Flex layout context + * \param[in] item Item to get sizes of + * \param[in] available_width Available width in pixels + * \return true on success false on failure + */ +static inline bool layout_flex__base_and_main_sizes( + const struct flex_ctx *ctx, + struct flex_item_data *item, + int available_width) +{ + struct box *b = item->box; + int content_min_width = b->min_width; + int content_max_width = b->max_width; + int delta_outer_main = lh__delta_outer_main(ctx->flex, b); + + NSLOG(flex, DEEPDEBUG, "box %p: delta_outer_main: %i", + b, delta_outer_main); + + if (item->basis == CSS_FLEX_BASIS_SET) { + if (item->basis_unit == CSS_UNIT_PCT) { + item->base_size = FPCT_OF_INT_TOINT( + item->basis_length, + available_width); + } else { + item->base_size = FIXTOINT(css_unit_len2device_px( + b->style, ctx->unit_len_ctx, + item->basis_length, + item->basis_unit)); + } + + } else if (item->basis == CSS_FLEX_BASIS_AUTO) { + item->base_size = ctx->horizontal ? b->width : b->height; + } else { + item->base_size = AUTO; + } + + if (ctx->horizontal == false) { + if (b->width == AUTO) { + b->width = min(max(content_min_width, available_width), + content_max_width); + b->width -= lh__delta_outer_width(b); + } + + if (!layout_flex_item(ctx, item, b->width)) { + return false; + } + } + + if (item->base_size == AUTO) { + if (ctx->horizontal == false) { + item->base_size = b->height; + } else { + item->base_size = content_max_width - delta_outer_main; + } + } + + item->base_size += delta_outer_main; + + if (ctx->horizontal) { + item->base_size = min(item->base_size, available_width); + item->base_size = max(item->base_size, content_min_width); + } + + item->target_main_size = item->base_size; + item->main_size = item->base_size; + + if (item->max_main > 0 && + item->main_size > item->max_main + delta_outer_main) { + item->main_size = item->max_main + delta_outer_main; + } + + if (item->main_size < item->min_main + delta_outer_main) { + item->main_size = item->min_main + delta_outer_main; + } + + NSLOG(flex, DEEPDEBUG, "flex-item box: %p: base_size: %i, main_size %i", + b, item->base_size, item->main_size); + + return true; +} + +/** + * Fill out all item's data in a flex container. + * + * \param[in] ctx Flex layout context + * \param[in] flex Flex box + * \param[in] available_width Available width in pixels + */ +static void layout_flex_ctx__populate_item_data( + const struct flex_ctx *ctx, + const struct box *flex, + int available_width) +{ + size_t i = 0; + bool horizontal = ctx->horizontal; + + for (struct box *b = flex->children; b != NULL; b = b->next) { + struct flex_item_data *item = &ctx->item.data[i++]; + + b->float_container = b->parent; + layout_find_dimensions(ctx->unit_len_ctx, available_width, -1, + b, b->style, &b->width, &b->height, + horizontal ? &item->max_main : &item->max_cross, + horizontal ? &item->min_main : &item->min_cross, + horizontal ? &item->max_cross : &item->max_main, + horizontal ? &item->min_cross : &item->min_main, + b->margin, b->padding, b->border); + b->float_container = NULL; + + NSLOG(flex, DEEPDEBUG, "flex-item box: %p: width: %i", + b, b->width); + + item->box = b; + item->basis = css_computed_flex_basis(b->style, + &item->basis_length, &item->basis_unit); + + css_computed_flex_shrink(b->style, &item->shrink); + css_computed_flex_grow(b->style, &item->grow); + + layout_flex__base_and_main_sizes(ctx, item, available_width); + } +} + +/** + * Ensure context's lines array has a free space + * + * \param[in] ctx Flex layout context + * \return true on success false on out of memory + */ +static bool layout_flex_ctx__ensure_line(struct flex_ctx *ctx) +{ + struct flex_line_data *temp; + size_t line_alloc = ctx->line.alloc * 2; + + if (ctx->line.alloc > ctx->line.count) { + return true; + } + + temp = realloc(ctx->line.data, sizeof(*ctx->line.data) * line_alloc); + if (temp == NULL) { + return false; + } + ctx->line.data = temp; + + memset(ctx->line.data + ctx->line.alloc, 0, + sizeof(*ctx->line.data) * (line_alloc - ctx->line.alloc)); + ctx->line.alloc = line_alloc; + + return true; +} + +/** + * Assigns flex items to the line and returns the line + * + * \param[in] ctx Flex layout context + * \param[in] item_index Index to first item to assign to this line + * \return Pointer to the new line, or NULL on error. + */ +static struct flex_line_data *layout_flex__build_line(struct flex_ctx *ctx, + size_t item_index) +{ + enum box_side start_side = layout_flex__main_start_side(ctx); + enum box_side end_side = layout_flex__main_end_side(ctx); + struct flex_line_data *line; + int used_main = 0; + + if (!layout_flex_ctx__ensure_line(ctx)) { + return NULL; + } + + line = &ctx->line.data[ctx->line.count]; + line->first = item_index; + + NSLOG(flex, DEEPDEBUG, "flex container %p: available main: %i", + ctx->flex, ctx->available_main); + + while (item_index < ctx->item.count) { + struct flex_item_data *item = &ctx->item.data[item_index]; + struct box *b = item->box; + int pos_main; + + pos_main = ctx->horizontal ? + item->main_size : + b->height + lh__delta_outer_main(ctx->flex, b); + + if (ctx->wrap == CSS_FLEX_WRAP_NOWRAP || + pos_main + used_main <= ctx->available_main || + lh__box_is_absolute(item->box) || + ctx->available_main == AUTO || + line->count == 0 || + pos_main == 0) { + if (lh__box_is_absolute(item->box) == false) { + line->main_size += item->main_size; + used_main += pos_main; + + if (b->margin[start_side] == AUTO) { + line->main_auto_margin_count++; + } + if (b->margin[end_side] == AUTO) { + line->main_auto_margin_count++; + } + } + item->line = ctx->line.count; + line->count++; + item_index++; + } else { + break; + } + } + + if (line->count > 0) { + ctx->line.count++; + } else { + NSLOG(layout, ERROR, "Failed to fit any flex items"); + } + + return line; +} + +/** + * Freeze an item on a line + * + * \param[in] line Line to containing item + * \param[in] item Item to freeze + */ +static inline void layout_flex__item_freeze( + struct flex_line_data *line, + struct flex_item_data *item) +{ + item->freeze = true; + line->frozen++; + + if (!lh__box_is_absolute(item->box)){ + line->used_main_size += item->target_main_size; + } + + NSLOG(flex, DEEPDEBUG, "flex-item box: %p: " + "Frozen at target_main_size: %i", + item->box, item->target_main_size); +} + +/** + * Calculate remaining free space and unfrozen item factor sum + * + * \param[in] ctx Flex layout context + * \param[in] line Line to calculate free space on + * \param[out] unfrozen_factor_sum Returns sum of unfrozen item's flex factors + * \param[in] initial_free_main Initial free space in main direction + * \param[in] available_main Available space in main direction + * \param[in] grow Whether to grow or shrink + * return remaining free space on line + */ +static inline int layout_flex__remaining_free_main( + struct flex_ctx *ctx, + struct flex_line_data *line, + css_fixed *unfrozen_factor_sum, + int initial_free_main, + int available_main, + bool grow) +{ + int remaining_free_main = available_main; + size_t item_count = line->first + line->count; + + *unfrozen_factor_sum = 0; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + + if (item->freeze) { + remaining_free_main -= item->target_main_size; + } else { + remaining_free_main -= item->base_size; + + *unfrozen_factor_sum += grow ? + item->grow : item->shrink; + } + } + + if (*unfrozen_factor_sum < F_1) { + int free_space = FIXTOINT(FMUL(INTTOFIX(initial_free_main), + *unfrozen_factor_sum)); + + if (free_space < remaining_free_main) { + remaining_free_main = free_space; + } + } + + NSLOG(flex, DEEPDEBUG, "Remaining free space: %i", + remaining_free_main); + + return remaining_free_main; +} + +/** + * Clamp flex item target main size and get min/max violations + * + * \param[in] ctx Flex layout context + * \param[in] line Line to align items on + * return total violation in pixels + */ +static inline int layout_flex__get_min_max_violations( + struct flex_ctx *ctx, + struct flex_line_data *line) +{ + + int total_violation = 0; + size_t item_count = line->first + line->count; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + int target_main_size = item->target_main_size; + + NSLOG(flex, DEEPDEBUG, "item %p: target_main_size: %i", + item->box, target_main_size); + + if (item->freeze) { + continue; + } + + if (item->max_main > 0 && + target_main_size > item->max_main) { + target_main_size = item->max_main; + item->max_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: max_main: %i", + item->max_main); + } + + if (target_main_size < item->min_main) { + target_main_size = item->min_main; + item->min_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: min_main: %i", + item->min_main); + } + + if (target_main_size < item->box->min_width) { + target_main_size = item->box->min_width; + item->min_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: box min_width: %i", + item->box->min_width); + } + + if (target_main_size < 0) { + target_main_size = 0; + item->min_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: less than 0"); + } + + total_violation += target_main_size - item->target_main_size; + item->target_main_size = target_main_size; + } + + NSLOG(flex, DEEPDEBUG, "Total violation: %i", total_violation); + + return total_violation; +} + +/** + * Distribute remaining free space proportional to the flex factors. + * + * Remaining free space may be negative. + * + * \param[in] ctx Flex layout context + * \param[in] line Line to distribute free space on + * \param[in] unfrozen_factor_sum Sum of unfrozen item's flex factors + * \param[in] remaining_free_main Remaining free space in main direction + * \param[in] grow Whether to grow or shrink + */ +static inline void layout_flex__distribute_free_main( + struct flex_ctx *ctx, + struct flex_line_data *line, + css_fixed unfrozen_factor_sum, + int remaining_free_main, + bool grow) +{ + size_t item_count = line->first + line->count; + + if (grow) { + css_fixed remainder = 0; + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + css_fixed result; + css_fixed ratio; + + if (item->freeze) { + continue; + } + + ratio = FDIV(item->grow, unfrozen_factor_sum); + result = FMUL(INTTOFIX(remaining_free_main), ratio) + + remainder; + + item->target_main_size = item->base_size + + FIXTOINT(result); + remainder = FIXFRAC(result); + } + } else { + css_fixed scaled_shrink_factor_sum = 0; + css_fixed remainder = 0; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + css_fixed scaled_shrink_factor; + + if (item->freeze) { + continue; + } + + scaled_shrink_factor = FMUL( + item->shrink, + INTTOFIX(item->base_size)); + scaled_shrink_factor_sum += scaled_shrink_factor; + } + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + css_fixed scaled_shrink_factor; + css_fixed result; + css_fixed ratio; + + if (item->freeze) { + continue; + } else if (scaled_shrink_factor_sum == 0) { + item->target_main_size = item->main_size; + layout_flex__item_freeze(line, item); + continue; + } + + scaled_shrink_factor = FMUL( + item->shrink, + INTTOFIX(item->base_size)); + ratio = FDIV(scaled_shrink_factor, + scaled_shrink_factor_sum); + result = FMUL(INTTOFIX(abs(remaining_free_main)), + ratio) + remainder; + + item->target_main_size = item->base_size - + FIXTOINT(result); + remainder = FIXFRAC(result); + } + } +} + +/** + * Resolve flexible item lengths along a line. + * + * See 9.7 of Tests CSS Flexible Box Layout Module Level 1. + * + * \param[in] ctx Flex layout context + * \param[in] line Line to resolve + * \return true on success, false on failure. + */ +static bool layout_flex__resolve_line( + struct flex_ctx *ctx, + struct flex_line_data *line) +{ + size_t item_count = line->first + line->count; + int available_main = ctx->available_main; + int initial_free_main; + bool grow; + + if (available_main == AUTO) { + available_main = INT_MAX; + } + + grow = (line->main_size < available_main); + initial_free_main = available_main; + + NSLOG(flex, DEEPDEBUG, "box %p: line %zu: first: %zu, count: %zu", + ctx->flex, line - ctx->line.data, + line->first, line->count); + NSLOG(flex, DEEPDEBUG, "Line main_size: %i, available_main: %i", + line->main_size, available_main); + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + + /* 3. Size inflexible items */ + if (grow) { + if (item->grow == 0 || + item->base_size > item->main_size) { + item->target_main_size = item->main_size; + layout_flex__item_freeze(line, item); + } + } else { + if (item->shrink == 0 || + item->base_size < item->main_size) { + item->target_main_size = item->main_size; + layout_flex__item_freeze(line, item); + } + } + + /* 4. Calculate initial free space */ + if (item->freeze) { + initial_free_main -= item->target_main_size; + } else { + initial_free_main -= item->base_size; + } + } + + /* 5. Loop */ + while (line->frozen < line->count) { + css_fixed unfrozen_factor_sum; + int remaining_free_main; + int total_violation; + + NSLOG(flex, DEEPDEBUG, "flex-container: %p: Resolver pass", + ctx->flex); + + /* b */ + remaining_free_main = layout_flex__remaining_free_main(ctx, + line, &unfrozen_factor_sum, initial_free_main, + available_main, grow); + + /* c */ + if (remaining_free_main != 0) { + layout_flex__distribute_free_main(ctx, + line, unfrozen_factor_sum, + remaining_free_main, grow); + } + + /* d */ + total_violation = layout_flex__get_min_max_violations( + ctx, line); + + /* e */ + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + + if (item->freeze) { + continue; + } + + if (total_violation == 0 || + (total_violation > 0 && item->min_violation) || + (total_violation < 0 && item->max_violation)) { + layout_flex__item_freeze(line, item); + } + } + } + + return true; +} + +/** + * Position items along a line + * + * \param[in] ctx Flex layout context + * \param[in] line Line to resolve + * \return true on success, false on failure. + */ +static bool layout_flex__place_line_items_main( + struct flex_ctx *ctx, + struct flex_line_data *line) +{ + int main_pos = ctx->flex->padding[layout_flex__main_start_side(ctx)]; + int post_multiplier = ctx->main_reversed ? 0 : 1; + int pre_multiplier = ctx->main_reversed ? -1 : 0; + size_t item_count = line->first + line->count; + int extra_remainder = 0; + int extra = 0; + + if (ctx->main_reversed) { + main_pos = lh__box_size_main(ctx->horizontal, ctx->flex) - + main_pos; + } + + if (ctx->available_main != AUTO && + ctx->available_main != UNKNOWN_WIDTH && + ctx->available_main > line->used_main_size) { + if (line->main_auto_margin_count > 0) { + extra = ctx->available_main - line->used_main_size; + + extra_remainder = extra % line->main_auto_margin_count; + extra /= line->main_auto_margin_count; + } + } + + for (size_t i = line->first; i < item_count; i++) { + enum box_side main_end = ctx->horizontal ? RIGHT : BOTTOM; + enum box_side main_start = ctx->horizontal ? LEFT : TOP; + struct flex_item_data *item = &ctx->item.data[i]; + struct box *b = item->box; + int extra_total = 0; + int extra_post = 0; + int extra_pre = 0; + int box_size_main; + int *box_pos_main; + + if (ctx->horizontal) { + b->width = item->target_main_size - + lh__delta_outer_width(b); + + if (!layout_flex_item(ctx, item, b->width)) { + return false; + } + } + + box_size_main = lh__box_size_main(ctx->horizontal, b); + box_pos_main = ctx->horizontal ? &b->x : &b->y; + + if (!lh__box_is_absolute(b)) { + if (b->margin[main_start] == AUTO) { + extra_pre = extra + extra_remainder; + } + if (b->margin[main_end] == AUTO) { + extra_post = extra + extra_remainder; + } + extra_total = extra_pre + extra_post; + + main_pos += pre_multiplier * + (extra_total + box_size_main + + lh__delta_outer_main(ctx->flex, b)); + } + + *box_pos_main = main_pos + lh__non_auto_margin(b, main_start) + + extra_pre + b->border[main_start].width; + + if (!lh__box_is_absolute(b)) { + int cross_size; + int box_size_cross = lh__box_size_cross( + ctx->horizontal, b); + + main_pos += post_multiplier * + (extra_total + box_size_main + + lh__delta_outer_main(ctx->flex, b)); + + cross_size = box_size_cross + lh__delta_outer_cross( + ctx->flex, b); + if (line->cross_size < cross_size) { + line->cross_size = cross_size; + } + } + } + + return true; +} + +/** + * Collect items onto lines and place items along the lines + * + * \param[in] ctx Flex layout context + * \return true on success, false on failure. + */ +static bool layout_flex__collect_items_into_lines( + struct flex_ctx *ctx) +{ + size_t pos = 0; + + while (pos < ctx->item.count) { + struct flex_line_data *line; + + line = layout_flex__build_line(ctx, pos); + if (line == NULL) { + return false; + } + + pos += line->count; + + NSLOG(flex, DEEPDEBUG, "flex-container: %p: " + "fitted: %zu (total: %zu/%zu)", + ctx->flex, line->count, + pos, ctx->item.count); + + if (!layout_flex__resolve_line(ctx, line)) { + return false; + } + + if (!layout_flex__place_line_items_main(ctx, line)) { + return false; + } + + ctx->cross_size += line->cross_size; + if (ctx->main_size < line->main_size) { + ctx->main_size = line->main_size; + } + } + + return true; +} + +/** + * Align items on a line. + * + * \param[in] ctx Flex layout context + * \param[in] line Line to align items on + * \param[in] extra Extra line width in pixels + */ +static void layout_flex__place_line_items_cross(struct flex_ctx *ctx, + struct flex_line_data *line, int extra) +{ + enum box_side cross_start = ctx->horizontal ? TOP : LEFT; + size_t item_count = line->first + line->count; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + struct box *b = item->box; + int cross_free_space; + int *box_size_cross; + int *box_pos_cross; + + box_pos_cross = ctx->horizontal ? &b->y : &b->x; + box_size_cross = lh__box_size_cross_ptr(ctx->horizontal, b); + + cross_free_space = line->cross_size + extra - *box_size_cross - + lh__delta_outer_cross(ctx->flex, b); + + switch (lh__box_align_self(ctx->flex, b)) { + default: + case CSS_ALIGN_SELF_STRETCH: + if (lh__box_size_cross_is_auto(ctx->horizontal, b)) { + *box_size_cross += cross_free_space; + + /* Relayout children for stretch. */ + if (!layout_flex_item(ctx, item, b->width)) { + return; + } + } + fallthrough; + case CSS_ALIGN_SELF_FLEX_START: + *box_pos_cross = ctx->flex->padding[cross_start] + + line->pos + + lh__non_auto_margin(b, cross_start) + + b->border[cross_start].width; + break; + + case CSS_ALIGN_SELF_FLEX_END: + *box_pos_cross = ctx->flex->padding[cross_start] + + line->pos + cross_free_space + + lh__non_auto_margin(b, cross_start) + + b->border[cross_start].width; + break; + + case CSS_ALIGN_SELF_BASELINE: + case CSS_ALIGN_SELF_CENTER: + *box_pos_cross = ctx->flex->padding[cross_start] + + line->pos + cross_free_space / 2 + + lh__non_auto_margin(b, cross_start) + + b->border[cross_start].width; + break; + } + } +} + +/** + * Place the lines and align the items on the line. + * + * \param[in] ctx Flex layout context + */ +static void layout_flex__place_lines(struct flex_ctx *ctx) +{ + bool reversed = ctx->wrap == CSS_FLEX_WRAP_WRAP_REVERSE; + int line_pos = reversed ? ctx->cross_size : 0; + int post_multiplier = reversed ? 0 : 1; + int pre_multiplier = reversed ? -1 : 0; + int extra_remainder = 0; + int extra = 0; + + if (ctx->available_cross != AUTO && + ctx->available_cross > ctx->cross_size && + ctx->line.count > 0) { + extra = ctx->available_cross - ctx->cross_size; + + extra_remainder = extra % ctx->line.count; + extra /= ctx->line.count; + } + + for (size_t i = 0; i < ctx->line.count; i++) { + struct flex_line_data *line = &ctx->line.data[i]; + + line_pos += pre_multiplier * line->cross_size; + line->pos = line_pos; + line_pos += post_multiplier * line->cross_size + + extra + extra_remainder; + + layout_flex__place_line_items_cross(ctx, line, + extra + extra_remainder); + + if (extra_remainder > 0) { + extra_remainder--; + } + } +} + +/** + * Layout a flex container. + * + * \param[in] flex table to layout + * \param[in] available_width width of containing block + * \param[in] content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +bool layout_flex(struct box *flex, int available_width, + html_content *content) +{ + int max_height, min_height; + struct flex_ctx *ctx; + bool success = false; + + ctx = layout_flex_ctx__create(content, flex); + if (ctx == NULL) { + return false; + } + + NSLOG(flex, DEEPDEBUG, "box %p: %s, available_width %i, width: %i", + flex, ctx->horizontal ? "horizontal" : "vertical", + available_width, flex->width); + + layout_find_dimensions( + ctx->unit_len_ctx, available_width, -1, + flex, flex->style, NULL, &flex->height, + NULL, NULL, &max_height, &min_height, + flex->margin, flex->padding, flex->border); + + available_width = min(available_width, flex->width); + + if (ctx->horizontal) { + ctx->available_main = available_width; + ctx->available_cross = ctx->flex->height; + } else { + ctx->available_main = ctx->flex->height; + ctx->available_cross = available_width; + } + + NSLOG(flex, DEEPDEBUG, "box %p: available_main: %i", + flex, ctx->available_main); + NSLOG(flex, DEEPDEBUG, "box %p: available_cross: %i", + flex, ctx->available_cross); + + layout_flex_ctx__populate_item_data(ctx, flex, available_width); + + /* Place items onto lines. */ + success = layout_flex__collect_items_into_lines(ctx); + if (!success) { + goto cleanup; + } + + layout_flex__place_lines(ctx); + + if (flex->height == AUTO) { + flex->height = ctx->horizontal ? + ctx->cross_size : + ctx->main_size; + } + + if (flex->height != AUTO) { + if (max_height >= 0 && flex->height > max_height) { + flex->height = max_height; + } + if (min_height > 0 && flex->height < min_height) { + flex->height = min_height; + } + } + + success = true; + +cleanup: + layout_flex_ctx__destroy(ctx); + + NSLOG(flex, DEEPDEBUG, "box %p: %s: w: %i, h: %i", flex, + success ? "success" : "failure", + flex->width, flex->height); + return success; +} |