You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1118 lines
27 KiB
1118 lines
27 KiB
/*
|
|
* 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;
|
|
}
|