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.
1176 lines
32 KiB
1176 lines
32 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 lines
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "utils/log.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/talloc.h"
|
|
#include "utils/nsoption.h"
|
|
#include "netsurf/inttypes.h"
|
|
#include "netsurf/types.h"
|
|
#include "netsurf/content.h"
|
|
#include "netsurf/mouse.h"
|
|
#include "netsurf/plot_style.h"
|
|
#include "netsurf/layout.h"
|
|
#include "content/content.h"
|
|
#include "desktop/scrollbar.h"
|
|
|
|
#include "html/private.h"
|
|
#include "html/box.h"
|
|
#include "html/font.h"
|
|
#include "html/form_internal.h"
|
|
|
|
#include "html/layout/internal.h"
|
|
#include "html/layout/block.h"
|
|
#include "html/layout/table.h"
|
|
#include "html/layout/flex.h"
|
|
#include "html/layout/line.h"
|
|
|
|
/**
|
|
* Compute dimensions of box, margins, paddings, and borders for a floating
|
|
* element using shrink-to-fit. Also used for inline-blocks.
|
|
*
|
|
* \param unit_len_ctx CSS length conversion context for document.
|
|
* \param available_width Max width available in pixels
|
|
* \param style Box's style
|
|
* \param box Box for which to find dimensions
|
|
* Box margins, borders, paddings, width and
|
|
* height are updated.
|
|
*/
|
|
static void
|
|
layout_float_find_dimensions(
|
|
const css_unit_ctx *unit_len_ctx,
|
|
int available_width,
|
|
const css_computed_style *style,
|
|
struct box *box)
|
|
{
|
|
int width, height, max_width, min_width, max_height, min_height;
|
|
int *margin = box->margin;
|
|
int *padding = box->padding;
|
|
struct box_border *border = box->border;
|
|
enum css_overflow_e overflow_x = css_computed_overflow_x(style);
|
|
enum css_overflow_e overflow_y = css_computed_overflow_y(style);
|
|
int scrollbar_width_x =
|
|
(overflow_x == CSS_OVERFLOW_SCROLL ||
|
|
overflow_x == CSS_OVERFLOW_AUTO) ?
|
|
SCROLLBAR_WIDTH : 0;
|
|
int scrollbar_width_y =
|
|
(overflow_y == CSS_OVERFLOW_SCROLL ||
|
|
overflow_y == CSS_OVERFLOW_AUTO) ?
|
|
SCROLLBAR_WIDTH : 0;
|
|
|
|
layout_find_dimensions(unit_len_ctx, available_width, -1, box, style,
|
|
&width, &height, &max_width, &min_width,
|
|
&max_height, &min_height, margin, padding, border);
|
|
|
|
if (margin[LEFT] == AUTO)
|
|
margin[LEFT] = 0;
|
|
if (margin[RIGHT] == AUTO)
|
|
margin[RIGHT] = 0;
|
|
|
|
if (box->gadget == NULL) {
|
|
padding[RIGHT] += scrollbar_width_y;
|
|
padding[BOTTOM] += scrollbar_width_x;
|
|
}
|
|
|
|
if (box->object && !(box->flags & REPLACE_DIM) &&
|
|
content_get_type(box->object) != CONTENT_HTML) {
|
|
/* Floating replaced element, with intrinsic width or height.
|
|
* See 10.3.6 and 10.6.2 */
|
|
layout_get_object_dimensions(box, &width, &height,
|
|
min_width, max_width, min_height, max_height);
|
|
} else if (box->gadget && (box->gadget->type == GADGET_TEXTBOX ||
|
|
box->gadget->type == GADGET_PASSWORD ||
|
|
box->gadget->type == GADGET_FILE ||
|
|
box->gadget->type == GADGET_TEXTAREA)) {
|
|
css_fixed size = 0;
|
|
css_unit unit = CSS_UNIT_EM;
|
|
|
|
/* Give sensible dimensions to gadgets, with auto width/height,
|
|
* that don't shrink to fit contained text. */
|
|
assert(box->style);
|
|
|
|
if (box->gadget->type == GADGET_TEXTBOX ||
|
|
box->gadget->type == GADGET_PASSWORD ||
|
|
box->gadget->type == GADGET_FILE) {
|
|
if (width == AUTO) {
|
|
size = INTTOFIX(10);
|
|
width = FIXTOINT(css_unit_len2device_px(
|
|
box->style, unit_len_ctx,
|
|
size, unit));
|
|
}
|
|
if (box->gadget->type == GADGET_FILE &&
|
|
height == AUTO) {
|
|
size = FLTTOFIX(1.5);
|
|
height = FIXTOINT(css_unit_len2device_px(
|
|
box->style, unit_len_ctx,
|
|
size, unit));
|
|
}
|
|
}
|
|
if (box->gadget->type == GADGET_TEXTAREA) {
|
|
if (width == AUTO) {
|
|
size = INTTOFIX(10);
|
|
width = FIXTOINT(css_unit_len2device_px(
|
|
box->style, unit_len_ctx,
|
|
size, unit));
|
|
}
|
|
if (height == AUTO) {
|
|
size = INTTOFIX(4);
|
|
height = FIXTOINT(css_unit_len2device_px(
|
|
box->style, unit_len_ctx,
|
|
size, unit));
|
|
}
|
|
}
|
|
} else if (width == AUTO) {
|
|
/* CSS 2.1 section 10.3.5 */
|
|
width = min(max(box->min_width, available_width),
|
|
box->max_width);
|
|
|
|
/* width includes margin, borders and padding */
|
|
if (width == available_width) {
|
|
width -= box->margin[LEFT] + box->border[LEFT].width +
|
|
box->padding[LEFT] +
|
|
box->padding[RIGHT] +
|
|
box->border[RIGHT].width +
|
|
box->margin[RIGHT];
|
|
} else {
|
|
/* width was obtained from a min_width or max_width
|
|
* value, so need to use the same method for calculating
|
|
* mbp as was used in layout_minmax_block() */
|
|
int fixed = 0;
|
|
float frac = 0;
|
|
calculate_mbp_width(unit_len_ctx, box->style, LEFT,
|
|
true, true, true, &fixed, &frac);
|
|
calculate_mbp_width(unit_len_ctx, box->style, RIGHT,
|
|
true, true, true, &fixed, &frac);
|
|
if (fixed < 0)
|
|
fixed = 0;
|
|
|
|
width -= fixed;
|
|
}
|
|
|
|
if (max_width >= 0 && width > max_width) width = max_width;
|
|
if (min_width > 0 && width < min_width) width = min_width;
|
|
|
|
} else {
|
|
if (max_width >= 0 && width > max_width) width = max_width;
|
|
if (min_width > 0 && width < min_width) width = min_width;
|
|
width -= scrollbar_width_y;
|
|
}
|
|
|
|
box->width = width;
|
|
box->height = height;
|
|
|
|
if (margin[TOP] == AUTO)
|
|
margin[TOP] = 0;
|
|
if (margin[BOTTOM] == AUTO)
|
|
margin[BOTTOM] = 0;
|
|
}
|
|
|
|
/**
|
|
* Insert a float into a container.
|
|
*
|
|
* \param cont block formatting context block, used to contain float
|
|
* \param b box to add to float
|
|
*
|
|
* This sorts floats in order of descending bottom edges.
|
|
*/
|
|
static void add_float_to_container(struct box *cont, struct box *b)
|
|
{
|
|
struct box *box = cont->float_children;
|
|
int b_bottom = b->y + b->height;
|
|
|
|
assert(b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT);
|
|
|
|
if (box == NULL) {
|
|
/* No other float children */
|
|
b->next_float = NULL;
|
|
cont->float_children = b;
|
|
return;
|
|
} else if (b_bottom >= box->y + box->height) {
|
|
/* Goes at start of list */
|
|
b->next_float = cont->float_children;
|
|
cont->float_children = b;
|
|
} else {
|
|
struct box *prev = NULL;
|
|
while (box != NULL && b_bottom < box->y + box->height) {
|
|
prev = box;
|
|
box = box->next_float;
|
|
}
|
|
if (prev != NULL) {
|
|
b->next_float = prev->next_float;
|
|
prev->next_float = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Split a text box.
|
|
*
|
|
* \param content memory pool for any new boxes
|
|
* \param fstyle style for text in text box
|
|
* \param split_box box with text to split
|
|
* \param new_length new length for text in split_box, after splitting
|
|
* \param new_width new width for text in split_box, after splitting
|
|
* \return true on success, false on memory exhaustion
|
|
*
|
|
* A new box is created and inserted into the box tree after split_box,
|
|
* containing the text after new_length excluding the initial space character.
|
|
*/
|
|
static bool
|
|
layout_text_box_split(html_content *content,
|
|
plot_font_style_t *fstyle,
|
|
struct box *split_box,
|
|
size_t new_length,
|
|
int new_width)
|
|
{
|
|
int space_width = split_box->space;
|
|
struct box *c2;
|
|
const struct gui_layout_table *font_func = content->font_func;
|
|
bool space = (split_box->text[new_length] == ' ');
|
|
int used_length = new_length + (space ? 1 : 0);
|
|
|
|
if ((space && space_width == 0) || space_width == UNKNOWN_WIDTH) {
|
|
/* We're need to add a space, and we don't know how big
|
|
* it's to be, OR we have a space of unknown width anyway;
|
|
* Calculate space width */
|
|
font_func->width(fstyle, " ", 1, &space_width);
|
|
}
|
|
|
|
if (split_box->space == UNKNOWN_WIDTH)
|
|
split_box->space = space_width;
|
|
if (!space)
|
|
space_width = 0;
|
|
|
|
/* Create clone of split_box, c2 */
|
|
c2 = talloc_memdup(content->bctx, split_box, sizeof *c2);
|
|
if (!c2)
|
|
return false;
|
|
c2->flags |= CLONE;
|
|
|
|
/* Set remaining text in c2 */
|
|
c2->text += used_length;
|
|
|
|
/* Set c2 according to the remaining text */
|
|
c2->width -= new_width + space_width;
|
|
c2->flags &= ~MEASURED; /* width has been estimated */
|
|
c2->length = split_box->length - used_length;
|
|
|
|
/* Update split_box for its reduced text */
|
|
split_box->width = new_width;
|
|
split_box->flags |= MEASURED;
|
|
split_box->length = new_length;
|
|
split_box->space = space_width;
|
|
|
|
/* Insert c2 into box list */
|
|
c2->next = split_box->next;
|
|
split_box->next = c2;
|
|
c2->prev = split_box;
|
|
if (c2->next)
|
|
c2->next->prev = c2;
|
|
else
|
|
c2->parent->last = c2;
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"split_box %p len: %" PRIsizet " \"%.*s\"",
|
|
split_box,
|
|
split_box->length,
|
|
(int)split_box->length,
|
|
split_box->text);
|
|
NSLOG(layout, DEBUG,
|
|
" new_box %p len: %" PRIsizet " \"%.*s\"",
|
|
c2,
|
|
c2->length,
|
|
(int)c2->length,
|
|
c2->text);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Layout the contents of a float or inline block.
|
|
*
|
|
* \param b float or inline block box
|
|
* \param width available width
|
|
* \param content memory pool for any new boxes
|
|
* \return true on success, false on memory exhaustion
|
|
*/
|
|
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 ||
|
|
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 || 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)
|
|
b->margin[RIGHT] = 0;
|
|
if (b->margin[TOP] == AUTO)
|
|
b->margin[TOP] = 0;
|
|
if (b->margin[BOTTOM] == AUTO)
|
|
b->margin[BOTTOM] = 0;
|
|
} else {
|
|
return layout_block_context(b, -1, content);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Position a float in the first available space.
|
|
*
|
|
* \param c float box to position
|
|
* \param width available width
|
|
* \param cx x coordinate relative to cont to place float right of
|
|
* \param y y coordinate relative to cont to place float below
|
|
* \param cont ancestor box which defines horizontal space, for floats
|
|
*/
|
|
static void
|
|
place_float_below(struct box *c, int width, int cx, int y, struct box *cont)
|
|
{
|
|
int x0, x1, yy;
|
|
struct box *left;
|
|
struct box *right;
|
|
|
|
yy = y > cont->cached_place_below_level ?
|
|
y : cont->cached_place_below_level;
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"c %p, width %i, cx %i, y %i, cont %p", c,
|
|
width, cx, y, cont);
|
|
|
|
do {
|
|
y = yy;
|
|
x0 = cx;
|
|
x1 = cx + width;
|
|
find_sides(cont->float_children, y, y + c->height, &x0, &x1,
|
|
&left, &right);
|
|
if (left != 0 && right != 0) {
|
|
yy = (left->y + left->height <
|
|
right->y + right->height ?
|
|
left->y + left->height :
|
|
right->y + right->height);
|
|
} else if (left == 0 && right != 0) {
|
|
yy = right->y + right->height;
|
|
} else if (left != 0 && right == 0) {
|
|
yy = left->y + left->height;
|
|
}
|
|
} while ((left != 0 || right != 0) && (c->width > x1 - x0));
|
|
|
|
if (c->type == BOX_FLOAT_LEFT) {
|
|
c->x = x0;
|
|
} else {
|
|
c->x = x1 - c->width;
|
|
}
|
|
c->y = y;
|
|
cont->cached_place_below_level = y;
|
|
}
|
|
|
|
/**
|
|
* Calculate line height from a style.
|
|
*/
|
|
int layout_line_height(const css_unit_ctx *unit_len_ctx,
|
|
const css_computed_style *style)
|
|
{
|
|
enum css_line_height_e lhtype;
|
|
css_fixed lhvalue = 0;
|
|
css_unit lhunit = CSS_UNIT_PX;
|
|
css_fixed line_height;
|
|
|
|
assert(style);
|
|
|
|
lhtype = css_computed_line_height(style, &lhvalue, &lhunit);
|
|
if (lhtype == CSS_LINE_HEIGHT_NORMAL) {
|
|
/* Normal => use a constant of 1.3 * font-size */
|
|
lhvalue = FLTTOFIX(1.3);
|
|
lhtype = CSS_LINE_HEIGHT_NUMBER;
|
|
}
|
|
|
|
if (lhtype == CSS_LINE_HEIGHT_NUMBER ||
|
|
lhunit == CSS_UNIT_PCT) {
|
|
line_height = css_unit_len2device_px(style, unit_len_ctx,
|
|
lhvalue, CSS_UNIT_EM);
|
|
|
|
if (lhtype != CSS_LINE_HEIGHT_NUMBER)
|
|
line_height = FDIV(line_height, F_100);
|
|
} else {
|
|
assert(lhunit != CSS_UNIT_PCT);
|
|
|
|
line_height = css_unit_len2device_px(style, unit_len_ctx,
|
|
lhvalue, lhunit);
|
|
}
|
|
|
|
return FIXTOINT(line_height);
|
|
}
|
|
|
|
/**
|
|
* Position a line of boxes in inline formatting context.
|
|
*
|
|
* \param first box at start of line
|
|
* \param width available width on input, updated with actual width on output
|
|
* (may be incorrect if the line gets split?)
|
|
* \param y coordinate of top of line, updated on exit to bottom
|
|
* \param cx coordinate of left of line relative to cont
|
|
* \param cy coordinate of top of line relative to cont
|
|
* \param cont ancestor box which defines horizontal space, for floats
|
|
* \param indent apply any first-line indent
|
|
* \param has_text_children at least one TEXT in the inline_container
|
|
* \param next_box updated to first box for next line, or 0 at end
|
|
* \param content memory pool for any new boxes
|
|
* \return true on success, false on memory exhaustion
|
|
*/
|
|
bool
|
|
layout_line(struct box *first,
|
|
int *width,
|
|
int *y,
|
|
int cx,
|
|
int cy,
|
|
struct box *cont,
|
|
bool indent,
|
|
bool has_text_children,
|
|
html_content *content,
|
|
struct box **next_box)
|
|
{
|
|
int height, used_height;
|
|
int x0 = 0;
|
|
int x1 = *width;
|
|
int x, h, x_previous;
|
|
int fy = cy;
|
|
struct box *left;
|
|
struct box *right;
|
|
struct box *b;
|
|
struct box *split_box = 0;
|
|
struct box *d;
|
|
struct box *br_box = 0;
|
|
bool move_y = false;
|
|
bool place_below = false;
|
|
int space_before = 0, space_after = 0;
|
|
unsigned int inline_count = 0;
|
|
unsigned int i;
|
|
const struct gui_layout_table *font_func = content->font_func;
|
|
plot_font_style_t fstyle;
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"first %p, first->text '%.*s', width %i, y %i, cx %i, cy %i",
|
|
first,
|
|
(int)first->length,
|
|
first->text,
|
|
*width,
|
|
*y,
|
|
cx,
|
|
cy);
|
|
|
|
/* find sides at top of line */
|
|
x0 += cx;
|
|
x1 += cx;
|
|
find_sides(cont->float_children, cy, cy, &x0, &x1, &left, &right);
|
|
x0 -= cx;
|
|
x1 -= cx;
|
|
|
|
if (indent)
|
|
x0 += layout_text_indent(&content->unit_len_ctx,
|
|
first->parent->parent->style, *width);
|
|
|
|
if (x1 < x0)
|
|
x1 = x0;
|
|
|
|
/* get minimum line height from containing block.
|
|
* this is the line-height if there are text children and also in the
|
|
* case of an initially empty text input */
|
|
if (has_text_children || first->parent->parent->gadget)
|
|
used_height = height = layout_line_height(&content->unit_len_ctx,
|
|
first->parent->parent->style);
|
|
else
|
|
/* inline containers with no text are usually for layout and
|
|
* look better with no minimum line-height */
|
|
used_height = height = 0;
|
|
|
|
/* pass 1: find height of line assuming sides at top of line: loop
|
|
* body executed at least once
|
|
* keep in sync with the loop in layout_minmax_line() */
|
|
|
|
NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0);
|
|
|
|
|
|
for (x = 0, b = first; x <= x1 - x0 && b != 0; b = b->next) {
|
|
int min_width, max_width, min_height, max_height;
|
|
|
|
assert(lh__box_is_inline_content(b));
|
|
|
|
NSLOG(layout, DEBUG, "pass 1: b %p, x %i", b, x);
|
|
|
|
if (b->type == BOX_BR)
|
|
break;
|
|
|
|
if (lh__box_is_float_box(b))
|
|
continue;
|
|
if (b->type == BOX_INLINE_BLOCK &&
|
|
(css_computed_position(b->style) ==
|
|
CSS_POSITION_ABSOLUTE ||
|
|
css_computed_position(b->style) ==
|
|
CSS_POSITION_FIXED))
|
|
continue;
|
|
|
|
assert(b->style != NULL);
|
|
font_plot_style_from_css(&content->unit_len_ctx, b->style, &fstyle);
|
|
|
|
x += space_after;
|
|
|
|
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;
|
|
h = b->border[TOP].width + b->padding[TOP] + b->height +
|
|
b->padding[BOTTOM] +
|
|
b->border[BOTTOM].width;
|
|
if (height < h)
|
|
height = h;
|
|
x += b->margin[LEFT] + b->border[LEFT].width +
|
|
b->padding[LEFT] + b->width +
|
|
b->padding[RIGHT] +
|
|
b->border[RIGHT].width +
|
|
b->margin[RIGHT];
|
|
space_after = 0;
|
|
continue;
|
|
}
|
|
|
|
if (b->type == BOX_INLINE) {
|
|
/* calculate borders, margins, and padding */
|
|
layout_find_dimensions(&content->unit_len_ctx,
|
|
*width, -1, b, b->style, 0, 0, 0, 0,
|
|
0, 0, b->margin, b->padding, b->border);
|
|
for (i = 0; i != 4; i++)
|
|
if (b->margin[i] == AUTO)
|
|
b->margin[i] = 0;
|
|
x += b->margin[LEFT] + b->border[LEFT].width +
|
|
b->padding[LEFT];
|
|
if (b->inline_end) {
|
|
b->inline_end->margin[RIGHT] = b->margin[RIGHT];
|
|
b->inline_end->padding[RIGHT] =
|
|
b->padding[RIGHT];
|
|
b->inline_end->border[RIGHT] =
|
|
b->border[RIGHT];
|
|
} else {
|
|
x += b->padding[RIGHT] +
|
|
b->border[RIGHT].width +
|
|
b->margin[RIGHT];
|
|
}
|
|
} else if (b->type == BOX_INLINE_END) {
|
|
b->width = 0;
|
|
if (b->space == UNKNOWN_WIDTH) {
|
|
font_func->width(&fstyle, " ", 1, &b->space);
|
|
/** \todo handle errors */
|
|
}
|
|
space_after = b->space;
|
|
|
|
x += b->padding[RIGHT] + b->border[RIGHT].width +
|
|
b->margin[RIGHT];
|
|
continue;
|
|
}
|
|
|
|
if (lh__box_is_replace(b) == false) {
|
|
/* inline non-replaced, 10.3.1 and 10.6.1 */
|
|
b->height = layout_line_height(&content->unit_len_ctx,
|
|
b->style ? b->style :
|
|
b->parent->parent->style);
|
|
if (height < b->height)
|
|
height = b->height;
|
|
|
|
if (!b->text) {
|
|
b->width = 0;
|
|
space_after = 0;
|
|
continue;
|
|
}
|
|
|
|
if (b->width == UNKNOWN_WIDTH) {
|
|
/** \todo handle errors */
|
|
|
|
/* If it's a select element, we must use the
|
|
* width of the widest option text */
|
|
if (b->parent->parent->gadget &&
|
|
b->parent->parent->gadget->type
|
|
== GADGET_SELECT) {
|
|
int opt_maxwidth = 0;
|
|
struct form_option *o;
|
|
|
|
for (o = b->parent->parent->gadget->
|
|
data.select.items; o;
|
|
o = o->next) {
|
|
int opt_width;
|
|
font_func->width(&fstyle,
|
|
o->text,
|
|
strlen(o->text),
|
|
&opt_width);
|
|
|
|
if (opt_maxwidth < opt_width)
|
|
opt_maxwidth =opt_width;
|
|
}
|
|
b->width = opt_maxwidth;
|
|
if (nsoption_bool(core_select_menu))
|
|
b->width += SCROLLBAR_WIDTH;
|
|
} else {
|
|
font_func->width(&fstyle, b->text,
|
|
b->length, &b->width);
|
|
b->flags |= MEASURED;
|
|
}
|
|
}
|
|
|
|
/* If the current text has not been measured (i.e. its
|
|
* width was estimated after splitting), and it fits on
|
|
* the line, measure it properly, so next box is placed
|
|
* correctly. */
|
|
if (b->text && (x + b->width < x1 - x0) &&
|
|
!(b->flags & MEASURED) &&
|
|
b->next) {
|
|
font_func->width(&fstyle, b->text,
|
|
b->length, &b->width);
|
|
b->flags |= MEASURED;
|
|
}
|
|
|
|
x += b->width;
|
|
if (b->space == UNKNOWN_WIDTH) {
|
|
font_func->width(&fstyle, " ", 1, &b->space);
|
|
/** \todo handle errors */
|
|
}
|
|
space_after = b->space;
|
|
continue;
|
|
}
|
|
|
|
space_after = 0;
|
|
|
|
/* inline replaced, 10.3.2 and 10.6.2 */
|
|
assert(b->style);
|
|
|
|
layout_find_dimensions(&content->unit_len_ctx,
|
|
*width, -1, b, b->style,
|
|
&b->width, &b->height,
|
|
&max_width, &min_width,
|
|
&max_height, &min_height,
|
|
NULL, NULL, NULL);
|
|
|
|
if (b->object && !(b->flags & REPLACE_DIM)) {
|
|
layout_get_object_dimensions(b, &b->width, &b->height,
|
|
min_width, max_width,
|
|
min_height, max_height);
|
|
} else if (b->flags & IFRAME) {
|
|
/* TODO: should we look at the content dimensions? */
|
|
if (b->width == AUTO)
|
|
b->width = 400;
|
|
if (b->height == AUTO)
|
|
b->height = 300;
|
|
|
|
/* We reformat the iframe browser window to new
|
|
* dimensions in pass 2 */
|
|
} else {
|
|
/* form control with no object */
|
|
if (b->width == AUTO)
|
|
b->width = FIXTOINT(css_unit_len2device_px(
|
|
b->style,
|
|
&content->unit_len_ctx, INTTOFIX(1),
|
|
CSS_UNIT_EM));
|
|
if (b->height == AUTO)
|
|
b->height = FIXTOINT(css_unit_len2device_px(
|
|
b->style,
|
|
&content->unit_len_ctx, INTTOFIX(1),
|
|
CSS_UNIT_EM));
|
|
}
|
|
|
|
/* Reformat object to new box size */
|
|
if (b->object && content_can_reformat(b->object) &&
|
|
b->width !=
|
|
content_get_available_width(b->object)) {
|
|
css_fixed value = 0;
|
|
css_unit unit = CSS_UNIT_PX;
|
|
enum css_height_e htype = css_computed_height(b->style,
|
|
&value, &unit);
|
|
|
|
content_reformat(b->object, false, b->width, b->height);
|
|
|
|
if (htype == CSS_HEIGHT_AUTO)
|
|
b->height = content_get_height(b->object);
|
|
}
|
|
|
|
if (height < b->height)
|
|
height = b->height;
|
|
|
|
x += b->width;
|
|
}
|
|
|
|
/* find new sides using this height */
|
|
x0 = cx;
|
|
x1 = cx + *width;
|
|
find_sides(cont->float_children, cy, cy + height, &x0, &x1,
|
|
&left, &right);
|
|
x0 -= cx;
|
|
x1 -= cx;
|
|
|
|
if (indent)
|
|
x0 += layout_text_indent(&content->unit_len_ctx,
|
|
first->parent->parent->style, *width);
|
|
|
|
if (x1 < x0)
|
|
x1 = x0;
|
|
|
|
space_after = space_before = 0;
|
|
|
|
/* pass 2: place boxes in line: loop body executed at least once */
|
|
|
|
NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0);
|
|
|
|
for (x = x_previous = 0, b = first; x <= x1 - x0 && b; b = b->next) {
|
|
|
|
NSLOG(layout, DEBUG, "pass 2: b %p, x %i", b, x);
|
|
|
|
if (b->type == BOX_INLINE_BLOCK &&
|
|
(css_computed_position(b->style) ==
|
|
CSS_POSITION_ABSOLUTE ||
|
|
css_computed_position(b->style) ==
|
|
CSS_POSITION_FIXED)) {
|
|
b->x = x + space_after;
|
|
|
|
} else if (lh__box_is_inline_flow(b)) {
|
|
assert(b->width != UNKNOWN_WIDTH);
|
|
|
|
x_previous = x;
|
|
x += space_after;
|
|
b->x = x;
|
|
|
|
if ((b->type == BOX_INLINE && !b->inline_end) ||
|
|
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] +
|
|
b->border[RIGHT].width +
|
|
b->margin[RIGHT];
|
|
} else if (b->type == BOX_INLINE) {
|
|
b->x += b->margin[LEFT] + b->border[LEFT].width;
|
|
x = b->x + b->padding[LEFT] + b->width;
|
|
} else if (b->type == BOX_INLINE_END) {
|
|
b->height = b->inline_end->height;
|
|
x += b->padding[RIGHT] +
|
|
b->border[RIGHT].width +
|
|
b->margin[RIGHT];
|
|
} else {
|
|
x += b->width;
|
|
}
|
|
|
|
space_before = space_after;
|
|
if (b->object || b->flags & REPLACE_DIM ||
|
|
b->flags & IFRAME)
|
|
space_after = 0;
|
|
else if (b->text || b->type == BOX_INLINE_END) {
|
|
if (b->space == UNKNOWN_WIDTH) {
|
|
font_plot_style_from_css(
|
|
&content->unit_len_ctx,
|
|
b->style, &fstyle);
|
|
/** \todo handle errors */
|
|
font_func->width(&fstyle, " ", 1,
|
|
&b->space);
|
|
}
|
|
space_after = b->space;
|
|
} else {
|
|
space_after = 0;
|
|
}
|
|
split_box = b;
|
|
move_y = true;
|
|
inline_count++;
|
|
} else if (b->type == BOX_BR) {
|
|
b->x = x;
|
|
b->width = 0;
|
|
br_box = b;
|
|
b = b->next;
|
|
split_box = 0;
|
|
move_y = true;
|
|
break;
|
|
|
|
} else {
|
|
/* float */
|
|
NSLOG(layout, DEBUG, "float %p", b);
|
|
|
|
d = b->children;
|
|
d->float_children = 0;
|
|
d->cached_place_below_level = 0;
|
|
b->float_container = d->float_container = cont;
|
|
|
|
if (!layout_float(d, *width, content))
|
|
return false;
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"%p : %d %d",
|
|
d,
|
|
d->margin[TOP],
|
|
d->border[TOP].width);
|
|
|
|
d->x = d->margin[LEFT] + d->border[LEFT].width;
|
|
d->y = d->margin[TOP] + d->border[TOP].width;
|
|
b->width = d->margin[LEFT] + d->border[LEFT].width +
|
|
d->padding[LEFT] + d->width +
|
|
d->padding[RIGHT] +
|
|
d->border[RIGHT].width +
|
|
d->margin[RIGHT];
|
|
b->height = d->margin[TOP] + d->border[TOP].width +
|
|
d->padding[TOP] + d->height +
|
|
d->padding[BOTTOM] +
|
|
d->border[BOTTOM].width +
|
|
d->margin[BOTTOM];
|
|
|
|
if (b->width > (x1 - x0) - x)
|
|
place_below = true;
|
|
if (d->style && (css_computed_clear(d->style) ==
|
|
CSS_CLEAR_NONE ||
|
|
(css_computed_clear(d->style) ==
|
|
CSS_CLEAR_LEFT && left == 0) ||
|
|
(css_computed_clear(d->style) ==
|
|
CSS_CLEAR_RIGHT &&
|
|
right == 0) ||
|
|
(css_computed_clear(d->style) ==
|
|
CSS_CLEAR_BOTH &&
|
|
left == 0 && right == 0)) &&
|
|
(!place_below ||
|
|
(left == 0 && right == 0 && x == 0)) &&
|
|
cy >= cont->clear_level &&
|
|
cy >= cont->cached_place_below_level) {
|
|
/* + not cleared or,
|
|
* cleared and there are no floats to clear
|
|
* + fits without needing to be placed below or,
|
|
* this line is empty with no floats
|
|
* + current y, cy, is below the clear level
|
|
*
|
|
* Float affects current line */
|
|
if (b->type == BOX_FLOAT_LEFT) {
|
|
b->x = cx + x0;
|
|
if (b->width > 0)
|
|
x0 += b->width;
|
|
left = b;
|
|
} else {
|
|
b->x = cx + x1 - b->width;
|
|
if (b->width > 0)
|
|
x1 -= b->width;
|
|
right = b;
|
|
}
|
|
b->y = cy;
|
|
} else {
|
|
/* cleared or doesn't fit on line */
|
|
/* place below into next available space */
|
|
int fcy = (cy > cont->clear_level) ? cy :
|
|
cont->clear_level;
|
|
fcy = (fcy > cont->cached_place_below_level) ?
|
|
fcy :
|
|
cont->cached_place_below_level;
|
|
fy = (fy > fcy) ? fy : fcy;
|
|
fy = (fy == cy) ? fy + height : fy;
|
|
|
|
place_float_below(b, *width, cx, fy, cont);
|
|
fy = b->y;
|
|
if (d->style && (
|
|
(css_computed_clear(d->style) ==
|
|
CSS_CLEAR_LEFT && left != 0) ||
|
|
(css_computed_clear(d->style) ==
|
|
CSS_CLEAR_RIGHT &&
|
|
right != 0) ||
|
|
(css_computed_clear(d->style) ==
|
|
CSS_CLEAR_BOTH &&
|
|
(left != 0 || right != 0)))) {
|
|
/* to be cleared below existing
|
|
* floats */
|
|
if (b->type == BOX_FLOAT_LEFT)
|
|
b->x = cx;
|
|
else
|
|
b->x = cx + *width - b->width;
|
|
|
|
fcy = layout_clear(cont->float_children,
|
|
css_computed_clear(d->style));
|
|
if (fcy > cont->clear_level)
|
|
cont->clear_level = fcy;
|
|
if (b->y < fcy)
|
|
b->y = fcy;
|
|
}
|
|
if (b->type == BOX_FLOAT_LEFT)
|
|
left = b;
|
|
else
|
|
right = b;
|
|
}
|
|
add_float_to_container(cont, b);
|
|
|
|
split_box = 0;
|
|
}
|
|
}
|
|
|
|
if (x1 - x0 < x && split_box) {
|
|
/* the last box went over the end */
|
|
size_t split = 0;
|
|
int w;
|
|
bool no_wrap = css_computed_white_space(
|
|
split_box->style) == CSS_WHITE_SPACE_NOWRAP ||
|
|
css_computed_white_space(
|
|
split_box->style) == CSS_WHITE_SPACE_PRE;
|
|
|
|
x = x_previous;
|
|
|
|
if (!no_wrap &&
|
|
(split_box->type == BOX_INLINE ||
|
|
split_box->type == BOX_TEXT) &&
|
|
!split_box->object &&
|
|
!(split_box->flags & REPLACE_DIM) &&
|
|
!(split_box->flags & IFRAME) &&
|
|
!split_box->gadget && split_box->text) {
|
|
|
|
font_plot_style_from_css(&content->unit_len_ctx,
|
|
split_box->style, &fstyle);
|
|
/** \todo handle errors */
|
|
font_func->split(&fstyle,
|
|
split_box->text,
|
|
split_box->length,
|
|
x1 - x0 - x - space_before,
|
|
&split,
|
|
&w);
|
|
}
|
|
|
|
/* split == 0 implies that text can't be split */
|
|
|
|
if (split == 0)
|
|
w = split_box->width;
|
|
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"splitting: split_box %p \"%.*s\", spilt %"PRIsizet
|
|
", w %i, left %p, right %p, inline_count %u",
|
|
split_box,
|
|
(int)split_box->length,
|
|
split_box->text,
|
|
split,
|
|
w,
|
|
left,
|
|
right,
|
|
inline_count);
|
|
|
|
if ((split == 0 || x1 - x0 <= x + space_before + w) &&
|
|
!left && !right && inline_count == 1) {
|
|
/* first word of box doesn't fit, but no floats and
|
|
* first box on line so force in */
|
|
if (split == 0 || split == split_box->length) {
|
|
/* only one word in this box, or not text
|
|
* or white-space:nowrap */
|
|
b = split_box->next;
|
|
} else {
|
|
/* cut off first word for this line */
|
|
if (!layout_text_box_split(content, &fstyle,
|
|
split_box, split, w))
|
|
return false;
|
|
b = split_box->next;
|
|
}
|
|
x += space_before + w;
|
|
|
|
NSLOG(layout, DEBUG, "forcing");
|
|
|
|
} else if ((split == 0 || x1 - x0 <= x + space_before + w) &&
|
|
inline_count == 1) {
|
|
/* first word of first box doesn't fit, but a float is
|
|
* taking some of the width so move below it */
|
|
assert(left || right);
|
|
used_height = 0;
|
|
if (left) {
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"cy %i, left->y %i, left->height %i",
|
|
cy,
|
|
left->y,
|
|
left->height);
|
|
|
|
used_height = left->y + left->height - cy + 1;
|
|
|
|
NSLOG(layout, DEBUG, "used_height %i",
|
|
used_height);
|
|
|
|
}
|
|
if (right && used_height <
|
|
right->y + right->height - cy + 1)
|
|
used_height = right->y + right->height - cy + 1;
|
|
|
|
if (used_height < 0)
|
|
used_height = 0;
|
|
|
|
b = split_box;
|
|
|
|
NSLOG(layout, DEBUG, "moving below float");
|
|
|
|
} else if (split == 0 || x1 - x0 <= x + space_before + w) {
|
|
/* first word of box doesn't fit so leave box for next
|
|
* line */
|
|
b = split_box;
|
|
|
|
NSLOG(layout, DEBUG, "leaving for next line");
|
|
|
|
} else {
|
|
/* fit as many words as possible */
|
|
assert(split != 0);
|
|
|
|
NSLOG(layout, DEBUG,
|
|
"'%.*s' %i %"PRIsizet" %i",
|
|
(int)split_box->length, split_box->text,
|
|
x1 - x0, split, w);
|
|
|
|
if (split != split_box->length) {
|
|
if (!layout_text_box_split(content, &fstyle,
|
|
split_box, split, w))
|
|
return false;
|
|
b = split_box->next;
|
|
}
|
|
x += space_before + w;
|
|
|
|
NSLOG(layout, DEBUG, "fitting words");
|
|
|
|
}
|
|
move_y = true;
|
|
}
|
|
|
|
/* set positions */
|
|
switch (css_computed_text_align(first->parent->parent->style)) {
|
|
case CSS_TEXT_ALIGN_RIGHT:
|
|
case CSS_TEXT_ALIGN_LIBCSS_RIGHT:
|
|
x0 = x1 - x;
|
|
break;
|
|
case CSS_TEXT_ALIGN_CENTER:
|
|
case CSS_TEXT_ALIGN_LIBCSS_CENTER:
|
|
x0 = (x0 + (x1 - x)) / 2;
|
|
break;
|
|
case CSS_TEXT_ALIGN_LEFT:
|
|
case CSS_TEXT_ALIGN_LIBCSS_LEFT:
|
|
case CSS_TEXT_ALIGN_JUSTIFY:
|
|
/* leave on left */
|
|
break;
|
|
case CSS_TEXT_ALIGN_DEFAULT:
|
|
/* None; consider text direction */
|
|
switch (css_computed_direction(first->parent->parent->style)) {
|
|
case CSS_DIRECTION_LTR:
|
|
/* leave on left */
|
|
break;
|
|
case CSS_DIRECTION_RTL:
|
|
x0 = x1 - x;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
for (d = first; d != b; d = d->next) {
|
|
d->flags &= ~NEW_LINE;
|
|
|
|
if (d->type == BOX_INLINE_BLOCK &&
|
|
(css_computed_position(d->style) ==
|
|
CSS_POSITION_ABSOLUTE ||
|
|
css_computed_position(d->style) ==
|
|
CSS_POSITION_FIXED)) {
|
|
/* positioned inline-blocks:
|
|
* set static position (x,y) only, rest of positioning
|
|
* is handled later */
|
|
d->x += x0;
|
|
d->y = *y;
|
|
continue;
|
|
} else if ((d->type == BOX_INLINE &&
|
|
lh__box_is_replace(d) == false) ||
|
|
d->type == BOX_BR ||
|
|
d->type == BOX_TEXT ||
|
|
d->type == BOX_INLINE_END) {
|
|
/* regular (non-replaced) inlines */
|
|
d->x += x0;
|
|
d->y = *y - d->padding[TOP];
|
|
|
|
if (d->type == BOX_TEXT && d->height > used_height) {
|
|
/* text */
|
|
used_height = d->height;
|
|
}
|
|
} else if ((d->type == BOX_INLINE) ||
|
|
d->type == BOX_INLINE_BLOCK) {
|
|
/* replaced inlines and inline-blocks */
|
|
d->x += x0;
|
|
d->y = *y + d->border[TOP].width + d->margin[TOP];
|
|
h = d->margin[TOP] + d->border[TOP].width +
|
|
d->padding[TOP] + d->height +
|
|
d->padding[BOTTOM] +
|
|
d->border[BOTTOM].width +
|
|
d->margin[BOTTOM];
|
|
if (used_height < h)
|
|
used_height = h;
|
|
}
|
|
}
|
|
|
|
first->flags |= NEW_LINE;
|
|
|
|
assert(b != first || (move_y && 0 < used_height && (left || right)));
|
|
|
|
/* handle vertical-align by adjusting box y values */
|
|
/** \todo proper vertical alignment handling */
|
|
for (d = first; d != b; d = d->next) {
|
|
if ((d->type == BOX_INLINE && d->inline_end) ||
|
|
d->type == BOX_BR ||
|
|
d->type == BOX_TEXT ||
|
|
d->type == BOX_INLINE_END) {
|
|
css_fixed value = 0;
|
|
css_unit unit = CSS_UNIT_PX;
|
|
switch (css_computed_vertical_align(d->style, &value,
|
|
&unit)) {
|
|
case CSS_VERTICAL_ALIGN_SUPER:
|
|
case CSS_VERTICAL_ALIGN_TOP:
|
|
case CSS_VERTICAL_ALIGN_TEXT_TOP:
|
|
/* already at top */
|
|
break;
|
|
case CSS_VERTICAL_ALIGN_SUB:
|
|
case CSS_VERTICAL_ALIGN_BOTTOM:
|
|
case CSS_VERTICAL_ALIGN_TEXT_BOTTOM:
|
|
d->y += used_height - d->height;
|
|
break;
|
|
default:
|
|
case CSS_VERTICAL_ALIGN_BASELINE:
|
|
d->y += 0.75 * (used_height - d->height);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* handle clearance for br */
|
|
if (br_box && css_computed_clear(br_box->style) != CSS_CLEAR_NONE) {
|
|
int clear_y = layout_clear(cont->float_children,
|
|
css_computed_clear(br_box->style));
|
|
if (used_height < clear_y - cy)
|
|
used_height = clear_y - cy;
|
|
}
|
|
|
|
if (move_y)
|
|
*y += used_height;
|
|
*next_box = b;
|
|
*width = x; /* return actual width */
|
|
return true;
|
|
}
|