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.
1065 lines
30 KiB
1065 lines
30 KiB
/*
|
|
* Copyright 2005 James Bursa <bursa@users.sourceforge.net>
|
|
* Copyright 2005 Richard Wilson <info@tinct.net>
|
|
*
|
|
* 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
|
|
* implementation of HTML table processing and layout.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <dom/dom.h>
|
|
|
|
#include "utils/log.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/talloc.h"
|
|
#include "css/utils.h"
|
|
|
|
#include "html/box.h"
|
|
#include "html/table.h"
|
|
|
|
/* Define to enable verbose table debug */
|
|
#undef TABLE_DEBUG
|
|
|
|
/**
|
|
* Container for border values during table border calculations
|
|
*/
|
|
struct border {
|
|
enum css_border_style_e style; /**< border-style */
|
|
enum css_border_color_e color; /**< border-color type */
|
|
css_color c; /**< border-color value */
|
|
css_fixed width; /**< border-width length */
|
|
css_unit unit; /**< border-width units */
|
|
};
|
|
|
|
|
|
/**
|
|
* Determine if a border style is more eyecatching than another
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param a Reference border style
|
|
* \param a_src Source of \a a
|
|
* \param b Candidate border style
|
|
* \param b_src Source of \a b
|
|
* \return True if \a b is more eyecatching than \a a
|
|
*/
|
|
static bool
|
|
table_border_is_more_eyecatching(const css_unit_ctx *unit_len_ctx,
|
|
const struct border *a,
|
|
box_type a_src,
|
|
const struct border *b,
|
|
box_type b_src)
|
|
{
|
|
css_fixed awidth, bwidth;
|
|
int impact = 0;
|
|
|
|
/* See CSS 2.1 $17.6.2.1 */
|
|
|
|
/* 1 + 2 -- hidden beats everything, none beats nothing */
|
|
if (a->style == CSS_BORDER_STYLE_HIDDEN ||
|
|
b->style == CSS_BORDER_STYLE_NONE)
|
|
return false;
|
|
|
|
if (b->style == CSS_BORDER_STYLE_HIDDEN ||
|
|
a->style == CSS_BORDER_STYLE_NONE)
|
|
return true;
|
|
|
|
/* 3a -- wider borders beat narrow ones */
|
|
/* The widths must be absolute, which will be the case
|
|
* if they've come from a computed style. */
|
|
assert(a->unit != CSS_UNIT_EM && a->unit != CSS_UNIT_EX);
|
|
assert(b->unit != CSS_UNIT_EM && b->unit != CSS_UNIT_EX);
|
|
awidth = css_unit_len2device_px(NULL, unit_len_ctx, a->width, a->unit);
|
|
bwidth = css_unit_len2device_px(NULL, unit_len_ctx, b->width, b->unit);
|
|
|
|
if (awidth < bwidth)
|
|
return true;
|
|
else if (bwidth < awidth)
|
|
return false;
|
|
|
|
/* 3b -- sort by style */
|
|
switch (a->style) {
|
|
case CSS_BORDER_STYLE_DOUBLE: impact++; fallthrough;
|
|
case CSS_BORDER_STYLE_SOLID: impact++; fallthrough;
|
|
case CSS_BORDER_STYLE_DASHED: impact++; fallthrough;
|
|
case CSS_BORDER_STYLE_DOTTED: impact++; fallthrough;
|
|
case CSS_BORDER_STYLE_RIDGE: impact++; fallthrough;
|
|
case CSS_BORDER_STYLE_OUTSET: impact++; fallthrough;
|
|
case CSS_BORDER_STYLE_GROOVE: impact++; fallthrough;
|
|
case CSS_BORDER_STYLE_INSET: impact++; fallthrough;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (b->style) {
|
|
case CSS_BORDER_STYLE_DOUBLE: impact--; fallthrough;
|
|
case CSS_BORDER_STYLE_SOLID: impact--; fallthrough;
|
|
case CSS_BORDER_STYLE_DASHED: impact--; fallthrough;
|
|
case CSS_BORDER_STYLE_DOTTED: impact--; fallthrough;
|
|
case CSS_BORDER_STYLE_RIDGE: impact--; fallthrough;
|
|
case CSS_BORDER_STYLE_OUTSET: impact--; fallthrough;
|
|
case CSS_BORDER_STYLE_GROOVE: impact--; fallthrough;
|
|
case CSS_BORDER_STYLE_INSET: impact--; fallthrough;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (impact < 0)
|
|
return true;
|
|
else if (impact > 0)
|
|
return false;
|
|
|
|
/* 4a -- sort by origin */
|
|
impact = 0;
|
|
|
|
/** \todo COL/COL_GROUP */
|
|
switch (a_src) {
|
|
case BOX_TABLE_CELL: impact++; fallthrough;
|
|
case BOX_TABLE_ROW: impact++; fallthrough;
|
|
case BOX_TABLE_ROW_GROUP: impact++; fallthrough;
|
|
case BOX_TABLE: impact++; fallthrough;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/** \todo COL/COL_GROUP */
|
|
switch (b_src) {
|
|
case BOX_TABLE_CELL: impact--; fallthrough;
|
|
case BOX_TABLE_ROW: impact--; fallthrough;
|
|
case BOX_TABLE_ROW_GROUP: impact--; fallthrough;
|
|
case BOX_TABLE: impact--; fallthrough;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (impact < 0)
|
|
return true;
|
|
else if (impact > 0)
|
|
return false;
|
|
|
|
/* 4b -- furthest left (if direction: ltr) and towards top wins */
|
|
/** \todo Currently assumes b satisifies this */
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Process a table
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param table Table to process
|
|
* \param a Current border style for cell
|
|
* \param a_src Source of \a a
|
|
*
|
|
* \post \a a will be updated with most eyecatching style
|
|
* \post \a a_src will be updated also
|
|
*/
|
|
static void
|
|
table_cell_top_process_table(const css_unit_ctx *unit_len_ctx,
|
|
struct box *table,
|
|
struct border *a,
|
|
box_type *a_src)
|
|
{
|
|
struct border b;
|
|
box_type b_src;
|
|
|
|
/* Top border of table */
|
|
b.style = css_computed_border_top_style(table->style);
|
|
b.color = css_computed_border_top_color(table->style, &b.c);
|
|
css_computed_border_top_width(table->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(table->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) {
|
|
*a = b;
|
|
*a_src = b_src;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Process a row
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param cell Cell being considered
|
|
* \param row Row to process
|
|
* \param a Current border style for cell
|
|
* \param a_src Source of \a a
|
|
* \return true if row has cells, false otherwise
|
|
*
|
|
* \post \a a will be updated with most eyecatching style
|
|
* \post \a a_src will be updated also
|
|
*/
|
|
static bool
|
|
table_cell_top_process_row(const css_unit_ctx *unit_len_ctx,
|
|
struct box *cell,
|
|
struct box *row,
|
|
struct border *a,
|
|
box_type *a_src)
|
|
{
|
|
struct border b;
|
|
box_type b_src;
|
|
|
|
/* Bottom border of row */
|
|
b.style = css_computed_border_bottom_style(row->style);
|
|
b.color = css_computed_border_bottom_color(row->style, &b.c);
|
|
css_computed_border_bottom_width(row->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(row->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) {
|
|
*a = b;
|
|
*a_src = b_src;
|
|
}
|
|
|
|
if (row->children == NULL) {
|
|
/* Row is empty, so consider its top border */
|
|
b.style = css_computed_border_top_style(row->style);
|
|
b.color = css_computed_border_top_color(row->style, &b.c);
|
|
css_computed_border_top_width(row->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(row->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
a, *a_src, &b, b_src)) {
|
|
*a = b;
|
|
*a_src = b_src;
|
|
}
|
|
|
|
return false;
|
|
} else {
|
|
/* Process cells that are directly above the cell being
|
|
* considered. They may not be in this row, but in one of the
|
|
* rows above it in the case where rowspan > 1. */
|
|
struct box *c;
|
|
bool processed = false;
|
|
|
|
while (processed == false) {
|
|
for (c = row->children; c != NULL; c = c->next) {
|
|
/* Ignore cells to the left */
|
|
if (c->start_column + c->columns - 1 <
|
|
cell->start_column)
|
|
continue;
|
|
/* Ignore cells to the right */
|
|
if (c->start_column > cell->start_column +
|
|
cell->columns - 1)
|
|
continue;
|
|
|
|
/* Flag that we've processed a cell */
|
|
processed = true;
|
|
|
|
/* Consider bottom border */
|
|
b.style = css_computed_border_bottom_style(
|
|
c->style);
|
|
b.color = css_computed_border_bottom_color(
|
|
c->style, &b.c);
|
|
css_computed_border_bottom_width(c->style,
|
|
&b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(
|
|
c->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_CELL;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
a,
|
|
*a_src,
|
|
&b,
|
|
b_src)) {
|
|
*a = b;
|
|
*a_src = b_src;
|
|
}
|
|
}
|
|
|
|
if (processed == false) {
|
|
/* There must be a preceding row */
|
|
assert(row->prev != NULL);
|
|
|
|
row = row->prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Process a group
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param cell Cell being considered
|
|
* \param group Group to process
|
|
* \param a Current border style for cell
|
|
* \param a_src Source of \a a
|
|
* \return true if group has non-empty rows, false otherwise
|
|
*
|
|
* \post \a a will be updated with most eyecatching style
|
|
* \post \a a_src will be updated also
|
|
*/
|
|
static bool
|
|
table_cell_top_process_group(const css_unit_ctx *unit_len_ctx,
|
|
struct box *cell,
|
|
struct box *group,
|
|
struct border *a,
|
|
box_type *a_src)
|
|
{
|
|
struct border b;
|
|
box_type b_src;
|
|
|
|
/* Bottom border of group */
|
|
b.style = css_computed_border_bottom_style(group->style);
|
|
b.color = css_computed_border_bottom_color(group->style, &b.c);
|
|
css_computed_border_bottom_width(group->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(group->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW_GROUP;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) {
|
|
*a = b;
|
|
*a_src = b_src;
|
|
}
|
|
|
|
if (group->last != NULL) {
|
|
/* Process rows in group, starting with last */
|
|
struct box *row = group->last;
|
|
|
|
while (table_cell_top_process_row(unit_len_ctx, cell, row,
|
|
a, a_src) == false) {
|
|
if (row->prev == NULL) {
|
|
return false;
|
|
} else {
|
|
row = row->prev;
|
|
}
|
|
}
|
|
} else {
|
|
/* Group is empty, so consider its top border */
|
|
b.style = css_computed_border_top_style(group->style);
|
|
b.color = css_computed_border_top_color(group->style, &b.c);
|
|
css_computed_border_top_width(group->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(group->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW_GROUP;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
a, *a_src, &b, b_src)) {
|
|
*a = b;
|
|
*a_src = b_src;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Calculate used values of border-left-{style,color,width}
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param cell Table cell to consider
|
|
*/
|
|
static void
|
|
table_used_left_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
|
|
{
|
|
struct border a, b;
|
|
box_type a_src, b_src;
|
|
|
|
/** \todo Need column and column_group, too */
|
|
|
|
/* Initialise to computed left border for cell */
|
|
a.style = css_computed_border_left_style(cell->style);
|
|
a.color = css_computed_border_left_color(cell->style, &a.c);
|
|
css_computed_border_left_width(cell->style, &a.width, &a.unit);
|
|
a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
|
|
a.width, a.unit);
|
|
a.unit = CSS_UNIT_PX;
|
|
a_src = BOX_TABLE_CELL;
|
|
|
|
if (cell->prev != NULL || cell->start_column != 0) {
|
|
/* Cell to the left -- consider its right border */
|
|
struct box *prev = NULL;
|
|
|
|
if (cell->prev == NULL) {
|
|
struct box *row;
|
|
|
|
/* Spanned from a previous row in current row group */
|
|
for (row = cell->parent; row != NULL; row = row->prev) {
|
|
for (prev = row->children; prev != NULL;
|
|
prev = prev->next) {
|
|
if (prev->start_column +
|
|
prev->columns ==
|
|
cell->start_column)
|
|
break;
|
|
}
|
|
|
|
if (prev != NULL)
|
|
break;
|
|
}
|
|
|
|
assert(prev != NULL);
|
|
} else {
|
|
prev = cell->prev;
|
|
}
|
|
|
|
b.style = css_computed_border_right_style(prev->style);
|
|
b.color = css_computed_border_right_color(prev->style, &b.c);
|
|
css_computed_border_right_width(prev->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(prev->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_CELL;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
} else {
|
|
/* First cell in row, so consider rows and row group */
|
|
struct box *row = cell->parent;
|
|
struct box *group = row->parent;
|
|
struct box *table = group->parent;
|
|
unsigned int rows = cell->rows;
|
|
|
|
while (rows-- > 0 && row != NULL) {
|
|
/* Spanned rows -- consider their left border */
|
|
b.style = css_computed_border_left_style(row->style);
|
|
b.color = css_computed_border_left_color(
|
|
row->style, &b.c);
|
|
css_computed_border_left_width(
|
|
row->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(
|
|
row->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
row = row->next;
|
|
}
|
|
|
|
/** \todo can cells span row groups? */
|
|
|
|
/* Row group -- consider its left border */
|
|
b.style = css_computed_border_left_style(group->style);
|
|
b.color = css_computed_border_left_color(group->style, &b.c);
|
|
css_computed_border_left_width(group->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(group->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW_GROUP;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
/* The table itself -- consider its left border */
|
|
b.style = css_computed_border_left_style(table->style);
|
|
b.color = css_computed_border_left_color(table->style, &b.c);
|
|
css_computed_border_left_width(table->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(table->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
}
|
|
|
|
/* a now contains the used left border for the cell */
|
|
cell->border[LEFT].style = a.style;
|
|
cell->border[LEFT].c = a.c;
|
|
cell->border[LEFT].width = FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx, a.width, a.unit));
|
|
}
|
|
|
|
|
|
/**
|
|
* Calculate used values of border-top-{style,color,width}
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param cell Table cell to consider
|
|
*/
|
|
static void
|
|
table_used_top_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
|
|
{
|
|
struct border a, b;
|
|
box_type a_src, b_src;
|
|
struct box *row = cell->parent;
|
|
bool process_group = false;
|
|
|
|
/* Initialise to computed top border for cell */
|
|
a.style = css_computed_border_top_style(cell->style);
|
|
css_computed_border_top_color(cell->style, &a.c);
|
|
css_computed_border_top_width(cell->style, &a.width, &a.unit);
|
|
a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
|
|
a.width, a.unit);
|
|
a.unit = CSS_UNIT_PX;
|
|
a_src = BOX_TABLE_CELL;
|
|
|
|
/* Top border of row */
|
|
b.style = css_computed_border_top_style(row->style);
|
|
css_computed_border_top_color(row->style, &b.c);
|
|
css_computed_border_top_width(row->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(row->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx, &a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
if (row->prev != NULL) {
|
|
/* Consider row(s) above */
|
|
while (table_cell_top_process_row(unit_len_ctx, cell, row->prev,
|
|
&a, &a_src) == false) {
|
|
if (row->prev->prev == NULL) {
|
|
/* Consider row group */
|
|
process_group = true;
|
|
break;
|
|
} else {
|
|
row = row->prev;
|
|
}
|
|
}
|
|
} else {
|
|
process_group = true;
|
|
}
|
|
|
|
if (process_group) {
|
|
struct box *group = row->parent;
|
|
|
|
/* Top border of row group */
|
|
b.style = css_computed_border_top_style(group->style);
|
|
b.color = css_computed_border_top_color(group->style, &b.c);
|
|
css_computed_border_top_width(group->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(group->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW_GROUP;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
if (group->prev == NULL) {
|
|
/* Top border of table */
|
|
table_cell_top_process_table(unit_len_ctx,
|
|
group->parent, &a, &a_src);
|
|
} else {
|
|
/* Process previous group(s) */
|
|
while (table_cell_top_process_group(unit_len_ctx,
|
|
cell, group->prev,
|
|
&a, &a_src) == false) {
|
|
if (group->prev->prev == NULL) {
|
|
/* Top border of table */
|
|
table_cell_top_process_table(unit_len_ctx,
|
|
group->parent,
|
|
&a, &a_src);
|
|
break;
|
|
} else {
|
|
group = group->prev;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* a now contains the used top border for the cell */
|
|
cell->border[TOP].style = a.style;
|
|
cell->border[TOP].c = a.c;
|
|
cell->border[TOP].width = FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx, a.width, a.unit));
|
|
}
|
|
|
|
/**
|
|
* Calculate used values of border-right-{style,color,width}
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param cell Table cell to consider
|
|
*/
|
|
static void
|
|
table_used_right_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
|
|
{
|
|
struct border a, b;
|
|
box_type a_src, b_src;
|
|
|
|
/** \todo Need column and column_group, too */
|
|
|
|
/* Initialise to computed right border for cell */
|
|
a.style = css_computed_border_right_style(cell->style);
|
|
css_computed_border_right_color(cell->style, &a.c);
|
|
css_computed_border_right_width(cell->style, &a.width, &a.unit);
|
|
a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
|
|
a.width, a.unit);
|
|
a.unit = CSS_UNIT_PX;
|
|
a_src = BOX_TABLE_CELL;
|
|
|
|
if (cell->next != NULL || cell->start_column + cell->columns !=
|
|
cell->parent->parent->parent->columns) {
|
|
/* Cell is not at right edge of table -- no right border */
|
|
a.style = CSS_BORDER_STYLE_NONE;
|
|
a.width = 0;
|
|
a.unit = CSS_UNIT_PX;
|
|
} else {
|
|
/* Last cell in row, so consider rows and row group */
|
|
struct box *row = cell->parent;
|
|
struct box *group = row->parent;
|
|
struct box *table = group->parent;
|
|
unsigned int rows = cell->rows;
|
|
|
|
while (rows-- > 0 && row != NULL) {
|
|
/* Spanned rows -- consider their right border */
|
|
b.style = css_computed_border_right_style(row->style);
|
|
b.color = css_computed_border_right_color(row->style,
|
|
&b.c);
|
|
css_computed_border_right_width(row->style,
|
|
&b.width,
|
|
&b.unit);
|
|
b.width = css_unit_len2device_px(
|
|
row->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src,
|
|
&b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
row = row->next;
|
|
}
|
|
|
|
/** \todo can cells span row groups? */
|
|
|
|
/* Row group -- consider its right border */
|
|
b.style = css_computed_border_right_style(group->style);
|
|
b.color = css_computed_border_right_color(group->style, &b.c);
|
|
css_computed_border_right_width(group->style,
|
|
&b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(group->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW_GROUP;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
/* The table itself -- consider its right border */
|
|
b.style = css_computed_border_right_style(table->style);
|
|
b.color = css_computed_border_right_color(table->style, &b.c);
|
|
css_computed_border_right_width(table->style,
|
|
&b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(table->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src,
|
|
&b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
}
|
|
|
|
/* a now contains the used right border for the cell */
|
|
cell->border[RIGHT].style = a.style;
|
|
cell->border[RIGHT].c = a.c;
|
|
cell->border[RIGHT].width = FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx, a.width, a.unit));
|
|
}
|
|
|
|
|
|
/**
|
|
* Calculate used values of border-bottom-{style,color,width}
|
|
*
|
|
* \param unit_len_ctx Length conversion context
|
|
* \param cell Table cell to consider
|
|
*/
|
|
static void
|
|
table_used_bottom_border_for_cell(const css_unit_ctx *unit_len_ctx,
|
|
struct box *cell)
|
|
{
|
|
struct border a, b;
|
|
box_type a_src, b_src;
|
|
struct box *row = cell->parent;
|
|
unsigned int rows = cell->rows;
|
|
|
|
/* Initialise to computed bottom border for cell */
|
|
a.style = css_computed_border_bottom_style(cell->style);
|
|
css_computed_border_bottom_color(cell->style, &a.c);
|
|
css_computed_border_bottom_width(cell->style, &a.width, &a.unit);
|
|
a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
|
|
a.width, a.unit);
|
|
a.unit = CSS_UNIT_PX;
|
|
a_src = BOX_TABLE_CELL;
|
|
|
|
while (rows-- > 0 && row != NULL)
|
|
row = row->next;
|
|
|
|
/** \todo Can cells span row groups? */
|
|
|
|
if (row != NULL) {
|
|
/* Cell is not at bottom edge of table -- no bottom border */
|
|
a.style = CSS_BORDER_STYLE_NONE;
|
|
a.width = 0;
|
|
a.unit = CSS_UNIT_PX;
|
|
} else {
|
|
/* Cell at bottom of table, so consider row and row group */
|
|
struct box *row = cell->parent;
|
|
struct box *group = row->parent;
|
|
struct box *table = group->parent;
|
|
|
|
/* Bottom border of row */
|
|
b.style = css_computed_border_bottom_style(row->style);
|
|
b.color = css_computed_border_bottom_color(row->style, &b.c);
|
|
css_computed_border_bottom_width(row->style, &b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(row->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
/* Row group -- consider its bottom border */
|
|
b.style = css_computed_border_bottom_style(group->style);
|
|
b.color = css_computed_border_bottom_color(group->style, &b.c);
|
|
css_computed_border_bottom_width(group->style,
|
|
&b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(group->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE_ROW_GROUP;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
a_src = b_src;
|
|
}
|
|
|
|
/* The table itself -- consider its bottom border */
|
|
b.style = css_computed_border_bottom_style(table->style);
|
|
b.color = css_computed_border_bottom_color(table->style, &b.c);
|
|
css_computed_border_bottom_width(table->style,
|
|
&b.width, &b.unit);
|
|
b.width = css_unit_len2device_px(table->style, unit_len_ctx,
|
|
b.width, b.unit);
|
|
b.unit = CSS_UNIT_PX;
|
|
b_src = BOX_TABLE;
|
|
|
|
if (table_border_is_more_eyecatching(unit_len_ctx,
|
|
&a, a_src, &b, b_src)) {
|
|
a = b;
|
|
}
|
|
}
|
|
|
|
/* a now contains the used bottom border for the cell */
|
|
cell->border[BOTTOM].style = a.style;
|
|
cell->border[BOTTOM].c = a.c;
|
|
cell->border[BOTTOM].width = FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx, a.width, a.unit));
|
|
}
|
|
|
|
|
|
/* exported interface documented in html/table.h */
|
|
bool
|
|
table_calculate_column_types(const css_unit_ctx *unit_len_ctx, struct box *table)
|
|
{
|
|
unsigned int i, j;
|
|
struct column *col;
|
|
struct box *row_group, *row, *cell;
|
|
|
|
if (table->col)
|
|
/* table->col already constructed, for example frameset table */
|
|
return true;
|
|
|
|
table->col = col = talloc_array(table, struct column, table->columns);
|
|
if (!col)
|
|
return false;
|
|
|
|
for (i = 0; i != table->columns; i++) {
|
|
col[i].type = COLUMN_WIDTH_UNKNOWN;
|
|
col[i].width = 0;
|
|
col[i].positioned = true;
|
|
}
|
|
|
|
/* 1st pass: cells with colspan 1 only */
|
|
for (row_group = table->children; row_group; row_group =row_group->next)
|
|
for (row = row_group->children; row; row = row->next)
|
|
for (cell = row->children; cell; cell = cell->next) {
|
|
enum css_width_e type;
|
|
css_fixed value = 0;
|
|
css_unit unit = CSS_UNIT_PX;
|
|
|
|
assert(cell->type == BOX_TABLE_CELL);
|
|
assert(cell->style);
|
|
|
|
if (cell->columns != 1)
|
|
continue;
|
|
i = cell->start_column;
|
|
|
|
if (css_computed_position(cell->style) !=
|
|
CSS_POSITION_ABSOLUTE &&
|
|
css_computed_position(cell->style) !=
|
|
CSS_POSITION_FIXED) {
|
|
col[i].positioned = false;
|
|
}
|
|
|
|
type = css_computed_width(cell->style, &value, &unit);
|
|
|
|
/* fixed width takes priority over any other width type */
|
|
if (col[i].type != COLUMN_WIDTH_FIXED &&
|
|
type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) {
|
|
col[i].type = COLUMN_WIDTH_FIXED;
|
|
col[i].width = FIXTOINT(css_unit_len2device_px(
|
|
cell->style,
|
|
unit_len_ctx,
|
|
value, unit));
|
|
if (col[i].width < 0)
|
|
col[i].width = 0;
|
|
continue;
|
|
}
|
|
|
|
if (col[i].type != COLUMN_WIDTH_UNKNOWN)
|
|
continue;
|
|
|
|
if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT) {
|
|
col[i].type = COLUMN_WIDTH_PERCENT;
|
|
col[i].width = FIXTOINT(value);
|
|
if (col[i].width < 0)
|
|
col[i].width = 0;
|
|
} else if (type == CSS_WIDTH_AUTO) {
|
|
col[i].type = COLUMN_WIDTH_AUTO;
|
|
}
|
|
}
|
|
|
|
/* 2nd pass: cells which span multiple columns */
|
|
for (row_group = table->children; row_group; row_group =row_group->next)
|
|
for (row = row_group->children; row; row = row->next)
|
|
for (cell = row->children; cell; cell = cell->next) {
|
|
unsigned int fixed_columns = 0,
|
|
percent_columns = 0,
|
|
auto_columns = 0,
|
|
unknown_columns = 0;
|
|
int fixed_width = 0, percent_width = 0;
|
|
enum css_width_e type;
|
|
css_fixed value = 0;
|
|
css_unit unit = CSS_UNIT_PX;
|
|
|
|
if (cell->columns == 1)
|
|
continue;
|
|
i = cell->start_column;
|
|
|
|
for (j = i; j < i + cell->columns; j++) {
|
|
col[j].positioned = false;
|
|
}
|
|
|
|
/* count column types in spanned cells */
|
|
for (j = 0; j != cell->columns; j++) {
|
|
if (col[i + j].type == COLUMN_WIDTH_FIXED) {
|
|
fixed_width += col[i + j].width;
|
|
fixed_columns++;
|
|
} else if (col[i + j].type == COLUMN_WIDTH_PERCENT) {
|
|
percent_width += col[i + j].width;
|
|
percent_columns++;
|
|
} else if (col[i + j].type == COLUMN_WIDTH_AUTO) {
|
|
auto_columns++;
|
|
} else {
|
|
unknown_columns++;
|
|
}
|
|
}
|
|
|
|
if (!unknown_columns)
|
|
continue;
|
|
|
|
type = css_computed_width(cell->style, &value, &unit);
|
|
|
|
/* if cell is fixed width, and all spanned columns are fixed
|
|
* or unknown width, split extra width among unknown columns */
|
|
if (type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT &&
|
|
fixed_columns + unknown_columns ==
|
|
cell->columns) {
|
|
int width = (FIXTOFLT(css_unit_len2device_px(
|
|
cell->style,
|
|
unit_len_ctx,
|
|
value, unit)) -
|
|
fixed_width) / unknown_columns;
|
|
if (width < 0)
|
|
width = 0;
|
|
for (j = 0; j != cell->columns; j++) {
|
|
if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) {
|
|
col[i + j].type = COLUMN_WIDTH_FIXED;
|
|
col[i + j].width = width;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* as above for percentage width */
|
|
if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT &&
|
|
percent_columns + unknown_columns ==
|
|
cell->columns) {
|
|
int width = (FIXTOFLT(value) -
|
|
percent_width) / unknown_columns;
|
|
if (width < 0)
|
|
width = 0;
|
|
for (j = 0; j != cell->columns; j++) {
|
|
if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) {
|
|
col[i + j].type = COLUMN_WIDTH_PERCENT;
|
|
col[i + j].width = width;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* use AUTO if no width type was specified */
|
|
for (i = 0; i != table->columns; i++) {
|
|
if (col[i].type == COLUMN_WIDTH_UNKNOWN)
|
|
col[i].type = COLUMN_WIDTH_AUTO;
|
|
}
|
|
|
|
#ifdef TABLE_DEBUG
|
|
for (i = 0; i != table->columns; i++)
|
|
NSLOG(netsurf, INFO,
|
|
"table %p, column %u: type %s, width %i",
|
|
table,
|
|
i,
|
|
((const char *[]){
|
|
"UNKNOWN",
|
|
"FIXED",
|
|
"AUTO",
|
|
"PERCENT",
|
|
"RELATIVE",
|
|
})[col[i].type],
|
|
col[i].width);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* exported interface documented in html/table.h */
|
|
void table_used_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
|
|
{
|
|
int side;
|
|
|
|
assert(cell->type == BOX_TABLE_CELL);
|
|
|
|
if (css_computed_border_collapse(cell->style) ==
|
|
CSS_BORDER_COLLAPSE_SEPARATE) {
|
|
css_fixed width = 0;
|
|
css_unit unit = CSS_UNIT_PX;
|
|
|
|
/* Left border */
|
|
cell->border[LEFT].style =
|
|
css_computed_border_left_style(cell->style);
|
|
css_computed_border_left_color(cell->style,
|
|
&cell->border[LEFT].c);
|
|
css_computed_border_left_width(cell->style, &width, &unit);
|
|
cell->border[LEFT].width =
|
|
FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx,
|
|
width, unit));
|
|
|
|
/* Top border */
|
|
cell->border[TOP].style =
|
|
css_computed_border_top_style(cell->style);
|
|
css_computed_border_top_color(cell->style,
|
|
&cell->border[TOP].c);
|
|
css_computed_border_top_width(cell->style, &width, &unit);
|
|
cell->border[TOP].width =
|
|
FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx,
|
|
width, unit));
|
|
|
|
/* Right border */
|
|
cell->border[RIGHT].style =
|
|
css_computed_border_right_style(cell->style);
|
|
css_computed_border_right_color(cell->style,
|
|
&cell->border[RIGHT].c);
|
|
css_computed_border_right_width(cell->style, &width, &unit);
|
|
cell->border[RIGHT].width =
|
|
FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx,
|
|
width, unit));
|
|
|
|
/* Bottom border */
|
|
cell->border[BOTTOM].style =
|
|
css_computed_border_bottom_style(cell->style);
|
|
css_computed_border_bottom_color(cell->style,
|
|
&cell->border[BOTTOM].c);
|
|
css_computed_border_bottom_width(cell->style, &width, &unit);
|
|
cell->border[BOTTOM].width =
|
|
FIXTOINT(css_unit_len2device_px(
|
|
cell->style, unit_len_ctx,
|
|
width, unit));
|
|
} else {
|
|
/* Left border */
|
|
table_used_left_border_for_cell(unit_len_ctx, cell);
|
|
|
|
/* Top border */
|
|
table_used_top_border_for_cell(unit_len_ctx, cell);
|
|
|
|
/* Right border */
|
|
table_used_right_border_for_cell(unit_len_ctx, cell);
|
|
|
|
/* Bottom border */
|
|
table_used_bottom_border_for_cell(unit_len_ctx, cell);
|
|
}
|
|
|
|
/* Finally, ensure that any borders configured as
|
|
* hidden or none have zero width. (c.f. layout_find_dimensions) */
|
|
for (side = 0; side != 4; side++) {
|
|
if (cell->border[side].style == CSS_BORDER_STYLE_HIDDEN ||
|
|
cell->border[side].style ==
|
|
CSS_BORDER_STYLE_NONE)
|
|
cell->border[side].width = 0;
|
|
}
|
|
}
|