summaryrefslogtreecommitdiff
path: root/content/handlers/html/table.c
diff options
context:
space:
mode:
Diffstat (limited to 'content/handlers/html/table.c')
-rw-r--r--content/handlers/html/table.c1064
1 files changed, 1064 insertions, 0 deletions
diff --git a/content/handlers/html/table.c b/content/handlers/html/table.c
new file mode 100644
index 000000000..f8762e862
--- /dev/null
+++ b/content/handlers/html/table.c
@@ -0,0 +1,1064 @@
+/*
+ * 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;
+ }
+}