diff options
author | Michael Drake <michael.drake@codethink.co.uk> | 2022-10-29 19:27:27 +0100 |
---|---|---|
committer | Michael Drake <mdrake.unique@gmail.com> | 2022-10-29 20:30:48 +0100 |
commit | 8615964c3fd381ef6d9a20487b9120135182dfd1 (patch) | |
tree | aa84a42f4d0058a7e4e3232d7da5dc79533e52ed /content/handlers | |
parent | 06eee4636fd444a78ee5fad70ae24cc606a3ed93 (diff) | |
download | netsurf-8615964c3fd381ef6d9a20487b9120135182dfd1.tar.gz netsurf-8615964c3fd381ef6d9a20487b9120135182dfd1.tar.bz2 |
html: layout: Initial implementation of display: flex
Diffstat (limited to 'content/handlers')
-rw-r--r-- | content/handlers/css/utils.h | 5 | ||||
-rw-r--r-- | content/handlers/html/Makefile | 1 | ||||
-rw-r--r-- | content/handlers/html/box.h | 4 | ||||
-rw-r--r-- | content/handlers/html/box_construct.c | 108 | ||||
-rw-r--r-- | content/handlers/html/box_inspect.c | 8 | ||||
-rw-r--r-- | content/handlers/html/box_inspect.h | 12 | ||||
-rw-r--r-- | content/handlers/html/box_normalise.c | 202 | ||||
-rw-r--r-- | content/handlers/html/box_normalise.h | 9 | ||||
-rw-r--r-- | content/handlers/html/box_special.c | 37 | ||||
-rw-r--r-- | content/handlers/html/layout.c | 121 | ||||
-rw-r--r-- | content/handlers/html/layout_flex.c | 830 | ||||
-rw-r--r-- | content/handlers/html/layout_internal.h | 84 |
12 files changed, 1329 insertions, 92 deletions
diff --git a/content/handlers/css/utils.h b/content/handlers/css/utils.h index 0b053745d..ee241e2cc 100644 --- a/content/handlers/css/utils.h +++ b/content/handlers/css/utils.h @@ -36,11 +36,9 @@ static inline uint8_t ns_computed_display( uint8_t value = css_computed_display(style, root); switch (value) { - case CSS_DISPLAY_FLEX: case CSS_DISPLAY_GRID: return CSS_DISPLAY_BLOCK; - case CSS_DISPLAY_INLINE_FLEX: case CSS_DISPLAY_INLINE_GRID: return CSS_DISPLAY_INLINE_BLOCK; @@ -61,11 +59,9 @@ static inline uint8_t ns_computed_display_static( uint8_t value = css_computed_display_static(style); switch (value) { - case CSS_DISPLAY_FLEX: case CSS_DISPLAY_GRID: return CSS_DISPLAY_BLOCK; - case CSS_DISPLAY_INLINE_FLEX: case CSS_DISPLAY_INLINE_GRID: return CSS_DISPLAY_INLINE_BLOCK; @@ -76,7 +72,6 @@ static inline uint8_t ns_computed_display_static( return value; } - static inline uint8_t ns_computed_min_height( const css_computed_style *style, css_fixed *length, css_unit *unit) diff --git a/content/handlers/html/Makefile b/content/handlers/html/Makefile index 8bb329b76..e41cc1d22 100644 --- a/content/handlers/html/Makefile +++ b/content/handlers/html/Makefile @@ -16,6 +16,7 @@ S_HTML := box_construct.c \ imagemap.c \ interaction.c \ layout.c \ + layout_flex.c \ object.c \ redraw.c \ redraw_border.c \ diff --git a/content/handlers/html/box.h b/content/handlers/html/box.h index 1059556e6..df2b99d87 100644 --- a/content/handlers/html/box.h +++ b/content/handlers/html/box.h @@ -66,7 +66,9 @@ typedef enum { BOX_BR, BOX_TEXT, BOX_INLINE_END, - BOX_NONE + BOX_NONE, + BOX_FLEX, + BOX_INLINE_FLEX, } box_type; diff --git a/content/handlers/html/box_construct.c b/content/handlers/html/box_construct.c index 12d9df87c..eeadb8453 100644 --- a/content/handlers/html/box_construct.c +++ b/content/handlers/html/box_construct.c @@ -86,28 +86,30 @@ struct box_construct_props { static const content_type image_types = CONTENT_IMAGE; -/** - * mapping from CSS display to box type this table must be in sync - * with libcss' css_display enum - */ +/* mapping from CSS display to box type + * this table must be in sync with libcss' css_display enum */ static const box_type box_map[] = { - 0, /* CSS_DISPLAY_INHERIT, */ - BOX_INLINE, /* CSS_DISPLAY_INLINE, */ - BOX_BLOCK, /* CSS_DISPLAY_BLOCK, */ - BOX_BLOCK, /* CSS_DISPLAY_LIST_ITEM, */ - BOX_INLINE, /* CSS_DISPLAY_RUN_IN, */ - BOX_INLINE_BLOCK, /* CSS_DISPLAY_INLINE_BLOCK, */ - BOX_TABLE, /* CSS_DISPLAY_TABLE, */ - BOX_TABLE, /* CSS_DISPLAY_INLINE_TABLE, */ - BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_ROW_GROUP, */ - BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_HEADER_GROUP, */ - BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_FOOTER_GROUP, */ - BOX_TABLE_ROW, /* CSS_DISPLAY_TABLE_ROW, */ - BOX_NONE, /* CSS_DISPLAY_TABLE_COLUMN_GROUP, */ - BOX_NONE, /* CSS_DISPLAY_TABLE_COLUMN, */ - BOX_TABLE_CELL, /* CSS_DISPLAY_TABLE_CELL, */ - BOX_INLINE, /* CSS_DISPLAY_TABLE_CAPTION, */ - BOX_NONE /* CSS_DISPLAY_NONE */ + BOX_BLOCK, /* CSS_DISPLAY_INHERIT */ + BOX_INLINE, /* CSS_DISPLAY_INLINE */ + BOX_BLOCK, /* CSS_DISPLAY_BLOCK */ + BOX_BLOCK, /* CSS_DISPLAY_LIST_ITEM */ + BOX_INLINE, /* CSS_DISPLAY_RUN_IN */ + BOX_INLINE_BLOCK, /* CSS_DISPLAY_INLINE_BLOCK */ + BOX_TABLE, /* CSS_DISPLAY_TABLE */ + BOX_TABLE, /* CSS_DISPLAY_INLINE_TABLE */ + BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_ROW_GROUP */ + BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_HEADER_GROUP */ + BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_FOOTER_GROUP */ + BOX_TABLE_ROW, /* CSS_DISPLAY_TABLE_ROW */ + BOX_NONE, /* CSS_DISPLAY_TABLE_COLUMN_GROUP */ + BOX_NONE, /* CSS_DISPLAY_TABLE_COLUMN */ + BOX_TABLE_CELL, /* CSS_DISPLAY_TABLE_CELL */ + BOX_INLINE, /* CSS_DISPLAY_TABLE_CAPTION */ + BOX_NONE, /* CSS_DISPLAY_NONE */ + BOX_FLEX, /* CSS_DISPLAY_FLEX */ + BOX_INLINE_FLEX, /* CSS_DISPLAY_INLINE_FLEX */ + BOX_BLOCK, /* CSS_DISPLAY_GRID */ + BOX_INLINE_BLOCK, /* CSS_DISPLAY_INLINE_GRID */ }; @@ -142,7 +144,6 @@ static inline bool box_is_root(dom_node *n) return true; } - /** * Extract transient construction properties * @@ -438,6 +439,23 @@ box_construct_marker(struct box *box, return true; } +static inline bool box__style_is_float(const struct box *box) +{ + return css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT; +} + +static inline bool box__is_flex(const struct box *box) +{ + return box->type == BOX_FLEX || box->type == BOX_INLINE_FLEX; +} + +static inline bool box__containing_block_is_flex( + const struct box_construct_props *props) +{ + return props->containing_block != NULL && + box__is_flex(props->containing_block); +} /** * Construct the box tree for an XML element. @@ -451,6 +469,7 @@ box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) { dom_string *title0, *s; lwc_string *id = NULL; + enum css_display_e css_display; struct box *box = NULL, *old_box; css_select_results *styles = NULL; lwc_string *bgimage_uri; @@ -549,16 +568,15 @@ box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) dom_string_unref(s); } + css_display = ns_computed_display_static(box->style); + /* Set box type from computed display */ if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || - css_computed_position(box->style) == - CSS_POSITION_FIXED) && - (ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE || - ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE_BLOCK || - ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE_TABLE)) { + css_computed_position(box->style) == CSS_POSITION_FIXED) && + (css_display == CSS_DISPLAY_INLINE || + css_display == CSS_DISPLAY_INLINE_BLOCK || + css_display == CSS_DISPLAY_INLINE_TABLE || + css_display == CSS_DISPLAY_INLINE_FLEX)) { /* Special case for absolute positioning: make absolute inlines * into inline block so that the boxes are constructed in an * inline container as if they were not absolutely positioned. @@ -572,6 +590,21 @@ box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) /* Normal mapping */ box->type = box_map[ns_computed_display(box->style, props.node_is_root)]; + + if (props.containing_block->type == BOX_FLEX || + props.containing_block->type == BOX_INLINE_FLEX) { + /* Blockification */ + switch (box->type) { + case BOX_INLINE_FLEX: + box->type = BOX_FLEX; + break; + case BOX_INLINE_BLOCK: + box->type = BOX_BLOCK; + break; + default: + break; + } + } } if (convert_special_elements(ctx->n, @@ -587,10 +620,9 @@ box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); } - if (box->type == BOX_NONE || - (ns_computed_display(box->style, - props.node_is_root) == CSS_DISPLAY_NONE && - props.node_is_root == false)) { + if (box->type == BOX_NONE || (ns_computed_display(box->style, + props.node_is_root) == CSS_DISPLAY_NONE && + props.node_is_root == false)) { css_select_results_destroy(styles); box->styles = NULL; box->style = NULL; @@ -625,8 +657,9 @@ box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) (box->type == BOX_INLINE || box->type == BOX_BR || box->type == BOX_INLINE_BLOCK || - css_computed_float(box->style) == CSS_FLOAT_LEFT || - css_computed_float(box->style) == CSS_FLOAT_RIGHT) && + box->type == BOX_INLINE_FLEX || + (box__style_is_float(box) && + !box__containing_block_is_flex(&props))) && props.node_is_root == false) { /* Found an inline child of a block without a current container * (i.e. this box is the first child of its parent, or was @@ -674,6 +707,7 @@ box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) box->flags |= CONVERT_CHILDREN; if (box->type == BOX_INLINE || box->type == BOX_BR || + box->type == BOX_INLINE_FLEX || box->type == BOX_INLINE_BLOCK) { /* Inline container must exist, as we'll have * created it above if it didn't */ @@ -690,6 +724,7 @@ box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) } if (props.node_is_root == false && + box__containing_block_is_flex(&props) == false && (css_computed_float(box->style) == CSS_FLOAT_LEFT || css_computed_float(box->style) == @@ -1342,7 +1377,6 @@ struct box *box_for_node(dom_node *n) return box; } - /* exported function documented in html/box_construct.h */ bool box_extract_link(const html_content *content, diff --git a/content/handlers/html/box_inspect.c b/content/handlers/html/box_inspect.c index b6b9f8199..181f58cf8 100644 --- a/content/handlers/html/box_inspect.c +++ b/content/handlers/html/box_inspect.c @@ -724,6 +724,14 @@ void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style) fprintf(stream, "TEXT "); break; + case BOX_FLEX: + fprintf(stream, "FLEX "); + break; + + case BOX_INLINE_FLEX: + fprintf(stream, "INLINE_FLEX "); + break; + default: fprintf(stream, "Unknown box type "); } diff --git a/content/handlers/html/box_inspect.h b/content/handlers/html/box_inspect.h index b9161f148..a218326d8 100644 --- a/content/handlers/html/box_inspect.h +++ b/content/handlers/html/box_inspect.h @@ -139,5 +139,17 @@ static inline bool box_is_first_child(struct box *b) return (b->parent == NULL || b == b->parent->children); } +static inline unsigned box_count_children(const struct box *b) +{ + const struct box *c = b->children; + unsigned count = 0; + + while (c != NULL) { + count++; + c = c->next; + } + + return count; +} #endif diff --git a/content/handlers/html/box_normalise.c b/content/handlers/html/box_normalise.c index 1b6a345d5..8f25b031f 100644 --- a/content/handlers/html/box_normalise.c +++ b/content/handlers/html/box_normalise.c @@ -177,6 +177,7 @@ box_normalise_table_row(struct box *row, return false; cell = child; break; + case BOX_FLEX: case BOX_BLOCK: case BOX_INLINE_CONTAINER: case BOX_TABLE: @@ -211,6 +212,7 @@ box_normalise_table_row(struct box *row, cell->prev = child->prev; while (child != NULL && ( + child->type == BOX_FLEX || child->type == BOX_BLOCK || child->type == BOX_INLINE_CONTAINER || child->type == BOX_TABLE || @@ -238,6 +240,7 @@ box_normalise_table_row(struct box *row, break; case BOX_INLINE: case BOX_INLINE_END: + case BOX_INLINE_FLEX: case BOX_INLINE_BLOCK: case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: @@ -314,6 +317,7 @@ box_normalise_table_row_group(struct box *row_group, c) == false) return false; break; + case BOX_FLEX: case BOX_BLOCK: case BOX_INLINE_CONTAINER: case BOX_TABLE: @@ -348,6 +352,7 @@ box_normalise_table_row_group(struct box *row_group, row->prev = child->prev; while (child != NULL && ( + child->type == BOX_FLEX || child->type == BOX_BLOCK || child->type == BOX_INLINE_CONTAINER || child->type == BOX_TABLE || @@ -377,6 +382,7 @@ box_normalise_table_row_group(struct box *row_group, break; case BOX_INLINE: case BOX_INLINE_END: + case BOX_INLINE_FLEX: case BOX_INLINE_BLOCK: case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: @@ -648,6 +654,7 @@ box_normalise_table(struct box *table, const struct box *root, html_content * c) return false; } break; + case BOX_FLEX: case BOX_BLOCK: case BOX_INLINE_CONTAINER: case BOX_TABLE: @@ -686,6 +693,7 @@ box_normalise_table(struct box *table, const struct box *root, html_content * c) row_group->prev = child->prev; while (child != NULL && ( + child->type == BOX_FLEX || child->type == BOX_BLOCK || child->type == BOX_INLINE_CONTAINER || child->type == BOX_TABLE || @@ -716,6 +724,7 @@ box_normalise_table(struct box *table, const struct box *root, html_content * c) break; case BOX_INLINE: case BOX_INLINE_END: + case BOX_INLINE_FLEX: case BOX_INLINE_BLOCK: case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: @@ -806,6 +815,181 @@ box_normalise_table(struct box *table, const struct box *root, html_content * c) return true; } +static bool box_normalise_flex( + struct box *flex_container, + const struct box *root, + html_content *c) +{ + struct box *child; + struct box *next_child; + struct box *implied_flex_item; + css_computed_style *style; + nscss_select_ctx ctx; + + assert(flex_container != NULL); + assert(root != NULL); + + ctx.root_style = root->style; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "flex_container %p, flex_container->type %u", + flex_container, flex_container->type); +#endif + + assert(flex_container->type == BOX_FLEX || + flex_container->type == BOX_INLINE_FLEX); + + for (child = flex_container->children; child != NULL; child = next_child) { +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "child %p, child->type = %d", + child, child->type); +#endif + + next_child = child->next; /* child may be destroyed */ + + switch (child->type) { + case BOX_FLEX: + /* ok */ + if (box_normalise_flex(child, root, c) == false) + return false; + break; + case BOX_BLOCK: + /* ok */ + if (box_normalise_block(child, root, c) == false) + return false; + break; + case BOX_INLINE_CONTAINER: + /* insert implied flex item */ + assert(flex_container->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + flex_container->style); + if (style == NULL) + return false; + + implied_flex_item = box_create(NULL, style, true, + flex_container->href, + flex_container->target, + NULL, NULL, c->bctx); + if (implied_flex_item == NULL) { + css_computed_style_destroy(style); + return false; + } + implied_flex_item->type = BOX_BLOCK; + + if (child->prev == NULL) + flex_container->children = implied_flex_item; + else + child->prev->next = implied_flex_item; + + implied_flex_item->prev = child->prev; + + while (child != NULL && + child->type == BOX_INLINE_CONTAINER) { + box_add_child(implied_flex_item, child); + + next_child = child->next; + child->next = NULL; + child = next_child; + } + + implied_flex_item->last->next = NULL; + implied_flex_item->next = next_child = child; + if (implied_flex_item->next != NULL) + implied_flex_item->next->prev = implied_flex_item; + else + flex_container->last = implied_flex_item; + implied_flex_item->parent = flex_container; + + if (box_normalise_block(implied_flex_item, + root, c) == false) + return false; + break; + + case BOX_TABLE: + if (box_normalise_table(child, root, c) == false) + return false; + break; + case BOX_INLINE: + case BOX_INLINE_END: + case BOX_INLINE_FLEX: + case BOX_INLINE_BLOCK: + case BOX_FLOAT_LEFT: + case BOX_FLOAT_RIGHT: + case BOX_BR: + case BOX_TEXT: + /* should have been wrapped in inline + container by convert_xml_to_box() */ + assert(0); + break; + case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_ROW: + case BOX_TABLE_CELL: + /* insert implied table */ + assert(flex_container->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + flex_container->style); + if (style == NULL) + return false; + + implied_flex_item = box_create(NULL, style, true, + flex_container->href, + flex_container->target, + NULL, NULL, c->bctx); + if (implied_flex_item == NULL) { + css_computed_style_destroy(style); + return false; + } + implied_flex_item->type = BOX_TABLE; + + if (child->prev == NULL) + flex_container->children = implied_flex_item; + else + child->prev->next = implied_flex_item; + + implied_flex_item->prev = child->prev; + + while (child != NULL && ( + child->type == BOX_TABLE_ROW_GROUP || + child->type == BOX_TABLE_ROW || + child->type == BOX_TABLE_CELL)) { + box_add_child(implied_flex_item, child); + + next_child = child->next; + child->next = NULL; + child = next_child; + } + + implied_flex_item->last->next = NULL; + implied_flex_item->next = next_child = child; + if (implied_flex_item->next != NULL) + implied_flex_item->next->prev = implied_flex_item; + else + flex_container->last = implied_flex_item; + implied_flex_item->parent = flex_container; + + if (box_normalise_table(implied_flex_item, + root, c) == false) + return false; + break; + default: + assert(0); + } + } + + return true; +} static bool box_normalise_inline_container(struct box *cont, @@ -836,6 +1020,11 @@ box_normalise_inline_container(struct box *cont, if (box_normalise_block(child, root, c) == false) return false; break; + case BOX_INLINE_FLEX: + /* ok */ + if (box_normalise_flex(child, root, c) == false) + return false; + break; case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: /* ok */ @@ -852,6 +1041,11 @@ box_normalise_inline_container(struct box *cont, c) == false) return false; break; + case BOX_FLEX: + if (box_normalise_flex(child->children, root, + c) == false) + return false; + break; default: assert(0); } @@ -870,6 +1064,7 @@ box_normalise_inline_container(struct box *cont, box_free(child); } break; + case BOX_FLEX: case BOX_BLOCK: case BOX_INLINE_CONTAINER: case BOX_TABLE: @@ -888,7 +1083,6 @@ box_normalise_inline_container(struct box *cont, return true; } - /* Exported function documented in html/box_normalise.h */ bool box_normalise_block(struct box *block, const struct box *root, html_content *c) @@ -920,6 +1114,11 @@ box_normalise_block(struct box *block, const struct box *root, html_content *c) next_child = child->next; /* child may be destroyed */ switch (child->type) { + case BOX_FLEX: + /* ok */ + if (box_normalise_flex(child, root, c) == false) + return false; + break; case BOX_BLOCK: /* ok */ if (box_normalise_block(child, root, c) == false) @@ -935,6 +1134,7 @@ box_normalise_block(struct box *block, const struct box *root, html_content *c) break; case BOX_INLINE: case BOX_INLINE_END: + case BOX_INLINE_FLEX: case BOX_INLINE_BLOCK: case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: diff --git a/content/handlers/html/box_normalise.h b/content/handlers/html/box_normalise.h index 591feab8d..377cd9019 100644 --- a/content/handlers/html/box_normalise.h +++ b/content/handlers/html/box_normalise.h @@ -50,14 +50,15 @@ * The tree is modified to satisfy the following: * \code * parent permitted child nodes - * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE - * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT + * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE, FLEX + * FLEX, INLINE_FLEX BLOCK, INLINE_CONTAINER, TABLE, FLEX + * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT, INLINE_FLEX * INLINE, TEXT none * TABLE at least 1 TABLE_ROW_GROUP * TABLE_ROW_GROUP at least 1 TABLE_ROW * TABLE_ROW at least 1 TABLE_CELL - * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK) - * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE + * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE, FLEX (same as BLOCK) + * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK, TABLE or FLEX * \endcode */ bool box_normalise_block(struct box *block, const struct box *root, struct html_content *c); diff --git a/content/handlers/html/box_special.c b/content/handlers/html/box_special.c index f761557e0..db3c4126d 100644 --- a/content/handlers/html/box_special.c +++ b/content/handlers/html/box_special.c @@ -560,8 +560,18 @@ static bool box_input_text(html_content *html, struct box *box, struct dom_node *node) { struct box *inline_container, *inline_box; + uint8_t display = css_computed_display_static(box->style); - box->type = BOX_INLINE_BLOCK; + switch (display) { + case CSS_DISPLAY_GRID: + case CSS_DISPLAY_FLEX: + case CSS_DISPLAY_BLOCK: + box->type = BOX_BLOCK; + break; + default: + box->type = BOX_INLINE_BLOCK; + break; + } inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx); if (!inline_container) @@ -825,8 +835,8 @@ box_canvas(dom_node *n, } *convert_children = false; - if (box->style && - ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) return true; /* This is replaced content */ @@ -854,8 +864,8 @@ box_embed(dom_node *n, dom_string *src; dom_exception err; - if (box->style && - ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) return true; params = talloc(content->bctx, struct object_params); @@ -1025,8 +1035,8 @@ box_iframe(dom_node *n, struct content_html_iframe *iframe; int i; - if (box->style && - ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) return true; if (box->style && @@ -1154,8 +1164,8 @@ box_image(dom_node *n, css_unit wunit = CSS_UNIT_PX; css_unit hunit = CSS_UNIT_PX; - if (box->style && - ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) return true; /* handle alt text */ @@ -1322,10 +1332,9 @@ box_input(dom_node *n, corestring_lwc_image)) { gadget->type = GADGET_IMAGE; - if (box->style && - ns_computed_display(box->style, + if (box->style && ns_computed_display(box->style, box_is_root(n)) != CSS_DISPLAY_NONE && - nsoption_bool(foreground_images) == true) { + nsoption_bool(foreground_images) == true) { dom_string *s; err = dom_element_get_attribute(n, corestring_dom_src, &s); @@ -1405,8 +1414,8 @@ box_object(dom_node *n, dom_node *c; dom_exception err; - if (box->style && - ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) return true; if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) == diff --git a/content/handlers/html/layout.c b/content/handlers/html/layout.c index 816775c3c..8ba45da78 100644 --- a/content/handlers/html/layout.c +++ b/content/handlers/html/layout.c @@ -522,11 +522,11 @@ layout_minmax_line(struct box *first, if (lh__box_is_float_box(b)) { assert(b->children); - if (b->children->type == BOX_BLOCK) - layout_minmax_block(b->children, font_func, + if (b->children->type == BOX_TABLE) + layout_minmax_table(b->children, font_func, content); else - layout_minmax_table(b->children, font_func, + layout_minmax_block(b->children, font_func, content); b->min_width = b->children->min_width; b->max_width = b->children->max_width; @@ -536,7 +536,7 @@ layout_minmax_line(struct box *first, continue; } - if (b->type == BOX_INLINE_BLOCK) { + if (b->type == BOX_INLINE_BLOCK || b->type == BOX_INLINE_FLEX) { layout_minmax_block(b, font_func, content); if (min < b->min_width) min = b->min_width; @@ -869,7 +869,6 @@ layout_minmax_inline_container(struct box *inline_container, inline_container->max_width); } - /** * Calculate minimum and maximum width of a block. * @@ -900,6 +899,8 @@ static void layout_minmax_block( bool child_has_height = false; assert(block->type == BOX_BLOCK || + block->type == BOX_FLEX || + block->type == BOX_INLINE_FLEX || block->type == BOX_INLINE_BLOCK || block->type == BOX_TABLE_CELL); @@ -915,7 +916,8 @@ static void layout_minmax_block( /* set whether the minimum width is of any interest for this box */ if (((block->parent && lh__box_is_float_box(block->parent)) || - block->type == BOX_INLINE_BLOCK) && + block->type == BOX_INLINE_BLOCK || + block->type == BOX_INLINE_FLEX) && wtype != CSS_WIDTH_SET) { /* box shrinks to fit; need minimum width */ block->flags |= NEED_MIN; @@ -926,6 +928,9 @@ static void layout_minmax_block( wtype != CSS_WIDTH_SET) { /* box inside shrink-to-fit context; need minimum width */ block->flags |= NEED_MIN; + } else if (block->parent && (block->parent->type == BOX_FLEX)) { + /* box is flex item */ + block->flags |= NEED_MIN; } if (block->gadget && (block->gadget->type == GADGET_TEXTBOX || @@ -975,6 +980,7 @@ static void layout_minmax_block( /* recurse through children */ for (child = block->children; child; child = child->next) { switch (child->type) { + case BOX_FLEX: case BOX_BLOCK: layout_minmax_block(child, font_func, content); @@ -1016,10 +1022,24 @@ static void layout_minmax_block( continue; } - if (min < child->min_width) - min = child->min_width; - if (max < child->max_width) - max = child->max_width; + if (lh__box_is_flex_container(block) && + lh__flex_main_is_horizontal(block)) { + if (block->style != NULL && + css_computed_flex_wrap(block->style) == + CSS_FLEX_WRAP_NOWRAP) { + min += child->min_width; + } else { + if (min < child->min_width) + min = child->min_width; + } + max += child->max_width; + + } else { + if (min < child->min_width) + min = child->min_width; + if (max < child->max_width) + max = child->max_width; + } if (child_has_height) block->flags |= HAS_HEIGHT; @@ -1032,7 +1052,7 @@ static void layout_minmax_block( } /* fixed width takes priority */ - if (block->type != BOX_TABLE_CELL) { + if (block->type != BOX_TABLE_CELL && !lh__box_is_flex_item(block)) { bool border_box = bs == CSS_BOX_SIZING_BORDER_BOX; enum css_max_width_e max_type; enum css_min_width_e min_type; @@ -1121,7 +1141,8 @@ static void layout_minmax_block( block->max_width = (max + extra_fixed) / (1.0 - extra_frac); } - assert(0 <= block->min_width && block->min_width <= block->max_width); + assert(0 <= block->min_width); + assert(block->min_width <= block->max_width); } @@ -2283,7 +2304,9 @@ static bool layout_block_object(struct box *block) { assert(block); assert(block->type == BOX_BLOCK || + block->type == BOX_FLEX || block->type == BOX_INLINE_BLOCK || + block->type == BOX_INLINE_FLEX || block->type == BOX_TABLE || block->type == BOX_TABLE_CELL); assert(block->object); @@ -2577,12 +2600,20 @@ layout_float_find_dimensions( */ static bool layout_float(struct box *b, int width, html_content *content) { - assert(b->type == BOX_TABLE || b->type == BOX_BLOCK || - b->type == BOX_INLINE_BLOCK); + assert(b->type == BOX_TABLE || + b->type == BOX_BLOCK || + b->type == BOX_INLINE_BLOCK || + b->type == BOX_FLEX || + b->type == BOX_INLINE_FLEX); layout_float_find_dimensions(&content->unit_len_ctx, width, b->style, b); - if (b->type == BOX_TABLE) { - if (!layout_table(b, width, content)) - return false; + if (b->type == BOX_TABLE || b->type == BOX_INLINE_FLEX) { + if (b->type == BOX_TABLE) { + if (!layout_table(b, width, content)) + return false; + } else { + if (!layout_flex(b, width, content)) + return false; + } if (b->margin[LEFT] == AUTO) b->margin[LEFT] = 0; if (b->margin[RIGHT] == AUTO) @@ -2591,8 +2622,9 @@ static bool layout_float(struct box *b, int width, html_content *content) b->margin[TOP] = 0; if (b->margin[BOTTOM] == AUTO) b->margin[BOTTOM] = 0; - } else + } else { return layout_block_context(b, -1, content); + } return true; } @@ -2800,7 +2832,8 @@ layout_line(struct box *first, x += space_after; - if (b->type == BOX_INLINE_BLOCK) { + if (b->type == BOX_INLINE_BLOCK || + b->type == BOX_INLINE_FLEX) { if (b->max_width != UNKNOWN_WIDTH) if (!layout_float(b, *width, content)) return false; @@ -3020,7 +3053,8 @@ layout_line(struct box *first, b->x = x; if ((b->type == BOX_INLINE && !b->inline_end) || - b->type == BOX_INLINE_BLOCK) { + b->type == BOX_INLINE_BLOCK || + b->type == BOX_INLINE_FLEX) { b->x += b->margin[LEFT] + b->border[LEFT].width; x = b->x + b->padding[LEFT] + b->width + b->padding[RIGHT] + @@ -3521,7 +3555,9 @@ bool layout_block_context( assert(block->type == BOX_BLOCK || block->type == BOX_INLINE_BLOCK || - block->type == BOX_TABLE_CELL); + block->type == BOX_TABLE_CELL || + block->type == BOX_FLEX || + block->type == BOX_INLINE_FLEX); assert(block->width != UNKNOWN_WIDTH); assert(block->width != AUTO); @@ -3590,7 +3626,9 @@ bool layout_block_context( enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; - assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || + assert(box->type == BOX_BLOCK || + box->type == BOX_FLEX || + box->type == BOX_TABLE || box->type == BOX_INLINE_CONTAINER); /* Tables are laid out before being positioned, because the @@ -3641,7 +3679,9 @@ bool layout_block_context( * left and right margins to avoid any floats. */ lm = rm = 0; - if (box->type == BOX_BLOCK || box->flags & IFRAME) { + if (box->type == BOX_FLEX || + box->type == BOX_BLOCK || + box->flags & IFRAME) { if (lh__box_is_object(box) == false && box->style && (overflow_x != CSS_OVERFLOW_VISIBLE || @@ -3727,6 +3767,7 @@ bool layout_block_context( /* Vertical margin */ if (((box->type == BOX_BLOCK && (box->flags & HAS_HEIGHT)) || + box->type == BOX_FLEX || box->type == BOX_TABLE || (box->type == BOX_INLINE_CONTAINER && !box_is_first_child(box)) || @@ -3751,11 +3792,19 @@ bool layout_block_context( /* Unless the box has an overflow style of visible, the box * establishes a new block context. */ - if (box->type == BOX_BLOCK && box->style && - (overflow_x != CSS_OVERFLOW_VISIBLE || - overflow_y != CSS_OVERFLOW_VISIBLE)) { + if (box->type == BOX_FLEX || + (box->type == BOX_BLOCK && box->style && + (overflow_x != CSS_OVERFLOW_VISIBLE || + overflow_y != CSS_OVERFLOW_VISIBLE))) { - layout_block_context(box, viewport_height, content); + if (box->type == BOX_FLEX) { + if (!layout_flex(box, box->width, content)) { + return false; + } + } else { + layout_block_context(box, + viewport_height, content); + } cy += box->padding[TOP]; @@ -3776,7 +3825,8 @@ bool layout_block_context( goto advance_to_next_box; } - NSLOG(layout, DEBUG, "box %p, cx %i, cy %i", box, cx, cy); + NSLOG(layout, DEBUG, "box %p, cx %i, cy %i, width %i", + box, cx, cy, box->width); /* Layout (except tables). */ if (box->object) { @@ -4552,7 +4602,9 @@ layout_absolute(struct box *box, int space; assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || - box->type == BOX_INLINE_BLOCK); + box->type == BOX_INLINE_BLOCK || + box->type == BOX_FLEX || + box->type == BOX_INLINE_FLEX); /* The static position is where the box would be if it was not * absolutely positioned. The x and y are filled in by @@ -4787,6 +4839,13 @@ layout_absolute(struct box *box, box->float_container = NULL; layout_solve_width(box, box->parent->width, box->width, 0, 0, -1, -1); + } else if (box->type == BOX_FLEX || box->type == BOX_INLINE_FLEX) { + /* layout_table also expects the containing block to be + * stored in the float_container field */ + box->float_container = containing_block; + if (!layout_flex(box, width, content)) + return false; + box->float_container = NULL; } /* 10.6.4 */ @@ -4923,7 +4982,9 @@ layout_position_absolute(struct box *box, for (c = box->children; c; c = c->next) { if ((c->type == BOX_BLOCK || c->type == BOX_TABLE || - c->type == BOX_INLINE_BLOCK) && + c->type == BOX_INLINE_BLOCK || + c->type == BOX_FLEX || + c->type == BOX_INLINE_FLEX) && (css_computed_position(c->style) == CSS_POSITION_ABSOLUTE || css_computed_position(c->style) == diff --git a/content/handlers/html/layout_flex.c b/content/handlers/html/layout_flex.c new file mode 100644 index 000000000..54bab70c3 --- /dev/null +++ b/content/handlers/html/layout_flex.c @@ -0,0 +1,830 @@ +/* + * 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" + +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; +}; + +struct flex_line_data { + int main_size; + int cross_size; + + size_t first; + size_t count; + size_t frozen; +}; + +struct flex_ctx { + html_content *content; + const struct box *flex; + const css_unit_ctx *unit_len_ctx; + + int main_size; + int cross_size; + + bool horizontal; + 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; +}; + +static void layout_flex_ctx__destroy(struct flex_ctx *ctx) +{ + if (ctx != NULL) { + free(ctx->item.data); + free(ctx->line.data); + free(ctx); + } +} + +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); + + return ctx; +} + +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; + } + + return success; +} + +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, WARNING, "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(available_width, content_max_width); + b->width -= lh__delta_outer_width(b); + } + + if (!layout_flex_item(ctx, item, b->width)) { + NSLOG(flex, WARNING, "box %p: layout failed", b); + 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, WARNING, "flex-item box: %p: base_size: %i, main_size %i", + b, item->base_size, item->main_size); + + return true; +} + +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, WARNING, "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); + } +} + +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; +} + +static struct flex_line_data *layout_flex__build_line(struct flex_ctx *ctx, + size_t item_index, int available_width, html_content *content) +{ + struct flex_line_data *line; + int available_main; + int used_main = 0; + + if (!layout_flex_ctx__ensure_line(ctx)) { + return 0; + } + + line = &ctx->line.data[ctx->line.count]; + line->first = item_index; + + if (ctx->horizontal) { + available_main = available_width; + } else { + available_main = ctx->flex->height; + } + + NSLOG(flex, WARNING, "flex container %p: available main: %i", + ctx->flex, available_main); + + while (item_index < ctx->item.count) { + struct flex_item_data *item = &ctx->item.data[item_index]; + struct box *b = item->box; + int main; + + main = ctx->horizontal ? + item->main_size : + b->height + lh__delta_outer_main(ctx->flex, b); + + if (ctx->wrap == CSS_FLEX_WRAP_NOWRAP || + main + used_main <= available_main || + lh__box_is_absolute(item->box) || + available_main == AUTO || + line->count == 0 || + main == 0) { + if (lh__box_is_absolute(item->box) == false) { + line->main_size += item->main_size; + used_main += main; + } + 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; +} + +static inline void layout_flex__item_freeze( + struct flex_line_data *line, + struct flex_item_data *item) +{ + item->freeze = true; + line->frozen++; + + NSLOG(flex, WARNING, "flex-item box: %p: Frozen at target_main_size: %i", + item->box, item->target_main_size); +} + +static inline int layout_flex__remaining_free_space( + struct flex_ctx *ctx, + struct flex_line_data *line, + css_fixed *unfrozen_factor_sum, + int initial_free_space, + int available_space, + bool grow) +{ + int remaining_free_space = available_space; + 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_space -= item->target_main_size; + } else { + remaining_free_space -= 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_space), + *unfrozen_factor_sum)); + + if (free_space < remaining_free_space) { + remaining_free_space = free_space; + } + } + + NSLOG(flex, WARNING, "Remaining free space: %i", remaining_free_space); + + return remaining_free_space; +} + +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, WARNING, "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, WARNING, "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, WARNING, "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, WARNING, "Violation: box min_width: %i", + item->box->min_width); + } + + if (target_main_size < 0) { + target_main_size = 0; + item->min_violation = true; + NSLOG(flex, WARNING, "Violation: less than 0"); + } + + total_violation += target_main_size - item->target_main_size; + item->target_main_size = target_main_size; + } + + NSLOG(flex, WARNING, "Total violation: %i", total_violation); + + return total_violation; +} + +static inline void layout_flex__distribute_free_space( + struct flex_ctx *ctx, + struct flex_line_data *line, + css_fixed unfrozen_factor_sum, + int remaining_free_space, + bool grow) +{ + size_t item_count = line->first + line->count; + + if (grow) { + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + css_fixed ratio; + + if (item->freeze) { + continue; + } + + ratio = FDIV(item->grow, unfrozen_factor_sum); + + item->target_main_size = item->base_size + + FIXTOINT(FMUL( + INTTOFIX(remaining_free_space), + ratio)); + } + } else { + css_fixed scaled_shrink_factor_sum = 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 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); + + item->target_main_size = item->base_size - + FIXTOINT(FMUL( + INTTOFIX(abs(remaining_free_space)), + ratio)); + } + } +} + +static bool layout_flex__resolve_line_horizontal( + struct flex_ctx *ctx, + struct flex_line_data *line, + int available_width) +{ + size_t item_count = line->first + line->count; + int x = ctx->flex->padding[LEFT]; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + struct box *b = item->box; + bool success = false; + + b->width = item->target_main_size - lh__delta_outer_width(b); + + success = layout_flex_item(ctx, item, b->width); + if (!success) { + NSLOG(flex, WARNING, "box %p: layout failed", b); + return false; + } + + b->y = ctx->flex->padding[TOP] + ctx->cross_size + + lh__non_auto_margin(b, TOP) + + b->border[TOP].width; + + b->x = x + lh__non_auto_margin(b, LEFT) + + b->border[LEFT].width; + + if (lh__box_is_absolute(b) == false) { + int height; + + height = b->height + lh__delta_outer_height(b); + if (line->cross_size < height) { + line->cross_size = height; + } + + x += b->width + lh__delta_outer_width(b); + } + } + + return true; +} + +static bool layout_flex__resolve_line_vertical( + struct flex_ctx *ctx, + struct flex_line_data *line, + int available_width) +{ + size_t item_count = line->first + line->count; + int y = ctx->flex->padding[TOP]; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + struct box *b = item->box; + + b->x = ctx->flex->padding[LEFT] + ctx->cross_size + + lh__non_auto_margin(b, LEFT) + + b->border[LEFT].width; + + b->y = y + lh__non_auto_margin(b, TOP) + + b->border[TOP].width; + + if (lh__box_is_absolute(b) == false) { + int width; + + width = b->width + lh__delta_outer_width(b); + if (line->cross_size < width) { + line->cross_size = width; + } + + y += b->height + lh__delta_outer_height(b); + } + } + + return true; +} + +/** 9.7. Resolving Flexible Lengths */ +static bool layout_flex__resolve_line( + struct flex_ctx *ctx, + struct flex_line_data *line, + int available_width) +{ + bool grow = (line->main_size < available_width); + size_t item_count = line->first + line->count; + int available_space = available_width; + int initial_free_space; + + available_space = available_width; + if (ctx->horizontal == false) { + available_space = ctx->flex->height; + if (available_space == AUTO) { + available_space = INT_MAX; + } + } + + initial_free_space = available_space; + + NSLOG(flex, WARNING, "box %p: line %zu: first: %zu, count: %zu", + ctx->flex, line - ctx->line.data, + line->first, line->count); + NSLOG(flex, WARNING, "Line main_size: %i, available_space: %i", + line->main_size, available_space); + + 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_space -= item->target_main_size; + } else { + initial_free_space -= item->base_size; + } + } + + /* 5. Loop */ + while (line->frozen < line->count) { + css_fixed unfrozen_factor_sum; + int remaining_free_space; + int total_violation; + + NSLOG(flex, WARNING, "flex-container: %p: Resolver pass", + ctx->flex); + + /* b */ + remaining_free_space = layout_flex__remaining_free_space(ctx, + line, &unfrozen_factor_sum, initial_free_space, + available_space, grow); + + /* c */ + if (remaining_free_space != 0) { + layout_flex__distribute_free_space(ctx, + line, unfrozen_factor_sum, + remaining_free_space, 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 (total_violation == 0 || + (total_violation > 0 && item->min_violation) || + (total_violation < 0 && item->max_violation)) { + layout_flex__item_freeze(line, item); + } + } + } + + if (ctx->horizontal) { + if (!layout_flex__resolve_line_horizontal(ctx, + line, available_width)) { + return false; + } + } else { + if (!layout_flex__resolve_line_vertical(ctx, + line, available_width)) { + return false; + } + } + + return true; +} + +static bool layout_flex__collect_items_into_lines( + struct flex_ctx *ctx, + int available_width, + html_content *content) +{ + size_t pos = 0; + + while (pos < ctx->item.count) { + struct flex_line_data *line; + + line = layout_flex__build_line(ctx, pos, + available_width, content); + if (line == NULL) { + return false; + } + + pos += line->count; + + NSLOG(flex, WARNING, "flex-container: %p: " + "fitted: %zu (total: %zu/%zu)", + ctx->flex, line->count, + pos, ctx->item.count); + + if (!layout_flex__resolve_line(ctx, line, available_width)) { + return false; + } + + ctx->cross_size += line->cross_size; + if (ctx->main_size < line->main_size) { + ctx->main_size = line->main_size; + } + } + + return true; +} + +/** + * 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, WARNING, "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); + + layout_flex_ctx__populate_item_data(ctx, flex, available_width); + + /* Place items onto lines. */ + success = layout_flex__collect_items_into_lines(ctx, + available_width, content); + if (!success) { + goto cleanup; + } + + 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, WARNING, "box %p: %s", flex, + success ? "success" : "failure"); + return success; +} diff --git a/content/handlers/html/layout_internal.h b/content/handlers/html/layout_internal.h index 39b42b10d..445b43051 100644 --- a/content/handlers/html/layout_internal.h +++ b/content/handlers/html/layout_internal.h @@ -106,11 +106,25 @@ static inline bool lh__box_is_float_box(const struct box *b) static inline bool lh__box_is_inline_flow(const struct box *b) { return b->type == BOX_INLINE || + b->type == BOX_INLINE_FLEX || b->type == BOX_INLINE_BLOCK || b->type == BOX_TEXT || b->type == BOX_INLINE_END; } +/** Layout helper: Check whether box takes part in inline flow. */ +static inline bool lh__box_is_flex_container(const struct box *b) +{ + return b->type == BOX_FLEX || + b->type == BOX_INLINE_FLEX; +} + +/** Layout helper: Check whether box takes part in inline flow. */ +static inline bool lh__box_is_flex_item(const struct box *b) +{ + return (b->parent != NULL) && lh__box_is_flex_container(b->parent); +} + /** Layout helper: Check whether box is inline level. (Includes BR.) */ static inline bool lh__box_is_inline_level(const struct box *b) { @@ -147,6 +161,76 @@ static inline bool lh__have_border( return border_style_funcs[side](style) != CSS_BORDER_STYLE_NONE; } +static inline bool lh__box_is_absolute(const struct box *b) +{ + return css_computed_position(b->style) == CSS_POSITION_ABSOLUTE || + css_computed_position(b->style) == CSS_POSITION_FIXED; +} + +static inline bool lh__flex_main_is_horizontal(const struct box *flex) +{ + const css_computed_style *style = flex->style; + + assert(style != NULL); + + switch (css_computed_flex_direction(style)) { + default: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_ROW: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_ROW_REVERSE: + return true; + case CSS_FLEX_DIRECTION_COLUMN: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_COLUMN_REVERSE: + return false; + } +} + +static inline int lh__non_auto_margin(const struct box *b, enum box_side side) +{ + return (b->margin[side] == AUTO) ? 0 : b->margin[side]; +} + +static inline int lh__delta_outer_height(const struct box *b) +{ + return b->padding[TOP] + + b->padding[BOTTOM] + + b->border[TOP].width + + b->border[BOTTOM].width + + lh__non_auto_margin(b, TOP) + + lh__non_auto_margin(b, BOTTOM); +} + +static inline int lh__delta_outer_width(const struct box *b) +{ + return b->padding[LEFT] + + b->padding[RIGHT] + + b->border[LEFT].width + + b->border[RIGHT].width + + lh__non_auto_margin(b, LEFT) + + lh__non_auto_margin(b, RIGHT); +} + +static inline int lh__delta_outer_main( + const struct box *flex, + const struct box *b) +{ + if (lh__flex_main_is_horizontal(flex)) { + return lh__delta_outer_width(b); + } else { + return lh__delta_outer_height(b); + } +} + +static inline int lh__delta_outer_cross( + const struct box *flex, + const struct box *b) +{ + if (lh__flex_main_is_horizontal(flex) == false) { + return lh__delta_outer_width(b); + } else { + return lh__delta_outer_height(b); + } +} + /** * Determine width of margin, borders, and padding on one side of a box. * |