1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-12-04 14:46:47 -05:00
elinks/src/document/html/tables.c

1367 lines
35 KiB
C

/* HTML tables renderer */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include "elinks.h"
#include "document/html/parser/parse.h"
#include "document/html/parser/table.h"
#include "document/html/parser.h"
#include "document/html/renderer.h"
#include "document/html/tables.h"
#include "document/options.h"
#include "terminal/draw.h"
#include "util/color.h"
#include "util/conv.h"
#include "util/error.h"
#include "util/memory.h"
#include "util/string.h"
/* Unsafe macros */
#include "document/html/internal.h"
struct table_frames {
unsigned int top:1;
unsigned int bottom:1;
unsigned int left:1;
unsigned int right:1;
};
static void
get_table_frames(struct table *table, struct table_frames *result)
{
assert(table && result);
if (table->border) {
result->top = !!(table->frame & TABLE_FRAME_ABOVE);
result->bottom = !!(table->frame & TABLE_FRAME_BELOW);
result->left = !!(table->frame & TABLE_FRAME_LHS);
result->right = !!(table->frame & TABLE_FRAME_RHS);
} else {
memset(result, 0, sizeof(*result));
}
}
/* Distance of the table from the left margin. */
int
get_table_indent(struct html_context *html_context, struct table *table)
{
int width = par_elformat.width - table->real_width;
int indent;
switch (table->align) {
case ALIGN_CENTER:
indent = /*par_elformat.blockquote_level +*/ (width + par_elformat.leftmargin - par_elformat.rightmargin) / 2;
break;
case ALIGN_RIGHT:
indent = width - par_elformat.rightmargin;
break;
case ALIGN_LEFT:
case ALIGN_JUSTIFY:
default:
indent = par_elformat.leftmargin + par_elformat.blockquote_level;
}
/* Don't use int_bounds(&x, 0, width) here,
* width may be < 0. --Zas */
if (indent > width) indent = width;
if (indent < 0) indent = 0;
return indent;
}
static inline struct part *
format_cell(struct html_context *html_context, struct table *table,
struct table_cell *cell, struct document *document,
int x, int y, int width)
{
if (document) {
x += table->part->box.x;
y += table->part->box.y;
}
return format_html_part(html_context, cell->start, cell->end,
cell->align, table->cellpadding, width,
document, x, y, NULL, cell->link_num);
}
static inline void
get_cell_width(struct html_context *html_context,
char *start, char *end,
int cellpadding, int width,
int a, int *min, int *max,
int link_num, int *new_link_num)
{
struct part *part;
if (min) *min = -1;
if (max) *max = -1;
if (new_link_num) *new_link_num = link_num;
part = format_html_part(html_context, start, end, ALIGN_LEFT,
cellpadding, width, NULL,
!!a, !!a, NULL, link_num);
if (!part) return;
if (min) *min = part->box.width;
if (max) *max = part->max_width;
if (new_link_num) *new_link_num = part->link_num;
if (min && max) {
assertm(*min <= *max, "get_cell_width: %d > %d", *min, *max);
}
mem_free(part);
}
static void
get_cell_widths(struct html_context *html_context, struct table *table)
{
int link_num = table->part->link_num;
if (!html_context->options->table_order) {
int col, row;
for (row = 0; row < table->rows; row++)
for (col = 0; col < table->cols; col++) {
struct table_cell *cell = CELL(table, col, row);
if (!cell->start) continue;
cell->link_num = link_num;
get_cell_width(html_context, cell->start,
cell->end, table->cellpadding,
0, 0, &cell->min_width,
&cell->max_width,
link_num, &link_num);
}
} else {
int col, row;
for (col = 0; col < table->cols; col++)
for (row = 0; row < table->rows; row++) {
struct table_cell *cell = CELL(table, col, row);
if (!cell->start) continue;
cell->link_num = link_num;
get_cell_width(html_context, cell->start,
cell->end, table->cellpadding,
0, 0, &cell->min_width,
&cell->max_width,
link_num, &link_num);
}
}
table->link_num = link_num;
}
static inline void
distribute_values(int *values, int count, int wanted, int *limits)
{
int i;
int sum = 0, d, r, t;
for (i = 0; i < count; i++) sum += values[i];
if (sum >= wanted) return;
again:
t = wanted - sum;
d = t / count;
r = t % count;
wanted = 0;
if (limits) {
for (i = 0; i < count; i++) {
int delta;
values[i] += d + (i < r);
delta = values[i] - limits[i];
if (delta > 0) {
wanted += delta;
values[i] = limits[i];
}
}
} else {
for (i = 0; i < count; i++) {
values[i] += d + (i < r);
}
}
if (wanted) {
assertm(limits != NULL, "bug in distribute_values()");
limits = NULL;
sum = 0;
goto again;
}
}
/* Returns: -1 none, 0, space, 1 line, 2 double */
static inline int
get_vline_width(struct table *table, int col)
{
int width = 0;
if (!col) return -1;
if (table->rules == TABLE_RULE_COLS || table->rules == TABLE_RULE_ALL)
width = table->cellspacing;
else if (table->rules == TABLE_RULE_GROUPS)
width = (col < table->columns_count && table->columns[col].group);
if (!width && table->cellpadding) width = -1;
return width;
}
static int
get_hline_width(struct table *table, int row)
{
if (!row) return -1;
if (table->rules == TABLE_RULE_ROWS || table->rules == TABLE_RULE_ALL) {
if (table->cellspacing || table->vcellpadding)
return table->cellspacing;
return -1;
} else if (table->rules == TABLE_RULE_GROUPS) {
int col;
for (col = 0; col < table->cols; col++)
if (CELL(table, col, row)->is_group) {
if (table->cellspacing || table->vcellpadding)
return table->cellspacing;
return -1;
}
}
return table->vcellpadding ? 0 : -1;
}
#define has_vline_width(table, col) (get_vline_width(table, col) >= 0)
#define has_hline_width(table, row) (get_hline_width(table, row) >= 0)
static int
get_column_widths(struct table *table)
{
int colspan;
if (!table->cols) return -1; /* prevents calloc(0, ...) calls */
if (!table->min_cols_widths) {
table->min_cols_widths = (int *)mem_calloc(table->cols, sizeof(*table->min_cols_widths));
if (!table->min_cols_widths) return -1;
}
if (!table->max_cols_widths) {
table->max_cols_widths = (int *)mem_calloc(table->cols, sizeof(*table->max_cols_widths));
if (!table->max_cols_widths) {
mem_free_set(&table->min_cols_widths, NULL);
return -1;
}
}
if (!table->cols_widths) {
table->cols_widths = (int *)mem_calloc(table->cols, sizeof(*table->cols_widths));
if (!table->cols_widths) {
mem_free_set(&table->min_cols_widths, NULL);
mem_free_set(&table->max_cols_widths, NULL);
return -1;
}
}
colspan = 1;
do {
int col, row;
int new_colspan = INT_MAX;
for (col = 0; col < table->cols; col++) for (row = 0; row < table->rows; row++) {
struct table_cell *cell = CELL(table, col, row);
if (cell->is_spanned || !cell->is_used) continue;
assertm(cell->colspan + col <= table->cols, "colspan out of table");
if_assert_failed return -1;
if (cell->colspan == colspan) {
int k, p = 0;
for (k = 1; k < colspan; k++)
p += has_vline_width(table, col + k);
distribute_values(&table->min_cols_widths[col],
colspan,
cell->min_width - p,
&table->max_cols_widths[col]);
distribute_values(&table->max_cols_widths[col],
colspan,
cell->max_width - p,
NULL);
for (k = 0; k < colspan; k++) {
int tmp = col + k;
int_lower_bound(&table->max_cols_widths[tmp],
table->min_cols_widths[tmp]);
}
} else if (cell->colspan > colspan
&& cell->colspan < new_colspan) {
new_colspan = cell->colspan;
}
}
colspan = new_colspan;
} while (colspan != INT_MAX);
return 0;
}
static void
get_table_width(struct table *table)
{
struct table_frames table_frames;
int min = 0;
int max = 0;
int col;
for (col = 0; col < table->cols; col++) {
int vl = has_vline_width(table, col);
min += vl + table->min_cols_widths[col];
max += vl + table->max_cols_widths[col];
if (table->cols_x[col] > table->max_cols_widths[col])
max += table->cols_x[col];
}
get_table_frames(table, &table_frames);
table->min_width = min + table_frames.left + table_frames.right;
table->max_width = max + table_frames.left + table_frames.right;
assertm(min <= max, "min(%d) > max(%d)", min, max);
/* XXX: Recovery path? --pasky */
}
/* Initialize @width and @max_width arrays depending on the @stretch_method */
static inline int
apply_stretch_method(struct table *table, int widths[], int max_widths[],
int stretch_method, int max_cols_width)
{
int col, total_width = 0;
for (col = 0; col < table->cols; col++) {
switch (stretch_method) {
case 0:
if (table->cols_widths[col] >= table->cols_x[col])
break;
widths[col] = 1;
max_widths[col] = int_min(table->cols_x[col],
table->max_cols_widths[col])
- table->cols_widths[col];
if (max_widths[col] <= 0) widths[col] = 0;
break;
case 1:
if (table->cols_x[col] > WIDTH_RELATIVE)
break;
widths[col] = WIDTH_RELATIVE - table->cols_x[col];
max_widths[col] = table->max_cols_widths[col]
- table->cols_widths[col];
if (max_widths[col] <= 0) widths[col] = 0;
break;
case 2:
if (table->cols_x[col] != WIDTH_AUTO)
break;
/* Fall-through */
case 3:
if (table->cols_widths[col] >= table->max_cols_widths[col])
break;
max_widths[col] = table->max_cols_widths[col]
- table->cols_widths[col];
if (max_cols_width) {
widths[col] = 5 + table->max_cols_widths[col] * 10 / max_cols_width;
} else {
widths[col] = 1;
}
break;
case 4:
if (table->cols_x[col] < 0)
break;
widths[col] = 1;
max_widths[col] = table->cols_x[col]
- table->cols_widths[col];
if (max_widths[col] <= 0) widths[col] = 0;
break;
case 5:
if (table->cols_x[col] >= 0)
break;
if (table->cols_x[col] <= WIDTH_RELATIVE) {
widths[col] = WIDTH_RELATIVE - table->cols_x[col];
} else {
widths[col] = 1;
}
max_widths[col] = INT_MAX;
break;
case 6:
widths[col] = 1;
max_widths[col] = INT_MAX;
break;
default:
return -1;
}
total_width += widths[col];
}
return total_width;
}
/* Stretches the table columns by distributed the @spare_width among them.
* Returns how much of @spare_width was actually distributed. */
static inline int
stretch_columns(struct table *table, int widths[], int max_widths[],
int spare_width, int total_width)
{
int total_spare_width = spare_width;
while (spare_width) {
int stretch_width = 0;
int stretch_col = -1;
int col;
for (col = 0; col < table->cols; col++) {
int col_spare_width;
if (!widths[col])
continue;
col_spare_width = total_spare_width * widths[col] / total_width;
int_bounds(&col_spare_width, 1, max_widths[col]);
if (col_spare_width > stretch_width) {
stretch_width = col_spare_width;
stretch_col = col;
}
}
/* Got stretch column? */
if (stretch_col == -1)
break;
/* Mark the column as visited */
widths[stretch_col] = 0;
if (stretch_width > spare_width)
stretch_width = spare_width;
assertm(stretch_width >= 0, "shrinking cell");
table->cols_widths[stretch_col] += stretch_width;
spare_width -= stretch_width;
}
return total_spare_width - spare_width;
}
/* This function distributes space evenly between @table columns so that it
* stretches to @width. */
static void
distribute_widths(struct table *table, int width)
{
int col;
int spare_width = width - table->min_width;
int stretch_method = 0;
int *widths, *max_widths;
int max_cols_width = 0;
int cols_array_size;
if (!table->cols)
return;
assertm(spare_width >= 0, "too small width %d, required %d",
width, table->min_width);
for (col = 0; col < table->cols; col++)
int_lower_bound(&max_cols_width, table->max_cols_widths[col]);
cols_array_size = table->cols * sizeof(*table->cols_widths);
memcpy(table->cols_widths, table->min_cols_widths, cols_array_size);
table->real_width = width;
widths = (int *)fmem_alloc(cols_array_size);
if (!widths) return;
max_widths = (int *)fmem_alloc(cols_array_size);
if (!max_widths) goto free_widths;
while (spare_width) {
int stretched, total_width;
memset(widths, 0, cols_array_size);
memset(max_widths, 0, cols_array_size);
total_width = apply_stretch_method(table, widths, max_widths, stretch_method, max_cols_width);
assertm(total_width != -1, "Could not expand table");
if_assert_failed break;
if (!total_width) {
stretch_method++;
continue;
}
stretched = stretch_columns(table, widths, max_widths, spare_width, total_width);
if (!stretched)
stretch_method++;
else
spare_width -= stretched;
}
fmem_free(max_widths);
free_widths:
fmem_free(widths);
}
int
get_table_cellpadding(struct html_context *html_context, struct table *table)
{
struct part *part = table->part;
int cpd_pass = 0, cpd_width = 0, cpd_last = table->cellpadding;
int margins = /*par_elformat.blockquote_level +*/ par_elformat.leftmargin + par_elformat.rightmargin;
again:
get_cell_widths(html_context, table);
if (get_column_widths(table)) return -1;
get_table_width(table);
if (!part->document && !part->box.x) {
if (!table->full_width)
int_upper_bound(&table->max_width, table->width);
int_lower_bound(&table->max_width, table->min_width);
int_lower_bound(&part->max_width, table->max_width + margins);
int_lower_bound(&part->box.width, table->min_width + margins);
return -1;
}
if (!cpd_pass && table->min_width > table->width && table->cellpadding) {
table->cellpadding = 0;
cpd_pass = 1;
cpd_width = table->min_width;
goto again;
}
if (cpd_pass == 1 && table->min_width > cpd_width) {
table->cellpadding = cpd_last;
cpd_pass = 2;
goto again;
}
return 0;
}
#ifdef HTML_TABLE_2ND_PASS /* This is by default ON! (<setup.h>) */
void
check_table_widths(struct html_context *html_context, struct table *table)
{
int col, row;
int colspan;
int width, new_width;
int max, max_index = 0; /* go away, warning! */
int *widths = (int *)mem_calloc(table->cols, sizeof(*widths));
if (!widths) return;
for (row = 0; row < table->rows; row++) for (col = 0; col < table->cols; col++) {
struct table_cell *cell = CELL(table, col, row);
int k, p = 0;
if (!cell->start) continue;
for (k = 0; k < cell->colspan; k++) {
#ifdef __TINYC__
p += table->cols_widths[col + k];
if (k) p+= (has_vline_width(table, col + k));
#else
p += table->cols_widths[col + k] +
(k && has_vline_width(table, col + k));
#endif
}
get_cell_width(html_context, cell->start, cell->end,
table->cellpadding, p, 1, &cell->width,
NULL, cell->link_num, NULL);
int_upper_bound(&cell->width, p);
}
colspan = 1;
do {
int new_colspan = INT_MAX;
for (col = 0; col < table->cols; col++) for (row = 0; row < table->rows; row++) {
struct table_cell *cell = CELL(table, col, row);
if (!cell->start) continue;
assertm(cell->colspan + col <= table->cols, "colspan out of table");
if_assert_failed goto end;
if (cell->colspan == colspan) {
int k, p = 0;
for (k = 1; k < colspan; k++)
p += has_vline_width(table, col + k);
distribute_values(&widths[col],
colspan,
cell->width - p,
&table->max_cols_widths[col]);
} else if (cell->colspan > colspan
&& cell->colspan < new_colspan) {
new_colspan = cell->colspan;
}
}
colspan = new_colspan;
} while (colspan != INT_MAX);
width = new_width = 0;
for (col = 0; col < table->cols; col++) {
width += table->cols_widths[col];
new_width += widths[col];
}
if (new_width > width) {
/* INTERNAL("new width(%d) is larger than previous(%d)", new_width, width); */
goto end;
}
max = -1;
for (col = 0; col < table->cols; col++)
if (table->max_cols_widths[col] > max) {
max = table->max_cols_widths[col];
max_index = col;
}
if (max != -1) {
widths[max_index] += width - new_width;
if (widths[max_index] <= table->max_cols_widths[max_index]) {
mem_free(table->cols_widths);
table->cols_widths = widths;
return;
}
}
end:
mem_free(widths);
}
#endif
static void
check_table_height(struct table *table, struct table_frames *frames, int y)
{
#ifndef CONFIG_FASTMEM
/* XXX: Cannot we simply use the @yp value we just calculated
* in draw_table_cells()? --pasky */
int old_height = table->real_height + table->part->cy;
int our_height = frames->top + y + frames->bottom + table->caption_height;
int row;
/* XXX: We cannot use get_table_real_height() because we are
* looking one row ahead - which is completely arcane to me.
* It makes a difference only when a table uses ruler="groups"
* and has non-zero cellspacing or vcellpadding. --pasky */
for (row = 0; row < table->rows; row++) {
#ifdef __TINYC__
our_height += table->rows_heights[row];
if (row < table->rows - 1 && has_hline_width(table, row + 1))
our_height++;
#else
our_height += table->rows_heights[row] +
(row < table->rows - 1 &&
has_hline_width(table, row + 1));
#endif
}
assertm(old_height == our_height, "size not matching! %d vs %d",
old_height, our_height);
#endif
}
static int
get_table_caption_height(struct html_context *html_context, struct table *table)
{
char *start = table->caption.start;
char *end = table->caption.end;
struct part *part;
if (!start || !end) return 0;
while (start < end && isspace((unsigned char)*start))
start++;
while (start < end && isspace((unsigned char)end[-1]))
end--;
if (start >= end) return 0;
part = format_html_part(html_context, start, end, table->align,
0, table->real_width, NULL, 0, 0,
NULL, table->link_num);
if (!part) {
return 0;
} else {
int height = part->box.height;
mem_free(part);
return height;
}
}
static int
get_table_real_height(struct table *table)
{
struct table_frames table_frames;
int height;
int row;
get_table_frames(table, &table_frames);
height = table_frames.top + table_frames.bottom;
height += table->caption_height;
for (row = 0; row < table->rows; row++) {
height += table->rows_heights[row];
if (row && has_hline_width(table, row))
height++;
}
return height;
}
void
get_table_heights(struct html_context *html_context, struct table *table)
{
int rowspan;
int col, row;
table->caption_height = get_table_caption_height(html_context, table);
for (row = 0; row < table->rows; row++) {
for (col = 0; col < table->cols; col++) {
struct table_cell *cell = CELL(table, col, row);
struct part *part;
int width = 0, sp;
if (!cell->is_used || cell->is_spanned) continue;
for (sp = 0; sp < cell->colspan; sp++) {
#ifdef __TINYC__
width += table->cols_widths[col + sp];
if (sp < cell->colspan - 1)
width += (has_vline_width(table, col + sp + 1));
#else
width += table->cols_widths[col + sp] +
(sp < cell->colspan - 1 &&
has_vline_width(table, col + sp + 1));
#endif
}
part = format_cell(html_context, table, cell, NULL,
2, 2, width);
if (!part) return;
cell->height = part->box.height;
/* DBG("%d, %d.", width, cell->height); */
mem_free(part);
}
}
rowspan = 1;
do {
int new_rowspan = INT_MAX;
for (row = 0; row < table->rows; row++) {
for (col = 0; col < table->cols; col++) {
struct table_cell *cell = CELL(table, col, row);
if (!cell->is_used || cell->is_spanned) continue;
if (cell->rowspan == rowspan) {
int k, p = 0;
for (k = 1; k < rowspan; k++)
p += has_hline_width(table, row + k);
distribute_values(&table->rows_heights[row],
rowspan,
cell->height - p,
NULL);
} else if (cell->rowspan > rowspan &&
cell->rowspan < new_rowspan) {
new_rowspan = cell->rowspan;
}
}
}
rowspan = new_rowspan;
} while (rowspan != INT_MAX);
table->real_height = get_table_real_height(table);
}
void
draw_table_cell(struct table *table, int col, int row, int x, int y,
struct html_context *html_context)
{
struct table_cell *cell = CELL(table, col, row);
struct document *document = table->part->document;
struct part *part;
int width = 0;
int height = 0;
int s, tmpy = y;
struct html_element *state;
if (!cell->start) return;
for (s = 0; s < cell->colspan; s++) {
#ifdef __TINYC__
width += table->cols_widths[col + s];
if (s < cell->colspan - 1)
width += (has_vline_width(table, col + s + 1));
#else
width += table->cols_widths[col + s] +
(s < cell->colspan - 1 &&
has_vline_width(table, col + s + 1));
#endif
}
for (s = 0; s < cell->rowspan; s++) {
#ifdef __TINYC__
height += table->rows_heights[row + s];
if (s < cell->rowspan - 1)
height += (has_hline_width(table, row + s + 1));
#else
height += table->rows_heights[row + s] +
(s < cell->rowspan - 1 &&
has_hline_width(table, row + s + 1));
#endif
}
state = (struct html_element *)init_html_parser_state(html_context, ELEMENT_DONT_KILL,
cell->align, 0, 0);
if (cell->is_header) elformat.style.attr |= AT_BOLD;
elformat.style.color.background = cell->bgcolor;
par_elformat.color.background = cell->bgcolor;
if (cell->valign == VALIGN_MIDDLE)
tmpy += (height - cell->height) / 2;
else if (cell->valign == VALIGN_BOTTOM)
tmpy += (height - cell->height);
part = format_cell(html_context, table, cell, document, x, tmpy, width);
if (part) {
/* The cell content doesn't necessarily fill out the whole cell
* height so use the calculated @height because it is an upper
* bound. */
assert(height >= cell->height);
/* The line expansion draws the _remaining_ background color of
* both untouched lines and lines that doesn't stretch the
* whole cell width. */
expand_lines(html_context, table->part, x + width - 1, y, height, cell->bgcolor);
if (cell->fragment_id)
add_fragment_identifier(html_context, part,
cell->fragment_id);
}
done_html_parser_state(html_context, state);
mem_free_if(part);
}
void
draw_table_cells(struct table *table, int x, int y,
struct html_context *html_context)
{
int col, row;
int xp;
color_T bgcolor = par_elformat.color.background;
struct table_frames table_frames;
get_table_frames(table, &table_frames);
if (table->fragment_id)
add_fragment_identifier(html_context, table->part,
table->fragment_id);
/* Expand using the background color of the ``parent context'' all the
* way down the start of the left edge of the table. */
expand_lines(html_context, table->part, x - 1, y, table->real_height, bgcolor);
xp = x + table_frames.left;
for (col = 0; col < table->cols; col++) {
int yp = y + table_frames.top;
for (row = 0; row < table->rows; row++) {
int row_height = table->rows_heights[row] +
(row < table->rows - 1 && has_hline_width(table, row + 1));
draw_table_cell(table, col, row, xp, yp, html_context);
yp += row_height;
}
if (col < table->cols - 1) {
xp += table->cols_widths[col] + has_vline_width(table, col + 1);
}
}
/* Finish the table drawing by aligning the right and bottom edge of
* the table */
x += table->real_width - 1;
expand_lines(html_context, table->part, x, y, table->real_height, table->color.background);
/* Tables are renderer column-wise which breaks forms where the
* form items appears in a column before the actual form tag is
* parsed. Consider the folloing example:
*
* +--------+--------+ Where cell 2 has a <form>-tag
* | cell 1 | cell 2 | and cell 3 has an <input>-tag.
* +--------+--------+
* | cell 3 | cell 4 | The table is rendered by drawing
* +--------+--------+ the cells in the order: 1, 3, 2, 4.
*
* That is the <input>-tag form-item is added before the <form>-tag,
* which means a ``dummy'' form to hold it is added too.
* Calling check_html_form_hierarchy() will join the the form-item
* to the correct form from cell 2. */
check_html_form_hierarchy(table->part);
/* Do a sanity check whether the height is correct */
check_table_height(table, &table_frames, y);
}
static inline int
get_frame_pos(int a, int a_size, int b, int b_size)
{
assert(a >= -1 || a < a_size + 2 || b >= 0 || b <= b_size);
if_assert_failed return 0;
return a + 1 + (a_size + 2) * b;
}
#define H_FRAME_POSITION(table, col, row) frame[0][get_frame_pos(col, (table)->cols, row, (table)->rows)]
#define V_FRAME_POSITION(table, col, row) frame[1][get_frame_pos(row, (table)->rows, col, (table)->cols)]
static inline void
draw_frame_point(struct table *table, signed char *frame[2], int x, int y,
int col, int row, struct html_context *html_context)
{
static border_char_T const border_chars[81] = {
BORDER_NONE, BORDER_SVLINE, BORDER_DVLINE,
BORDER_SHLINE, BORDER_SDLCORNER, BORDER_DSDLCORNER,
BORDER_DHLINE, BORDER_SDDLCORNER, BORDER_DDLCORNER,
BORDER_SHLINE, BORDER_SDRCORNER, BORDER_DSDRCORNER,
BORDER_SHLINE, BORDER_SUTEE, BORDER_DSUTEE,
BORDER_DHLINE, BORDER_SDDLCORNER, BORDER_DDLCORNER,
BORDER_DHLINE, BORDER_SDDRCORNER, BORDER_DDRCORNER,
BORDER_DHLINE, BORDER_SDDRCORNER, BORDER_DDRCORNER,
BORDER_DHLINE, BORDER_SDUTEE, BORDER_DUTEE,
BORDER_SVLINE, BORDER_SVLINE, BORDER_DVLINE,
BORDER_SULCORNER, BORDER_SRTEE, BORDER_DSDLCORNER,
BORDER_SDULCORNER, BORDER_SDRTEE, BORDER_DDLCORNER,
BORDER_SURCORNER, BORDER_SLTEE, BORDER_DSDRCORNER,
BORDER_SDTEE, BORDER_SCROSS, BORDER_DSUTEE,
BORDER_SDULCORNER, BORDER_SDRTEE, BORDER_DDLCORNER,
BORDER_SDURCORNER, BORDER_SDLTEE, BORDER_DDRCORNER,
BORDER_SDURCORNER, BORDER_SDLTEE, BORDER_DDRCORNER,
BORDER_SDDTEE, BORDER_SDCROSS, BORDER_DUTEE,
BORDER_DVLINE, BORDER_DVLINE, BORDER_DVLINE,
BORDER_DSULCORNER, BORDER_DSULCORNER, BORDER_DSRTEE,
BORDER_DULCORNER, BORDER_DULCORNER, BORDER_DRTEE,
BORDER_DSURCORNER, BORDER_DSURCORNER, BORDER_DSLTEE,
BORDER_DSDTEE, BORDER_DSDTEE, BORDER_DSCROSS,
BORDER_DULCORNER, BORDER_DULCORNER, BORDER_DRTEE,
BORDER_DURCORNER, BORDER_DURCORNER, BORDER_DLTEE,
BORDER_DURCORNER, BORDER_DURCORNER, BORDER_DLTEE,
BORDER_DDTEE, BORDER_DDTEE, BORDER_DCROSS,
};
/* Note: I have no clue wether any of these names are suitable but they
* should give an idea of what is going on. --jonas */
signed char left = H_FRAME_POSITION(table, col - 1, row);
signed char right = H_FRAME_POSITION(table, col, row);
signed char top = V_FRAME_POSITION(table, col, row - 1);
signed char bottom = V_FRAME_POSITION(table, col, row);
int pos;
if (left < 0 && right < 0 && top < 0 && bottom < 0) return;
pos = int_max(top, 0)
+ 3 * int_max(right, 0)
+ 9 * int_max(left, 0)
+ 27 * int_max(bottom, 0);
draw_frame_hchars(table->part, x, y, 1, border_chars[pos],
par_elformat.color.background, table->color.border,
html_context);
}
static inline void
draw_frame_hline(struct table *table, signed char *frame[2], int x, int y,
int col, int row, struct html_context *html_context)
{
static unsigned char const hltable[] = { ' ', BORDER_SHLINE, BORDER_DHLINE };
int pos = H_FRAME_POSITION(table, col, row);
assertm(pos < 3, "Horizontal table position out of bound [%d]", pos);
if_assert_failed return;
if (pos < 0 || table->cols_widths[col] <= 0) return;
draw_frame_hchars(table->part, x, y, table->cols_widths[col], hltable[pos],
par_elformat.color.background, table->color.border, html_context);
}
static inline void
draw_frame_vline(struct table *table, signed char *frame[2], int x, int y,
int col, int row, struct html_context *html_context)
{
static unsigned char const vltable[] = { ' ', BORDER_SVLINE, BORDER_DVLINE };
int pos = V_FRAME_POSITION(table, col, row);
assertm(pos < 3, "Vertical table position out of bound [%d]", pos);
if_assert_failed return;
if (pos < 0 || table->rows_heights[row] <= 0) return;
draw_frame_vchars(table->part, x, y, table->rows_heights[row], vltable[pos],
par_elformat.color.background, table->color.border, html_context);
}
static inline int
table_row_has_group(struct table *table, int row)
{
int col;
for (col = 0; col < table->cols; col++)
if (CELL(table, col, row)->is_group)
return 1;
return 0;
}
static void
init_table_rules(struct table *table, signed char *frame[2])
{
int col, row;
for (row = 0; row < table->rows; row++) for (col = 0; col < table->cols; col++) {
int xsp, ysp;
struct table_cell *cell = CELL(table, col, row);
if (!cell->is_used || cell->is_spanned) continue;
xsp = cell->colspan ? cell->colspan : table->cols - col;
ysp = cell->rowspan ? cell->rowspan : table->rows - row;
if (table->rules != TABLE_RULE_COLS) {
memset(&H_FRAME_POSITION(table, col, row), table->cellspacing, xsp);
memset(&H_FRAME_POSITION(table, col, row + ysp), table->cellspacing, xsp);
}
if (table->rules != TABLE_RULE_ROWS) {
memset(&V_FRAME_POSITION(table, col, row), table->cellspacing, ysp);
memset(&V_FRAME_POSITION(table, col + xsp, row), table->cellspacing, ysp);
}
}
if (table->rules == TABLE_RULE_GROUPS) {
for (col = 1; col < table->cols; col++) {
if (table->cols_x[col])
continue;
memset(&V_FRAME_POSITION(table, col, 0), 0, table->rows);
}
for (row = 1; row < table->rows; row++) {
if (table_row_has_group(table, row))
continue;
memset(&H_FRAME_POSITION(table, 0, row), 0, table->cols);
}
}
}
void
draw_table_frames(struct table *table, int indent, int y,
struct html_context *html_context)
{
struct table_frames table_frames;
signed char *frame[2];
int col, row;
int cx, cy;
int fh_size = (table->cols + 2) * (table->rows + 1);
int fv_size = (table->cols + 1) * (table->rows + 2);
frame[0] = (signed char *)fmem_alloc(fh_size + fv_size);
if (!frame[0]) return;
memset(frame[0], -1, fh_size + fv_size);
frame[1] = &frame[0][fh_size];
if (table->rules != TABLE_RULE_NONE)
init_table_rules(table, frame);
get_table_frames(table, &table_frames);
memset(&H_FRAME_POSITION(table, 0, 0), table_frames.top, table->cols);
memset(&H_FRAME_POSITION(table, 0, table->rows), table_frames.bottom, table->cols);
memset(&V_FRAME_POSITION(table, 0, 0), table_frames.left, table->rows);
memset(&V_FRAME_POSITION(table, table->cols, 0), table_frames.right, table->rows);
cy = y;
for (row = 0; row <= table->rows; row++) {
cx = indent;
if ((row > 0 && row < table->rows && has_hline_width(table, row))
|| (row == 0 && table_frames.top)
|| (row == table->rows && table_frames.bottom)) {
int w = table_frames.left ? table->border : -1;
for (col = 0; col < table->cols; col++) {
if (col > 0)
w = get_vline_width(table, col);
if (w >= 0) {
draw_frame_point(table, frame, cx, cy, col, row,
html_context);
if (row < table->rows)
draw_frame_vline(table, frame, cx, cy + 1, col, row, html_context);
cx++;
}
draw_frame_hline(table, frame, cx, cy, col, row,
html_context);
cx += table->cols_widths[col];
}
if (table_frames.right) {
draw_frame_point(table, frame, cx, cy, col, row,
html_context);
if (row < table->rows)
draw_frame_vline(table, frame, cx, cy + 1, col, row, html_context);
cx++;
}
cy++;
} else if (row < table->rows) {
for (col = 0; col <= table->cols; col++) {
if ((col > 0 && col < table->cols && has_vline_width(table, col))
|| (col == 0 && table_frames.left)
|| (col == table->cols && table_frames.right)) {
draw_frame_vline(table, frame, cx, cy, col, row, html_context);
cx++;
}
if (col < table->cols) cx += table->cols_widths[col];
}
}
if (row < table->rows) cy += table->rows_heights[row];
}
fmem_free(frame[0]);
}
void
draw_table_caption(struct html_context *html_context, struct table *table,
int x, int y)
{
char *start = table->caption.start;
char *end = table->caption.end;
struct part *part;
if (!start || !end) return;
while (start < end && isspace((unsigned char)*start))
start++;
while (start < end && isspace((unsigned char)end[-1]))
end--;
if (start >= end) return;
part = format_html_part(html_context, start, end, table->align,
0, table->real_width, table->part->document, x, y,
NULL, table->link_num);
if (!part) return;
if (par_elformat.blockquote_level) {
int yy;
for (yy = 0; yy < part->box.height; yy++) {
draw_blockquote_chars(table->part, y + yy, html_context);
}
}
table->part->cy += part->box.height;
table->part->cx = -1;
table->part->link_num = part->link_num;
mem_free(part);
}
/* This renders tag soup elements that the parser detected while chewing it's
* way through the table HTML. */
static void
draw_table_bad_html(struct html_context *html_context, struct table *table)
{
int i;
for (i = 0; i < table->bad_html_size; i++) {
struct html_start_end *html = &table->bad_html[i];
char *start = html->start;
char *end = html->end;
while (start < end && isspace((unsigned char)*start))
start++;
while (start < end && isspace((unsigned char)end[-1]))
end--;
if (start >= end) continue;
parse_html(start, end, table->part, NULL, html_context);
}
}
void
distribute_table_widths(struct table *table)
{
int width = table->width;
if (table->min_width >= width)
width = table->min_width;
else if (table->max_width < width && table->full_width)
width = table->max_width;
distribute_widths(table, width);
}
void
format_table(char *attr, char *html, char *eof,
char **end, struct html_context *html_context)
{
struct part *part = html_context->part;
struct table *table;
struct node *node, *new_node;
struct html_element *state;
int indent, margins;
html_context->table_level++;
table = parse_table(html, eof, end, attr, (part->document || part->box.x),
html_context);
if (!table) goto ret0;
table->part = part;
/* XXX: This tag soup handling needs to be done outside the create
* parser state. Something to do with link numbering. */
/* It needs to be done _before_ processing the actual table, too.
* Otherwise i.e. <form> tags between <table> and <tr> are broken. */
draw_table_bad_html(html_context, table);
state = (struct html_element *)init_html_parser_state(html_context, ELEMENT_DONT_KILL,
ALIGN_LEFT, 0, 0);
margins = /*par_elformat.blockquote_level + */par_elformat.leftmargin + par_elformat.rightmargin;
if (get_table_cellpadding(html_context, table)) goto ret2;
distribute_table_widths(table);
if (!part->document && part->box.x == 1) {
int total_width = table->real_width + margins;
int_bounds(&total_width, table->real_width, par_elformat.width);
int_lower_bound(&part->box.width, total_width);
part->cy += table->real_height;
goto ret2;
}
#ifdef HTML_TABLE_2ND_PASS
check_table_widths(html_context, table);
#endif
get_table_heights(html_context, table);
if (!part->document) {
int_lower_bound(&part->box.width, table->real_width + margins);
part->cy += table->real_height;
goto ret2;
}
node = (struct node *)part->document->nodes.next;
node->box.height = part->box.y - node->box.y + part->cy;
indent = get_table_indent(html_context, table);
/* FIXME: See bug 432. It should be possible to align the caption at
* the top, bottom or the sides. */
draw_table_caption(html_context, table, indent + part->box.x, part->box.y + part->cy);
draw_table_cells(table, indent, part->cy, html_context);
draw_table_frames(table, indent, part->cy, html_context);
part->cy += table->real_height;
part->cx = -1;
new_node = (struct node *)mem_alloc(sizeof(*new_node));
if (new_node) {
set_box(&new_node->box, node->box.x, part->box.y + part->cy,
node->box.width, 0);
add_to_list(part->document->nodes, new_node);
}
ret2:
part->link_num = table->link_num;
int_lower_bound(&part->box.height, part->cy);
html_context->part = part; /* Might've changed in draw_table_cells(). */
done_html_parser_state(html_context, state);
free_table(table);
ret0:
html_context->table_level--;
if (!html_context->table_level) free_table_cache();
}