mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-12-23 20:46:50 +03:00
c105738fa3
This changes the LOG macro to be varadic removing the need for all callsites to have double bracketing and allows for future improvement on how we use the logging macros. The callsites were changed with coccinelle and the changes checked by hand. Compile tested for several frontends but not all. A formatting annotation has also been added which allows the compiler to check the parameters and types passed to the logging.
1003 lines
24 KiB
C
1003 lines
24 KiB
C
/*
|
|
* Copyright 2005 James Bursa <bursa@users.sourceforge.net>
|
|
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
|
|
* Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk>
|
|
* Copyright 2004 Kevin Bagust <kevin.bagust@ntlworld.com>
|
|
*
|
|
* 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
|
|
* Box tree normalisation (implementation).
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include "css/css.h"
|
|
#include "css/select.h"
|
|
#include "render/box.h"
|
|
#include "render/html_internal.h"
|
|
#include "render/table.h"
|
|
#include "utils/log.h"
|
|
|
|
/* Define to enable box normalise debug */
|
|
#undef BOX_NORMALISE_DEBUG
|
|
|
|
/**
|
|
* Row spanning information for a cell
|
|
*/
|
|
struct span_info {
|
|
/** Number of rows this cell spans */
|
|
unsigned int row_span;
|
|
/** Row group of cell */
|
|
struct box *rg;
|
|
/** The cell in this column spans all rows until the end of the table */
|
|
bool auto_row;
|
|
};
|
|
|
|
/**
|
|
* Column record for a table
|
|
*/
|
|
struct columns {
|
|
/** Current column index */
|
|
unsigned int current_column;
|
|
/** Number of columns in main part of table 1..max columns */
|
|
unsigned int num_columns;
|
|
/** Information about columns in main table, array [0, num_columns) */
|
|
struct span_info *spans;
|
|
/** Number of rows in table */
|
|
unsigned int num_rows;
|
|
};
|
|
|
|
|
|
static bool box_normalise_table(struct box *table, html_content *c);
|
|
static bool box_normalise_table_spans(struct box *table,
|
|
struct span_info *spans, html_content *c);
|
|
static bool box_normalise_table_row_group(struct box *row_group,
|
|
struct columns *col_info,
|
|
html_content *c);
|
|
static bool box_normalise_table_row(struct box *row,
|
|
struct columns *col_info,
|
|
html_content *c);
|
|
static bool calculate_table_row(struct columns *col_info,
|
|
unsigned int col_span, unsigned int row_span,
|
|
unsigned int *start_column, struct box *cell);
|
|
static bool box_normalise_inline_container(struct box *cont, html_content *c);
|
|
|
|
/**
|
|
* Ensure the box tree is correctly nested by adding and removing nodes.
|
|
*
|
|
* \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL
|
|
* \param c content of boxes
|
|
* \return true on success, false on memory exhaustion
|
|
*
|
|
* The tree is modified to satisfy the following:
|
|
* \code
|
|
* parent permitted child nodes
|
|
* BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE
|
|
* INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT
|
|
* INLINE, TEXT none
|
|
* TABLE at least 1 TABLE_ROW_GROUP
|
|
* TABLE_ROW_GROUP at least 1 TABLE_ROW
|
|
* TABLE_ROW at least 1 TABLE_CELL
|
|
* TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK)
|
|
* FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE
|
|
* \endcode
|
|
*/
|
|
|
|
bool box_normalise_block(struct box *block, html_content *c)
|
|
{
|
|
struct box *child;
|
|
struct box *next_child;
|
|
struct box *table;
|
|
css_computed_style *style;
|
|
nscss_select_ctx ctx;
|
|
|
|
assert(block != NULL);
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("block %p, block->type %u", block, block->type);
|
|
#endif
|
|
|
|
assert(block->type == BOX_BLOCK || block->type == BOX_INLINE_BLOCK ||
|
|
block->type == BOX_TABLE_CELL);
|
|
|
|
for (child = block->children; child != NULL; child = next_child) {
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("child %p, child->type = %d", child, child->type);
|
|
#endif
|
|
|
|
next_child = child->next; /* child may be destroyed */
|
|
|
|
switch (child->type) {
|
|
case BOX_BLOCK:
|
|
/* ok */
|
|
if (box_normalise_block(child, c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_INLINE_CONTAINER:
|
|
if (box_normalise_inline_container(child, c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_TABLE:
|
|
if (box_normalise_table(child, c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_INLINE:
|
|
case BOX_INLINE_END:
|
|
case BOX_INLINE_BLOCK:
|
|
case BOX_FLOAT_LEFT:
|
|
case BOX_FLOAT_RIGHT:
|
|
case BOX_BR:
|
|
case BOX_TEXT:
|
|
/* should have been wrapped in inline
|
|
container by convert_xml_to_box() */
|
|
assert(0);
|
|
break;
|
|
case BOX_TABLE_ROW_GROUP:
|
|
case BOX_TABLE_ROW:
|
|
case BOX_TABLE_CELL:
|
|
/* insert implied table */
|
|
assert(block->style != NULL);
|
|
|
|
ctx.ctx = c->select_ctx;
|
|
ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
|
|
ctx.base_url = c->base_url;
|
|
ctx.universal = c->universal;
|
|
|
|
style = nscss_get_blank_style(&ctx, block->style);
|
|
if (style == NULL)
|
|
return false;
|
|
|
|
table = box_create(NULL, style, true, block->href,
|
|
block->target, NULL, NULL, c->bctx);
|
|
if (table == NULL) {
|
|
css_computed_style_destroy(style);
|
|
return false;
|
|
}
|
|
table->type = BOX_TABLE;
|
|
|
|
if (child->prev == NULL)
|
|
block->children = table;
|
|
else
|
|
child->prev->next = table;
|
|
|
|
table->prev = child->prev;
|
|
|
|
while (child != NULL && (
|
|
child->type == BOX_TABLE_ROW_GROUP ||
|
|
child->type == BOX_TABLE_ROW ||
|
|
child->type == BOX_TABLE_CELL)) {
|
|
box_add_child(table, child);
|
|
|
|
next_child = child->next;
|
|
child->next = NULL;
|
|
child = next_child;
|
|
}
|
|
|
|
table->last->next = NULL;
|
|
table->next = next_child = child;
|
|
if (table->next != NULL)
|
|
table->next->prev = table;
|
|
else
|
|
block->last = table;
|
|
table->parent = block;
|
|
|
|
if (box_normalise_table(table, c) == false)
|
|
return false;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool box_normalise_table(struct box *table, html_content * c)
|
|
{
|
|
struct box *child;
|
|
struct box *next_child;
|
|
struct box *row_group;
|
|
css_computed_style *style;
|
|
struct columns col_info;
|
|
nscss_select_ctx ctx;
|
|
|
|
assert(table != NULL);
|
|
assert(table->type == BOX_TABLE);
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("table %p", table);
|
|
#endif
|
|
|
|
col_info.num_columns = 1;
|
|
col_info.current_column = 0;
|
|
col_info.spans = malloc(2 * sizeof *col_info.spans);
|
|
if (col_info.spans == NULL)
|
|
return false;
|
|
|
|
col_info.spans[0].row_span = col_info.spans[1].row_span = 0;
|
|
col_info.spans[0].auto_row = false;
|
|
col_info.spans[1].auto_row = false;
|
|
col_info.num_rows = 0;
|
|
|
|
for (child = table->children; child != NULL; child = next_child) {
|
|
next_child = child->next;
|
|
switch (child->type) {
|
|
case BOX_TABLE_ROW_GROUP:
|
|
/* ok */
|
|
if (box_normalise_table_row_group(child,
|
|
&col_info, c) == false) {
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
break;
|
|
case BOX_BLOCK:
|
|
case BOX_INLINE_CONTAINER:
|
|
case BOX_TABLE:
|
|
case BOX_TABLE_ROW:
|
|
case BOX_TABLE_CELL:
|
|
/* insert implied table row group */
|
|
assert(table->style != NULL);
|
|
|
|
ctx.ctx = c->select_ctx;
|
|
ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
|
|
ctx.base_url = c->base_url;
|
|
ctx.universal = c->universal;
|
|
|
|
style = nscss_get_blank_style(&ctx, table->style);
|
|
if (style == NULL) {
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
|
|
row_group = box_create(NULL, style, true, table->href,
|
|
table->target, NULL, NULL, c->bctx);
|
|
if (row_group == NULL) {
|
|
css_computed_style_destroy(style);
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
|
|
row_group->type = BOX_TABLE_ROW_GROUP;
|
|
|
|
if (child->prev == NULL)
|
|
table->children = row_group;
|
|
else
|
|
child->prev->next = row_group;
|
|
|
|
row_group->prev = child->prev;
|
|
|
|
while (child != NULL && (
|
|
child->type == BOX_BLOCK ||
|
|
child->type == BOX_INLINE_CONTAINER ||
|
|
child->type == BOX_TABLE ||
|
|
child->type == BOX_TABLE_ROW ||
|
|
child->type == BOX_TABLE_CELL)) {
|
|
box_add_child(row_group, child);
|
|
|
|
next_child = child->next;
|
|
child->next = NULL;
|
|
child = next_child;
|
|
}
|
|
|
|
assert(row_group->last != NULL);
|
|
|
|
row_group->last->next = NULL;
|
|
row_group->next = next_child = child;
|
|
if (row_group->next != NULL)
|
|
row_group->next->prev = row_group;
|
|
else
|
|
table->last = row_group;
|
|
row_group->parent = table;
|
|
|
|
if (box_normalise_table_row_group(row_group,
|
|
&col_info, c) == false) {
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
break;
|
|
case BOX_INLINE:
|
|
case BOX_INLINE_END:
|
|
case BOX_INLINE_BLOCK:
|
|
case BOX_FLOAT_LEFT:
|
|
case BOX_FLOAT_RIGHT:
|
|
case BOX_BR:
|
|
case BOX_TEXT:
|
|
/* should have been wrapped in inline
|
|
container by convert_xml_to_box() */
|
|
assert(0);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "%i\n", child->type);
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
table->columns = col_info.num_columns;
|
|
table->rows = col_info.num_rows;
|
|
|
|
if (table->children == NULL) {
|
|
struct box *row;
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("table->children == 0, creating implied row");
|
|
#endif
|
|
|
|
assert(table->style != NULL);
|
|
|
|
ctx.ctx = c->select_ctx;
|
|
ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
|
|
ctx.base_url = c->base_url;
|
|
ctx.universal = c->universal;
|
|
|
|
style = nscss_get_blank_style(&ctx, table->style);
|
|
if (style == NULL) {
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
|
|
row_group = box_create(NULL, style, true, table->href,
|
|
table->target, NULL, NULL, c->bctx);
|
|
if (row_group == NULL) {
|
|
css_computed_style_destroy(style);
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
row_group->type = BOX_TABLE_ROW_GROUP;
|
|
|
|
style = nscss_get_blank_style(&ctx, row_group->style);
|
|
if (style == NULL) {
|
|
box_free(row_group);
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
|
|
row = box_create(NULL, style, true, row_group->href,
|
|
row_group->target, NULL, NULL, c->bctx);
|
|
if (row == NULL) {
|
|
css_computed_style_destroy(style);
|
|
box_free(row_group);
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
row->type = BOX_TABLE_ROW;
|
|
|
|
row->parent = row_group;
|
|
row_group->children = row_group->last = row;
|
|
|
|
row_group->parent = table;
|
|
table->children = table->last = row_group;
|
|
|
|
table->rows = 1;
|
|
}
|
|
|
|
if (box_normalise_table_spans(table, col_info.spans, c) == false) {
|
|
free(col_info.spans);
|
|
return false;
|
|
}
|
|
|
|
free(col_info.spans);
|
|
|
|
if (table_calculate_column_types(table) == false)
|
|
return false;
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("table %p done", table);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Normalise table cell column/row counts for colspan/rowspan = 0.
|
|
* Additionally, generate empty cells.
|
|
*
|
|
* \param table Table to process
|
|
* \param spans Array of length table->columns for use in empty cell detection
|
|
* \param c Content containing table
|
|
* \return True on success, false on memory exhaustion.
|
|
*/
|
|
|
|
bool box_normalise_table_spans(struct box *table, struct span_info *spans,
|
|
html_content *c)
|
|
{
|
|
struct box *table_row_group;
|
|
struct box *table_row;
|
|
struct box *table_cell;
|
|
unsigned int rows_left = table->rows;
|
|
unsigned int group_rows_left;
|
|
unsigned int col;
|
|
nscss_select_ctx ctx;
|
|
|
|
/* Clear span data */
|
|
memset(spans, 0, table->columns * sizeof(struct span_info));
|
|
|
|
/* Scan table, filling in width and height of table cells with
|
|
* colspan = 0 and rowspan = 0. Also generate empty cells */
|
|
for (table_row_group = table->children;
|
|
table_row_group != NULL;
|
|
table_row_group = table_row_group->next) {
|
|
|
|
group_rows_left = table_row_group->rows;
|
|
|
|
for (table_row = table_row_group->children;
|
|
table_row != NULL;
|
|
table_row = table_row->next) {
|
|
|
|
for (table_cell = table_row->children;
|
|
table_cell != NULL;
|
|
table_cell = table_cell->next) {
|
|
|
|
/* colspan = 0 -> colspan = 1 */
|
|
if (table_cell->columns == 0) {
|
|
table_cell->columns = 1;
|
|
}
|
|
|
|
/* if rowspan is 0 it is expanded to
|
|
* the number of rows left in the row
|
|
* group
|
|
*/
|
|
if (table_cell->rows == 0) {
|
|
table_cell->rows = group_rows_left;
|
|
}
|
|
|
|
/* limit rowspans within group */
|
|
if (table_cell->rows > group_rows_left) {
|
|
table_cell->rows = group_rows_left;
|
|
}
|
|
|
|
/* Record span information */
|
|
for (col = table_cell->start_column;
|
|
col < table_cell->start_column +
|
|
table_cell->columns; col++) {
|
|
spans[col].row_span = table_cell->rows;
|
|
}
|
|
}
|
|
|
|
/* Reduce span count of each column */
|
|
for (col = 0; col < table->columns; col++) {
|
|
if (spans[col].row_span == 0) {
|
|
unsigned int start = col;
|
|
css_computed_style *style;
|
|
struct box *cell, *prev;
|
|
|
|
/* If it's already zero, then we need
|
|
* to generate an empty cell for the
|
|
* gap in the row that spans as many
|
|
* columns as remain blank.
|
|
*/
|
|
assert(table_row->style != NULL);
|
|
|
|
/* Find width of gap */
|
|
while (col < table->columns &&
|
|
spans[col].row_span ==
|
|
0) {
|
|
col++;
|
|
}
|
|
|
|
ctx.ctx = c->select_ctx;
|
|
ctx.quirks = (c->quirks ==
|
|
DOM_DOCUMENT_QUIRKS_MODE_FULL);
|
|
ctx.base_url = c->base_url;
|
|
ctx.universal = c->universal;
|
|
|
|
style = nscss_get_blank_style(&ctx,
|
|
table_row->style);
|
|
if (style == NULL)
|
|
return false;
|
|
|
|
cell = box_create(NULL, style, true,
|
|
table_row->href,
|
|
table_row->target,
|
|
NULL, NULL, c->bctx);
|
|
if (cell == NULL) {
|
|
css_computed_style_destroy(
|
|
style);
|
|
return false;
|
|
}
|
|
cell->type = BOX_TABLE_CELL;
|
|
|
|
cell->rows = 1;
|
|
cell->columns = col - start;
|
|
cell->start_column = start;
|
|
|
|
/* Find place to insert cell */
|
|
for (prev = table_row->children;
|
|
prev != NULL;
|
|
prev = prev->next) {
|
|
if (prev->start_column +
|
|
prev->columns ==
|
|
start)
|
|
break;
|
|
if (prev->next == NULL)
|
|
break;
|
|
}
|
|
|
|
/* Insert it */
|
|
if (prev == NULL) {
|
|
if (table_row->children != NULL)
|
|
table_row->children->
|
|
prev = cell;
|
|
else
|
|
table_row->last = cell;
|
|
|
|
cell->next =
|
|
table_row->children;
|
|
table_row->children = cell;
|
|
} else {
|
|
if (prev->next != NULL)
|
|
prev->next->prev = cell;
|
|
else
|
|
table_row->last = cell;
|
|
|
|
cell->next = prev->next;
|
|
prev->next = cell;
|
|
cell->prev = prev;
|
|
}
|
|
cell->parent = table_row;
|
|
} else {
|
|
spans[col].row_span--;
|
|
}
|
|
}
|
|
|
|
assert(rows_left > 0);
|
|
|
|
rows_left--;
|
|
}
|
|
|
|
group_rows_left--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool box_normalise_table_row_group(struct box *row_group,
|
|
struct columns *col_info,
|
|
html_content * c)
|
|
{
|
|
struct box *child;
|
|
struct box *next_child;
|
|
struct box *row;
|
|
css_computed_style *style;
|
|
nscss_select_ctx ctx;
|
|
unsigned int group_row_count = 0;
|
|
|
|
assert(row_group != 0);
|
|
assert(row_group->type == BOX_TABLE_ROW_GROUP);
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("row_group %p", row_group);
|
|
#endif
|
|
|
|
for (child = row_group->children; child != NULL; child = next_child) {
|
|
next_child = child->next;
|
|
|
|
switch (child->type) {
|
|
case BOX_TABLE_ROW:
|
|
/* ok */
|
|
group_row_count++;
|
|
if (box_normalise_table_row(child, col_info,
|
|
c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_BLOCK:
|
|
case BOX_INLINE_CONTAINER:
|
|
case BOX_TABLE:
|
|
case BOX_TABLE_ROW_GROUP:
|
|
case BOX_TABLE_CELL:
|
|
/* insert implied table row */
|
|
assert(row_group->style != NULL);
|
|
|
|
ctx.ctx = c->select_ctx;
|
|
ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
|
|
ctx.base_url = c->base_url;
|
|
ctx.universal = c->universal;
|
|
|
|
style = nscss_get_blank_style(&ctx, row_group->style);
|
|
if (style == NULL)
|
|
return false;
|
|
|
|
row = box_create(NULL, style, true, row_group->href,
|
|
row_group->target, NULL, NULL, c->bctx);
|
|
if (row == NULL) {
|
|
css_computed_style_destroy(style);
|
|
return false;
|
|
}
|
|
row->type = BOX_TABLE_ROW;
|
|
|
|
if (child->prev == NULL)
|
|
row_group->children = row;
|
|
else
|
|
child->prev->next = row;
|
|
|
|
row->prev = child->prev;
|
|
|
|
while (child != NULL && (
|
|
child->type == BOX_BLOCK ||
|
|
child->type == BOX_INLINE_CONTAINER ||
|
|
child->type == BOX_TABLE ||
|
|
child->type == BOX_TABLE_ROW_GROUP ||
|
|
child->type == BOX_TABLE_CELL)) {
|
|
box_add_child(row, child);
|
|
|
|
next_child = child->next;
|
|
child->next = NULL;
|
|
child = next_child;
|
|
}
|
|
|
|
assert(row->last != NULL);
|
|
|
|
row->last->next = NULL;
|
|
row->next = next_child = child;
|
|
if (row->next != NULL)
|
|
row->next->prev = row;
|
|
else
|
|
row_group->last = row;
|
|
row->parent = row_group;
|
|
|
|
group_row_count++;
|
|
if (box_normalise_table_row(row, col_info,
|
|
c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_INLINE:
|
|
case BOX_INLINE_END:
|
|
case BOX_INLINE_BLOCK:
|
|
case BOX_FLOAT_LEFT:
|
|
case BOX_FLOAT_RIGHT:
|
|
case BOX_BR:
|
|
case BOX_TEXT:
|
|
/* should have been wrapped in inline
|
|
container by convert_xml_to_box() */
|
|
assert(0);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
if (row_group->children == NULL) {
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("row_group->children == 0, inserting implied row");
|
|
#endif
|
|
|
|
assert(row_group->style != NULL);
|
|
|
|
ctx.ctx = c->select_ctx;
|
|
ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
|
|
ctx.base_url = c->base_url;
|
|
ctx.universal = c->universal;
|
|
|
|
style = nscss_get_blank_style(&ctx, row_group->style);
|
|
if (style == NULL) {
|
|
return false;
|
|
}
|
|
|
|
row = box_create(NULL, style, true, row_group->href,
|
|
row_group->target, NULL, NULL, c->bctx);
|
|
if (row == NULL) {
|
|
css_computed_style_destroy(style);
|
|
return false;
|
|
}
|
|
row->type = BOX_TABLE_ROW;
|
|
|
|
row->parent = row_group;
|
|
row_group->children = row_group->last = row;
|
|
|
|
group_row_count = 1;
|
|
|
|
/* Keep table's row count in sync */
|
|
col_info->num_rows++;
|
|
}
|
|
|
|
row_group->rows = group_row_count;
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("row_group %p done", row_group);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool box_normalise_table_row(struct box *row,
|
|
struct columns *col_info,
|
|
html_content * c)
|
|
{
|
|
struct box *child;
|
|
struct box *next_child;
|
|
struct box *cell = NULL;
|
|
css_computed_style *style;
|
|
unsigned int i;
|
|
nscss_select_ctx ctx;
|
|
|
|
assert(row != NULL);
|
|
assert(row->type == BOX_TABLE_ROW);
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("row %p", row);
|
|
#endif
|
|
|
|
for (child = row->children; child != NULL; child = next_child) {
|
|
next_child = child->next;
|
|
|
|
switch (child->type) {
|
|
case BOX_TABLE_CELL:
|
|
/* ok */
|
|
if (box_normalise_block(child, c) == false)
|
|
return false;
|
|
cell = child;
|
|
break;
|
|
case BOX_BLOCK:
|
|
case BOX_INLINE_CONTAINER:
|
|
case BOX_TABLE:
|
|
case BOX_TABLE_ROW_GROUP:
|
|
case BOX_TABLE_ROW:
|
|
/* insert implied table cell */
|
|
assert(row->style != NULL);
|
|
|
|
ctx.ctx = c->select_ctx;
|
|
ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
|
|
ctx.base_url = c->base_url;
|
|
ctx.universal = c->universal;
|
|
|
|
style = nscss_get_blank_style(&ctx, row->style);
|
|
if (style == NULL)
|
|
return false;
|
|
|
|
cell = box_create(NULL, style, true, row->href,
|
|
row->target, NULL, NULL, c->bctx);
|
|
if (cell == NULL) {
|
|
css_computed_style_destroy(style);
|
|
return false;
|
|
}
|
|
cell->type = BOX_TABLE_CELL;
|
|
|
|
if (child->prev == NULL)
|
|
row->children = cell;
|
|
else
|
|
child->prev->next = cell;
|
|
|
|
cell->prev = child->prev;
|
|
|
|
while (child != NULL && (
|
|
child->type == BOX_BLOCK ||
|
|
child->type == BOX_INLINE_CONTAINER ||
|
|
child->type == BOX_TABLE ||
|
|
child->type == BOX_TABLE_ROW_GROUP ||
|
|
child->type == BOX_TABLE_ROW)) {
|
|
box_add_child(cell, child);
|
|
|
|
next_child = child->next;
|
|
child->next = NULL;
|
|
child = next_child;
|
|
}
|
|
|
|
assert(cell->last != NULL);
|
|
|
|
cell->last->next = NULL;
|
|
cell->next = next_child = child;
|
|
if (cell->next != NULL)
|
|
cell->next->prev = cell;
|
|
else
|
|
row->last = cell;
|
|
cell->parent = row;
|
|
|
|
if (box_normalise_block(cell, c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_INLINE:
|
|
case BOX_INLINE_END:
|
|
case BOX_INLINE_BLOCK:
|
|
case BOX_FLOAT_LEFT:
|
|
case BOX_FLOAT_RIGHT:
|
|
case BOX_BR:
|
|
case BOX_TEXT:
|
|
/* should have been wrapped in inline
|
|
container by convert_xml_to_box() */
|
|
assert(0);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
if (calculate_table_row(col_info, cell->columns, cell->rows,
|
|
&cell->start_column, cell) == false)
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Update row spanning details for all columns */
|
|
for (i = 0; i < col_info->num_columns; i++) {
|
|
if (col_info->spans[i].row_span != 0 &&
|
|
col_info->spans[i].auto_row == false) {
|
|
/* This cell spans rows, and is not an auto row.
|
|
* Reduce number of rows left to span */
|
|
col_info->spans[i].row_span--;
|
|
}
|
|
}
|
|
|
|
/* Reset current column for next row */
|
|
col_info->current_column = 0;
|
|
|
|
/* Increment row counter */
|
|
col_info->num_rows++;
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("row %p done", row);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute the column index at which the current cell begins.
|
|
* Additionally, update the column record to reflect row spanning.
|
|
*
|
|
* \param col_info Column record
|
|
* \param col_span Number of columns that current cell spans
|
|
* \param row_span Number of rows that current cell spans
|
|
* \param start_column Pointer to location to receive column index
|
|
* \param cell Box for current table cell
|
|
* \return true on success, false on memory exhaustion
|
|
*/
|
|
|
|
bool calculate_table_row(struct columns *col_info,
|
|
unsigned int col_span, unsigned int row_span,
|
|
unsigned int *start_column, struct box *cell)
|
|
{
|
|
unsigned int cell_start_col = col_info->current_column;
|
|
unsigned int cell_end_col;
|
|
unsigned int i;
|
|
struct span_info *spans;
|
|
struct box *rg = cell->parent->parent; /* Cell's row group */
|
|
|
|
/* Skip columns with cells spanning from above */
|
|
/* TODO: Need to ignore cells spanning from above that belong to
|
|
* different row group. We don't have that info here. */
|
|
while (col_info->spans[cell_start_col].row_span != 0 &&
|
|
col_info->spans[cell_start_col].rg == rg) {
|
|
cell_start_col++;
|
|
}
|
|
|
|
/* Update current column with calculated start */
|
|
col_info->current_column = cell_start_col;
|
|
|
|
/* If this cell has a colspan of 0, then assume 1.
|
|
* No other browser supports colspan=0, anyway. */
|
|
if (col_span == 0)
|
|
col_span = 1;
|
|
|
|
cell_end_col = cell_start_col + col_span;
|
|
|
|
if (col_info->num_columns < cell_end_col) {
|
|
/* It appears that this row has more columns than
|
|
* the maximum recorded for the table so far.
|
|
* Allocate more span records. */
|
|
spans = realloc(col_info->spans,
|
|
sizeof *spans * (cell_end_col + 1));
|
|
if (spans == NULL)
|
|
return false;
|
|
|
|
col_info->spans = spans;
|
|
col_info->num_columns = cell_end_col;
|
|
|
|
/* Mark new final column as sentinel */
|
|
col_info->spans[cell_end_col].row_span = 0;
|
|
col_info->spans[cell_end_col].auto_row = false;
|
|
}
|
|
|
|
/* This cell may span multiple columns. If it also wants to span
|
|
* multiple rows, temporarily assume it spans 1 row only. This will
|
|
* be fixed up in box_normalise_table_spans() */
|
|
for (i = cell_start_col; i < cell_end_col; i++) {
|
|
col_info->spans[i].row_span = (row_span == 0) ? 1 : row_span;
|
|
col_info->spans[i].auto_row = (row_span == 0);
|
|
col_info->spans[i].rg = rg;
|
|
}
|
|
|
|
/* Update current column with calculated end. */
|
|
col_info->current_column = cell_end_col;
|
|
|
|
*start_column = cell_start_col;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool box_normalise_inline_container(struct box *cont, html_content * c)
|
|
{
|
|
struct box *child;
|
|
struct box *next_child;
|
|
|
|
assert(cont != NULL);
|
|
assert(cont->type == BOX_INLINE_CONTAINER);
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("cont %p", cont);
|
|
#endif
|
|
|
|
for (child = cont->children; child != NULL; child = next_child) {
|
|
next_child = child->next;
|
|
switch (child->type) {
|
|
case BOX_INLINE:
|
|
case BOX_INLINE_END:
|
|
case BOX_BR:
|
|
case BOX_TEXT:
|
|
/* ok */
|
|
break;
|
|
case BOX_INLINE_BLOCK:
|
|
/* ok */
|
|
if (box_normalise_block(child, c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_FLOAT_LEFT:
|
|
case BOX_FLOAT_RIGHT:
|
|
/* ok */
|
|
assert(child->children != NULL);
|
|
|
|
switch (child->children->type) {
|
|
case BOX_BLOCK:
|
|
if (box_normalise_block(child->children,
|
|
c) == false)
|
|
return false;
|
|
break;
|
|
case BOX_TABLE:
|
|
if (box_normalise_table(child->children,
|
|
c) == false)
|
|
return false;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
if (child->children == NULL) {
|
|
/* the child has destroyed itself: remove float */
|
|
if (child->prev == NULL)
|
|
child->parent->children = child->next;
|
|
else
|
|
child->prev->next = child->next;
|
|
if (child->next != NULL)
|
|
child->next->prev = child->prev;
|
|
else
|
|
child->parent->last = child->prev;
|
|
|
|
box_free(child);
|
|
}
|
|
break;
|
|
case BOX_BLOCK:
|
|
case BOX_INLINE_CONTAINER:
|
|
case BOX_TABLE:
|
|
case BOX_TABLE_ROW_GROUP:
|
|
case BOX_TABLE_ROW:
|
|
case BOX_TABLE_CELL:
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
#ifdef BOX_NORMALISE_DEBUG
|
|
LOG("cont %p done", cont);
|
|
#endif
|
|
|
|
return true;
|
|
}
|