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.
909 lines
25 KiB
909 lines
25 KiB
/*
|
|
* Copyright 2023 Vincent Sanders <vince@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 for blocks
|
|
*/
|
|
|
|
#include "utils/log.h"
|
|
#include "utils/utils.h"
|
|
#include "netsurf/content.h"
|
|
#include "netsurf/mouse.h"
|
|
#include "netsurf/plot_style.h"
|
|
#include "content/content.h"
|
|
#include "desktop/scrollbar.h"
|
|
#include "desktop/textarea.h"
|
|
|
|
#include "html/private.h"
|
|
#include "html/box.h"
|
|
#include "html/box_inspect.h"
|
|
#include "html/font.h"
|
|
#include "html/form_internal.h"
|
|
|
|
#include "html/layout/internal.h"
|
|
#include "html/layout/flex.h"
|
|
#include "html/layout/table.h"
|
|
#include "html/layout/line.h"
|
|
#include "html/layout/block.h"
|
|
|
|
|
|
/**
|
|
* Find next block that current margin collapses to.
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param box box to start tree-order search from (top margin is included)
|
|
* \param block box responsible for current block fromatting context
|
|
* \param viewport_height height of viewport in px
|
|
* \param max_pos_margin updated to to maximum positive margin encountered
|
|
* \param max_neg_margin updated to to maximum negative margin encountered
|
|
* \return next box that current margin collapses to, or NULL if none.
|
|
*/
|
|
static struct box*
|
|
layout_next_margin_block(const css_unit_ctx *unit_len_ctx,
|
|
struct box *box,
|
|
struct box *block,
|
|
int viewport_height,
|
|
int *max_pos_margin,
|
|
int *max_neg_margin)
|
|
{
|
|
assert(block != NULL);
|
|
|
|
while (box != NULL) {
|
|
|
|
if (box->type == BOX_INLINE_CONTAINER || (box->style &&
|
|
(css_computed_position(box->style) !=
|
|
CSS_POSITION_ABSOLUTE &&
|
|
css_computed_position(box->style) !=
|
|
CSS_POSITION_FIXED))) {
|
|
/* Not positioned */
|
|
|
|
/* Get margins */
|
|
if (box->style) {
|
|
layout_find_dimensions(unit_len_ctx,
|
|
box->parent->width,
|
|
viewport_height, box,
|
|
box->style,
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, box->margin,
|
|
box->padding, box->border);
|
|
|
|
/* Apply top margin */
|
|
if (*max_pos_margin < box->margin[TOP])
|
|
*max_pos_margin = box->margin[TOP];
|
|
else if (*max_neg_margin < -box->margin[TOP])
|
|
*max_neg_margin = -box->margin[TOP];
|
|
}
|
|
|
|
/* Check whether box is the box current margin collapses
|
|
* to */
|
|
if (box->flags & MAKE_HEIGHT ||
|
|
box->border[TOP].width ||
|
|
box->padding[TOP] ||
|
|
(box->style &&
|
|
css_computed_overflow_y(box->style) !=
|
|
CSS_OVERFLOW_VISIBLE) ||
|
|
(box->type == BOX_INLINE_CONTAINER &&
|
|
!box_is_first_child(box))) {
|
|
/* Collapse to this box; return it */
|
|
return box;
|
|
}
|
|
}
|
|
|
|
|
|
/* Find next box */
|
|
if (box->type == BOX_BLOCK && !box->object && box->children &&
|
|
box->style &&
|
|
css_computed_overflow_y(box->style) ==
|
|
CSS_OVERFLOW_VISIBLE) {
|
|
/* Down into children. */
|
|
box = box->children;
|
|
} else {
|
|
if (!box->next) {
|
|
/* No more siblings:
|
|
* Go up to first ancestor with a sibling. */
|
|
do {
|
|
/* Apply bottom margin */
|
|
if (*max_pos_margin <
|
|
box->margin[BOTTOM])
|
|
*max_pos_margin =
|
|
box->margin[BOTTOM];
|
|
else if (*max_neg_margin <
|
|
-box->margin[BOTTOM])
|
|
*max_neg_margin =
|
|
-box->margin[BOTTOM];
|
|
|
|
box = box->parent;
|
|
} while (box != block && !box->next);
|
|
|
|
if (box == block) {
|
|
/* Margins don't collapse with stuff
|
|
* outside the block formatting context
|
|
*/
|
|
return block;
|
|
}
|
|
}
|
|
|
|
/* Apply bottom margin */
|
|
if (*max_pos_margin < box->margin[BOTTOM])
|
|
*max_pos_margin = box->margin[BOTTOM];
|
|
else if (*max_neg_margin < -box->margin[BOTTOM])
|
|
*max_neg_margin = -box->margin[BOTTOM];
|
|
|
|
/* To next sibling. */
|
|
box = box->next;
|
|
|
|
/* Get margins */
|
|
if (box->style) {
|
|
layout_find_dimensions(unit_len_ctx,
|
|
box->parent->width,
|
|
viewport_height, box,
|
|
box->style,
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, box->margin,
|
|
box->padding, box->border);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Manipulate a block's [RB]padding/height/width to accommodate scrollbars
|
|
*
|
|
* \param box Box to apply scrollbar space too. Must be BOX_BLOCK.
|
|
* \param which Which scrollbar to make space for. Must be RIGHT or BOTTOM.
|
|
*/
|
|
static void layout_block_add_scrollbar(struct box *box, int which)
|
|
{
|
|
enum css_overflow_e overflow_x, overflow_y;
|
|
|
|
assert(box->type == BOX_BLOCK && (which == RIGHT || which == BOTTOM));
|
|
|
|
if (box->style == NULL)
|
|
return;
|
|
|
|
overflow_x = css_computed_overflow_x(box->style);
|
|
overflow_y = css_computed_overflow_y(box->style);
|
|
|
|
if (which == BOTTOM &&
|
|
(overflow_x == CSS_OVERFLOW_SCROLL ||
|
|
overflow_x == CSS_OVERFLOW_AUTO ||
|
|
(box->object &&
|
|
content_get_type(box->object) == CONTENT_HTML))) {
|
|
/* make space for scrollbar, unless height is AUTO */
|
|
if (box->height != AUTO &&
|
|
(overflow_x == CSS_OVERFLOW_SCROLL ||
|
|
box_hscrollbar_present(box))) {
|
|
box->padding[BOTTOM] += SCROLLBAR_WIDTH;
|
|
}
|
|
|
|
} else if (which == RIGHT &&
|
|
(overflow_y == CSS_OVERFLOW_SCROLL ||
|
|
overflow_y == CSS_OVERFLOW_AUTO ||
|
|
(box->object &&
|
|
content_get_type(box->object) == CONTENT_HTML))) {
|
|
/* make space for scrollbars, unless width is AUTO */
|
|
enum css_height_e htype;
|
|
css_fixed height = 0;
|
|
css_unit hunit = CSS_UNIT_PX;
|
|
htype = css_computed_height(box->style, &height, &hunit);
|
|
|
|
if (which == RIGHT && box->width != AUTO &&
|
|
htype == CSS_HEIGHT_SET &&
|
|
(overflow_y == CSS_OVERFLOW_SCROLL ||
|
|
box_vscrollbar_present(box))) {
|
|
box->width -= SCROLLBAR_WIDTH;
|
|
box->padding[RIGHT] += SCROLLBAR_WIDTH;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Layout a block which contains an object.
|
|
*
|
|
* \param block box of type BLOCK, INLINE_BLOCK, TABLE, or TABLE_CELL
|
|
* \return true on success, false on memory exhaustion
|
|
*/
|
|
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);
|
|
|
|
NSLOG(layout, DEBUG, "block %p, object %p, width %i", block,
|
|
hlcache_handle_get_url(block->object), block->width);
|
|
|
|
if (content_can_reformat(block->object)) {
|
|
content_reformat(block->object, false, block->width, 1);
|
|
} else {
|
|
/* Non-HTML objects */
|
|
/* this case handled already in
|
|
* layout_block_find_dimensions() */
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Layout lines of text or inline boxes with floats.
|
|
*
|
|
* \param box inline container box
|
|
* \param width horizontal space available
|
|
* \param cont ancestor box which defines horizontal space, for floats
|
|
* \param cx box position relative to cont
|
|
* \param cy box position relative to cont
|
|
* \param content memory pool for any new boxes
|
|
* \return true on success, false on memory exhaustion
|
|
*/
|
|
static bool
|
|
layout_inline_container(struct box *inline_container,
|
|
int width,
|
|
struct box *cont,
|
|
int cx,
|
|
int cy,
|
|
html_content *content)
|
|
{
|
|
bool first_line = true;
|
|
bool has_text_children;
|
|
struct box *c, *next;
|
|
int y = 0;
|
|
int curwidth,maxwidth = width;
|
|
|
|
assert(inline_container->type == BOX_INLINE_CONTAINER);
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"inline_container %p, width %i, cont %p, cx %i, cy %i",
|
|
inline_container,
|
|
width,
|
|
cont,
|
|
cx,
|
|
cy);
|
|
|
|
|
|
has_text_children = false;
|
|
for (c = inline_container->children; c; c = c->next) {
|
|
bool is_pre = false;
|
|
|
|
if (c->style) {
|
|
enum css_white_space_e whitespace;
|
|
|
|
whitespace = css_computed_white_space(c->style);
|
|
|
|
is_pre = (whitespace == CSS_WHITE_SPACE_PRE ||
|
|
whitespace == CSS_WHITE_SPACE_PRE_LINE ||
|
|
whitespace == CSS_WHITE_SPACE_PRE_WRAP);
|
|
}
|
|
|
|
if ((lh__box_is_object(c) == false &&
|
|
c->text && (c->length || is_pre)) ||
|
|
c->type == BOX_BR)
|
|
has_text_children = true;
|
|
}
|
|
|
|
/** \todo fix wrapping so that a box with horizontal scrollbar will
|
|
* shrink back to 'width' if no word is wider than 'width' (Or just set
|
|
* curwidth = width and have the multiword lines wrap to the min width)
|
|
*/
|
|
for (c = inline_container->children; c; ) {
|
|
|
|
NSLOG(layout, DEBUG, "c %p", c);
|
|
|
|
curwidth = inline_container->width;
|
|
if (!layout_line(c, &curwidth, &y, cx, cy + y, cont, first_line,
|
|
has_text_children, content, &next))
|
|
return false;
|
|
maxwidth = max(maxwidth,curwidth);
|
|
c = next;
|
|
first_line = false;
|
|
}
|
|
|
|
inline_container->width = maxwidth;
|
|
inline_container->height = y;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Documented in layout_block.h */
|
|
bool layout_block_context(
|
|
struct box *block,
|
|
int viewport_height,
|
|
html_content *content)
|
|
{
|
|
struct box *box;
|
|
int cx, cy; /**< current coordinates */
|
|
int max_pos_margin = 0;
|
|
int max_neg_margin = 0;
|
|
int y = 0;
|
|
int lm, rm;
|
|
struct box *margin_collapse = NULL;
|
|
bool in_margin = false;
|
|
css_fixed gadget_size;
|
|
css_unit gadget_unit; /* Checkbox / radio buttons */
|
|
|
|
assert(block->type == BOX_BLOCK ||
|
|
block->type == BOX_INLINE_BLOCK ||
|
|
block->type == BOX_TABLE_CELL ||
|
|
block->type == BOX_FLEX ||
|
|
block->type == BOX_INLINE_FLEX);
|
|
assert(block->width != UNKNOWN_WIDTH);
|
|
assert(block->width != AUTO);
|
|
|
|
block->float_children = NULL;
|
|
block->cached_place_below_level = 0;
|
|
block->clear_level = 0;
|
|
|
|
/* special case if the block contains an object */
|
|
if (block->object) {
|
|
int temp_width = block->width;
|
|
if (!layout_block_object(block))
|
|
return false;
|
|
layout_get_object_dimensions(block, &temp_width,
|
|
&block->height, INT_MIN, INT_MAX,
|
|
INT_MIN, INT_MAX);
|
|
return true;
|
|
} else if (block->flags & REPLACE_DIM) {
|
|
return true;
|
|
}
|
|
|
|
/* special case if the block contains an radio button or checkbox */
|
|
if (block->gadget && (block->gadget->type == GADGET_RADIO ||
|
|
block->gadget->type == GADGET_CHECKBOX)) {
|
|
/* form checkbox or radio button
|
|
* if width or height is AUTO, set it to 1em */
|
|
gadget_unit = CSS_UNIT_EM;
|
|
gadget_size = INTTOFIX(1);
|
|
if (block->height == AUTO)
|
|
block->height = FIXTOINT(css_unit_len2device_px(
|
|
block->style,
|
|
&content->unit_len_ctx,
|
|
gadget_size, gadget_unit));
|
|
}
|
|
|
|
box = block->children;
|
|
/* set current coordinates to top-left of the block */
|
|
cx = 0;
|
|
y = cy = block->padding[TOP];
|
|
if (box)
|
|
box->y = block->padding[TOP];
|
|
|
|
/* Step through the descendants of the block in depth-first order, but
|
|
* not into the children of boxes which aren't blocks. For example, if
|
|
* the tree passed to this function looks like this (box->type shown):
|
|
*
|
|
* block -> BOX_BLOCK
|
|
* BOX_BLOCK * (1)
|
|
* BOX_INLINE_CONTAINER * (2)
|
|
* BOX_INLINE
|
|
* BOX_TEXT
|
|
* ...
|
|
* BOX_BLOCK * (3)
|
|
* BOX_TABLE * (4)
|
|
* BOX_TABLE_ROW
|
|
* BOX_TABLE_CELL
|
|
* ...
|
|
* BOX_TABLE_CELL
|
|
* ...
|
|
* BOX_BLOCK * (5)
|
|
* BOX_INLINE_CONTAINER * (6)
|
|
* BOX_TEXT
|
|
* ...
|
|
* then the while loop will visit each box marked with *, setting box
|
|
* to each in the order shown. */
|
|
while (box) {
|
|
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_FLEX ||
|
|
box->type == BOX_TABLE ||
|
|
box->type == BOX_INLINE_CONTAINER);
|
|
|
|
/* Tables are laid out before being positioned, because the
|
|
* position depends on the width which is calculated in
|
|
* table layout. Blocks and inline containers are positioned
|
|
* before being laid out, because width is not dependent on
|
|
* content, and the position is required during layout for
|
|
* correct handling of floats.
|
|
*/
|
|
|
|
if (box->style &&
|
|
(css_computed_position(box->style) ==
|
|
CSS_POSITION_ABSOLUTE ||
|
|
css_computed_position(box->style) ==
|
|
CSS_POSITION_FIXED)) {
|
|
box->x = box->parent->padding[LEFT];
|
|
/* absolute positioned; this element will establish
|
|
* its own block context when it gets laid out later,
|
|
* so no need to look at its children now. */
|
|
goto advance_to_next_box;
|
|
}
|
|
|
|
/* If we don't know which box the current margin collapses
|
|
* through to, find out. Update the pos/neg margin values. */
|
|
if (margin_collapse == NULL) {
|
|
margin_collapse = layout_next_margin_block(
|
|
&content->unit_len_ctx, box, block,
|
|
viewport_height,
|
|
&max_pos_margin, &max_neg_margin);
|
|
/* We have a margin that has not yet been applied. */
|
|
in_margin = true;
|
|
}
|
|
|
|
/* Clearance. */
|
|
y = 0;
|
|
if (box->style && css_computed_clear(box->style) !=
|
|
CSS_CLEAR_NONE)
|
|
y = layout_clear(block->float_children,
|
|
css_computed_clear(box->style));
|
|
|
|
/* Find box's overflow properties */
|
|
if (box->style) {
|
|
overflow_x = css_computed_overflow_x(box->style);
|
|
overflow_y = css_computed_overflow_y(box->style);
|
|
}
|
|
|
|
/* Blocks establishing a block formatting context get minimum
|
|
* left and right margins to avoid any floats. */
|
|
lm = rm = 0;
|
|
|
|
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 ||
|
|
overflow_y != CSS_OVERFLOW_VISIBLE)) {
|
|
/* box establishes new block formatting context
|
|
* so available width may be diminished due to
|
|
* floats. */
|
|
int x0, x1, top;
|
|
struct box *left, *right;
|
|
top = cy + max_pos_margin - max_neg_margin;
|
|
top = (top > y) ? top : y;
|
|
x0 = cx;
|
|
x1 = cx + box->parent->width -
|
|
box->parent->padding[LEFT] -
|
|
box->parent->padding[RIGHT];
|
|
find_sides(block->float_children, top, top,
|
|
&x0, &x1, &left, &right);
|
|
/* calculate min required left & right margins
|
|
* needed to avoid floats */
|
|
lm = x0 - cx;
|
|
rm = cx + box->parent->width -
|
|
box->parent->padding[LEFT] -
|
|
box->parent->padding[RIGHT] -
|
|
x1;
|
|
}
|
|
layout_block_find_dimensions(&content->unit_len_ctx,
|
|
box->parent->width,
|
|
viewport_height, lm, rm, box);
|
|
if (box->type == BOX_BLOCK && !(box->flags & IFRAME)) {
|
|
layout_block_add_scrollbar(box, RIGHT);
|
|
layout_block_add_scrollbar(box, BOTTOM);
|
|
}
|
|
} else if (box->type == BOX_TABLE) {
|
|
if (box->style != NULL) {
|
|
enum css_width_e wtype;
|
|
css_fixed width = 0;
|
|
css_unit unit = CSS_UNIT_PX;
|
|
|
|
wtype = css_computed_width(box->style, &width,
|
|
&unit);
|
|
|
|
if (wtype == CSS_WIDTH_AUTO) {
|
|
/* max available width may be
|
|
* diminished due to floats. */
|
|
int x0, x1, top;
|
|
struct box *left, *right;
|
|
top = cy + max_pos_margin -
|
|
max_neg_margin;
|
|
top = (top > y) ? top : y;
|
|
x0 = cx;
|
|
x1 = cx + box->parent->width -
|
|
box->parent->padding[LEFT] -
|
|
box->parent->padding[RIGHT];
|
|
find_sides(block->float_children,
|
|
top, top, &x0, &x1,
|
|
&left, &right);
|
|
/* calculate min required left & right
|
|
* margins needed to avoid floats */
|
|
lm = x0 - cx;
|
|
rm = cx + box->parent->width -
|
|
box->parent->padding[LEFT] -
|
|
box->parent->padding[RIGHT] -
|
|
x1;
|
|
}
|
|
}
|
|
if (!layout_table(box, box->parent->width - lm - rm,
|
|
content))
|
|
return false;
|
|
layout_solve_width(box, box->parent->width, box->width,
|
|
lm, rm, -1, -1);
|
|
}
|
|
|
|
/* Position box: horizontal. */
|
|
box->x = box->parent->padding[LEFT] + box->margin[LEFT] +
|
|
box->border[LEFT].width;
|
|
cx += box->x;
|
|
|
|
/* Position box: vertical. */
|
|
if (box->border[TOP].width) {
|
|
box->y += box->border[TOP].width;
|
|
cy += box->border[TOP].width;
|
|
}
|
|
|
|
/* 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)) ||
|
|
margin_collapse == box) &&
|
|
in_margin == true) {
|
|
/* Margin goes above this box. */
|
|
cy += max_pos_margin - max_neg_margin;
|
|
box->y += max_pos_margin - max_neg_margin;
|
|
|
|
/* Current margin has been applied. */
|
|
in_margin = false;
|
|
max_pos_margin = max_neg_margin = 0;
|
|
}
|
|
|
|
/* Handle clearance */
|
|
if (box->type != BOX_INLINE_CONTAINER &&
|
|
(y > 0) && (cy < y)) {
|
|
/* box clears something*/
|
|
box->y += y - cy;
|
|
cy = y;
|
|
}
|
|
|
|
/* Unless the box has an overflow style of visible, the box
|
|
* establishes a new block context. */
|
|
if (box->type == BOX_FLEX ||
|
|
(box->type == BOX_BLOCK && box->style &&
|
|
(overflow_x != CSS_OVERFLOW_VISIBLE ||
|
|
overflow_y != CSS_OVERFLOW_VISIBLE))) {
|
|
|
|
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];
|
|
|
|
if (box->height == AUTO) {
|
|
box->height = 0;
|
|
layout_block_add_scrollbar(box, BOTTOM);
|
|
}
|
|
|
|
cx -= box->x;
|
|
cy += box->height + box->padding[BOTTOM] +
|
|
box->border[BOTTOM].width;
|
|
y = box->y + box->padding[TOP] + box->height +
|
|
box->padding[BOTTOM] +
|
|
box->border[BOTTOM].width;
|
|
|
|
/* Skip children, because they are done in the new
|
|
* block context */
|
|
goto advance_to_next_box;
|
|
}
|
|
|
|
NSLOG(layout, DEBUG, "box %p, cx %i, cy %i, width %i",
|
|
box, cx, cy, box->width);
|
|
|
|
/* Layout (except tables). */
|
|
if (box->object) {
|
|
if (!layout_block_object(box))
|
|
return false;
|
|
|
|
} else if (box->type == BOX_INLINE_CONTAINER) {
|
|
box->width = box->parent->width;
|
|
if (!layout_inline_container(box, box->width, block,
|
|
cx, cy, content))
|
|
return false;
|
|
|
|
} else if (box->type == BOX_TABLE) {
|
|
/* Move down to avoid floats if necessary. */
|
|
int x0, x1;
|
|
struct box *left, *right;
|
|
y = cy;
|
|
while (1) {
|
|
enum css_width_e wtype;
|
|
css_fixed width = 0;
|
|
css_unit unit = CSS_UNIT_PX;
|
|
|
|
wtype = css_computed_width(box->style,
|
|
&width, &unit);
|
|
|
|
x0 = cx;
|
|
x1 = cx + box->parent->width;
|
|
find_sides(block->float_children, y,
|
|
y + box->height,
|
|
&x0, &x1, &left, &right);
|
|
if (wtype == CSS_WIDTH_AUTO)
|
|
break;
|
|
if (box->width <= x1 - x0)
|
|
break;
|
|
if (!left && !right)
|
|
break;
|
|
else if (!left)
|
|
y = right->y + right->height + 1;
|
|
else if (!right)
|
|
y = left->y + left->height + 1;
|
|
else if (left->y + left->height <
|
|
right->y + right->height)
|
|
y = left->y + left->height + 1;
|
|
else
|
|
y = right->y + right->height + 1;
|
|
}
|
|
box->x += x0 - cx;
|
|
cx = x0;
|
|
box->y += y - cy;
|
|
cy = y;
|
|
}
|
|
|
|
/* Advance to next box. */
|
|
if (box->type == BOX_BLOCK && !box->object && !(box->iframe) &&
|
|
box->children) {
|
|
/* Down into children. */
|
|
|
|
if (box == margin_collapse) {
|
|
/* Current margin collapsed though to this box.
|
|
* Unset margin_collapse. */
|
|
margin_collapse = NULL;
|
|
}
|
|
|
|
y = box->padding[TOP];
|
|
box = box->children;
|
|
box->y = y;
|
|
cy += y;
|
|
continue;
|
|
} else if (box->type == BOX_BLOCK || box->object ||
|
|
box->flags & IFRAME)
|
|
cy += box->padding[TOP];
|
|
|
|
if (box->type == BOX_BLOCK && box->height == AUTO) {
|
|
box->height = 0;
|
|
layout_block_add_scrollbar(box, BOTTOM);
|
|
}
|
|
|
|
cy += box->height + box->padding[BOTTOM] +
|
|
box->border[BOTTOM].width;
|
|
cx -= box->x;
|
|
y = box->y + box->padding[TOP] + box->height +
|
|
box->padding[BOTTOM] +
|
|
box->border[BOTTOM].width;
|
|
|
|
advance_to_next_box:
|
|
if (!box->next) {
|
|
/* No more siblings:
|
|
* up to first ancestor with a sibling. */
|
|
|
|
do {
|
|
if (box == margin_collapse) {
|
|
/* Current margin collapsed though to
|
|
* this box. Unset margin_collapse. */
|
|
margin_collapse = NULL;
|
|
}
|
|
|
|
/* Apply bottom margin */
|
|
if (max_pos_margin < box->margin[BOTTOM])
|
|
max_pos_margin = box->margin[BOTTOM];
|
|
else if (max_neg_margin < -box->margin[BOTTOM])
|
|
max_neg_margin = -box->margin[BOTTOM];
|
|
|
|
box = box->parent;
|
|
if (box == block)
|
|
break;
|
|
|
|
/* Margin is invalidated if this is a box
|
|
* margins can't collapse through. */
|
|
if (box->type == BOX_BLOCK &&
|
|
box->flags & MAKE_HEIGHT) {
|
|
margin_collapse = NULL;
|
|
in_margin = false;
|
|
max_pos_margin = max_neg_margin = 0;
|
|
}
|
|
|
|
if (box->height == AUTO) {
|
|
box->height = y - box->padding[TOP];
|
|
|
|
if (box->type == BOX_BLOCK)
|
|
layout_block_add_scrollbar(box,
|
|
BOTTOM);
|
|
} else
|
|
cy += box->height -
|
|
(y - box->padding[TOP]);
|
|
|
|
/* Apply any min-height and max-height to
|
|
* boxes in normal flow */
|
|
if (box->style &&
|
|
css_computed_position(box->style) !=
|
|
CSS_POSITION_ABSOLUTE &&
|
|
layout_apply_minmax_height(
|
|
&content->unit_len_ctx,
|
|
box, NULL)) {
|
|
/* Height altered */
|
|
/* Set current cy */
|
|
cy += box->height -
|
|
(y - box->padding[TOP]);
|
|
}
|
|
|
|
cy += box->padding[BOTTOM] +
|
|
box->border[BOTTOM].width;
|
|
cx -= box->x;
|
|
y = box->y + box->padding[TOP] + box->height +
|
|
box->padding[BOTTOM] +
|
|
box->border[BOTTOM].width;
|
|
|
|
} while (box->next == NULL);
|
|
if (box == block)
|
|
break;
|
|
}
|
|
|
|
/* To next sibling. */
|
|
|
|
if (box == margin_collapse) {
|
|
/* Current margin collapsed though to this box.
|
|
* Unset margin_collapse. */
|
|
margin_collapse = NULL;
|
|
}
|
|
|
|
if (max_pos_margin < box->margin[BOTTOM])
|
|
max_pos_margin = box->margin[BOTTOM];
|
|
else if (max_neg_margin < -box->margin[BOTTOM])
|
|
max_neg_margin = -box->margin[BOTTOM];
|
|
|
|
box = box->next;
|
|
box->y = y;
|
|
}
|
|
|
|
/* Account for bottom margin of last contained block */
|
|
cy += max_pos_margin - max_neg_margin;
|
|
|
|
/* Increase height to contain any floats inside (CSS 2.1 10.6.7). */
|
|
for (box = block->float_children; box; box = box->next_float) {
|
|
y = box->y + box->height + box->padding[BOTTOM] +
|
|
box->border[BOTTOM].width + box->margin[BOTTOM];
|
|
if (cy < y)
|
|
cy = y;
|
|
}
|
|
|
|
if (block->height == AUTO) {
|
|
block->height = cy - block->padding[TOP];
|
|
if (block->type == BOX_BLOCK)
|
|
layout_block_add_scrollbar(block, BOTTOM);
|
|
}
|
|
|
|
if (block->style && css_computed_position(block->style) !=
|
|
CSS_POSITION_ABSOLUTE) {
|
|
/* Block is in normal flow */
|
|
layout_apply_minmax_height(&content->unit_len_ctx, block, NULL);
|
|
}
|
|
|
|
if (block->gadget &&
|
|
(block->gadget->type == GADGET_TEXTAREA ||
|
|
block->gadget->type == GADGET_PASSWORD ||
|
|
block->gadget->type == GADGET_TEXTBOX)) {
|
|
plot_font_style_t fstyle;
|
|
int ta_width = block->padding[LEFT] + block->width +
|
|
block->padding[RIGHT];
|
|
int ta_height = block->padding[TOP] + block->height +
|
|
block->padding[BOTTOM];
|
|
font_plot_style_from_css(&content->unit_len_ctx,
|
|
block->style, &fstyle);
|
|
fstyle.background = NS_TRANSPARENT;
|
|
textarea_set_layout(block->gadget->data.text.ta,
|
|
&fstyle, ta_width, ta_height,
|
|
block->padding[TOP], block->padding[RIGHT],
|
|
block->padding[BOTTOM], block->padding[LEFT]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Compute dimensions of box, margins, paddings, and borders for a block-level
|
|
* element.
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param available_width Max width available in pixels
|
|
* \param viewport_height Height of viewport in pixels or -ve if unknown
|
|
* \param lm min left margin required to avoid floats in px.
|
|
* zero if not applicable
|
|
* \param rm min right margin required to avoid floats in px.
|
|
* zero if not applicable
|
|
* \param box box to find dimensions of. updated with new width,
|
|
* height, margins, borders and paddings
|
|
*
|
|
* See CSS 2.1 10.3.3, 10.3.4, 10.6.2, and 10.6.3.
|
|
*/
|
|
/* Documented in layout_block.h */
|
|
void
|
|
layout_block_find_dimensions(const css_unit_ctx *unit_len_ctx,
|
|
int available_width,
|
|
int viewport_height,
|
|
int lm,
|
|
int rm,
|
|
struct box *box)
|
|
{
|
|
int width, max_width, min_width;
|
|
int height, max_height, min_height;
|
|
int *margin = box->margin;
|
|
int *padding = box->padding;
|
|
struct box_border *border = box->border;
|
|
const css_computed_style *style = box->style;
|
|
|
|
layout_find_dimensions(unit_len_ctx,
|
|
available_width,
|
|
viewport_height,
|
|
box,
|
|
style,
|
|
&width,
|
|
&height,
|
|
&max_width,
|
|
&min_width,
|
|
&max_height,
|
|
&min_height,
|
|
margin,
|
|
padding,
|
|
border);
|
|
|
|
if (box->object && !(box->flags & REPLACE_DIM) &&
|
|
content_get_type(box->object) != CONTENT_HTML) {
|
|
/* block-level replaced element, see 10.3.4 and 10.6.2 */
|
|
layout_get_object_dimensions(box,
|
|
&width,
|
|
&height,
|
|
min_width,
|
|
max_width,
|
|
min_height,
|
|
max_height);
|
|
}
|
|
|
|
box->width = layout_solve_width(box,
|
|
available_width,
|
|
width,
|
|
lm,
|
|
rm,
|
|
max_width,
|
|
min_width);
|
|
box->height = height;
|
|
|
|
if (margin[TOP] == AUTO)
|
|
margin[TOP] = 0;
|
|
if (margin[BOTTOM] == AUTO)
|
|
margin[BOTTOM] = 0;
|
|
}
|