/* HTML tables renderer */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #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. */ static int get_table_indent(struct html_context *html_context, struct table *table) { int width = par_format.width - table->real_width; int indent; switch (table->align) { case ALIGN_CENTER: indent = (width + par_format.leftmargin - par_format.rightmargin) / 2; break; case ALIGN_RIGHT: indent = width - par_format.rightmargin; break; case ALIGN_LEFT: case ALIGN_JUSTIFY: default: indent = par_format.leftmargin; } /* 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, unsigned char *start, unsigned 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, "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 = 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 = 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 = 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 = fmem_alloc(cols_array_size); if (!widths) return; max_widths = 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); } static 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_format.leftmargin + par_format.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! () */ static 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 = 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++) { p += table->cols_widths[col + k] + (k && has_vline_width(table, col + k)); } 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++) { our_height += table->rows_heights[row] + (row < table->rows - 1 && has_hline_width(table, row + 1)); } 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) { unsigned char *start = table->caption.start; unsigned char *end = table->caption.end; struct part *part; if (!start || !end) return 0; while (start < end && isspace(*start)) start++; while (start < end && isspace(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; } static 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++) { width += table->cols_widths[col + sp] + (sp < cell->colspan - 1 && has_vline_width(table, col + sp + 1)); } 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); } static 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++) { width += table->cols_widths[col + s] + (s < cell->colspan - 1 && has_vline_width(table, col + s + 1)); } for (s = 0; s < cell->rowspan; s++) { height += table->rows_heights[row + s] + (s < cell->rowspan - 1 && has_hline_width(table, row + s + 1)); } state = init_html_parser_state(html_context, ELEMENT_DONT_KILL, cell->align, 0, 0); if (cell->is_header) format.style.attr |= AT_BOLD; format.style.bg = cell->bgcolor; par_format.bgcolor = 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); if (part) mem_free(part); } static 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_format.bgcolor; 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->bgcolor); /* 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
-tag * | cell 1 | cell 2 | and cell 3 has an -tag. * +--------+--------+ * | cell 3 | cell 4 | The table is rendered by drawing * +--------+--------+ the cells in the order: 1, 3, 2, 4. * * That is the -tag form-item is added before the -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) { /* TODO: Use /BORDER._.* / macros ! --pasky */ static unsigned char const border_chars[81] = { 0x00, 0xb3, 0xba, 0xc4, 0xc0, 0xd3, 0xcd, 0xd4, 0xc8, 0xc4, 0xd9, 0xbd, 0xc4, 0xc1, 0xd0, 0xcd, 0xd4, 0xc8, 0xcd, 0xbe, 0xbc, 0xcd, 0xbe, 0xbc, 0xcd, 0xcf, 0xca, 0xb3, 0xb3, 0xba, 0xda, 0xc3, 0xd3, 0xd5, 0xc6, 0xc8, 0xbf, 0xb4, 0xbd, 0xc2, 0xc5, 0xd0, 0xd5, 0xc6, 0xc8, 0xb8, 0xb5, 0xbc, 0xb8, 0xb5, 0xbc, 0xd1, 0xd8, 0xca, 0xba, 0xba, 0xba, 0xd6, 0xd6, 0xc7, 0xc9, 0xc9, 0xcc, 0xb7, 0xb7, 0xb6, 0xd2, 0xd2, 0xd7, 0xc9, 0xc9, 0xcc, 0xbb, 0xbb, 0xb9, 0xbb, 0xbb, 0xb9, 0xcb, 0xcb, 0xce, }; /* 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_format.bgcolor, table->bordercolor, 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_format.bgcolor, table->bordercolor, 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_format.bgcolor, table->bordercolor, 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); } } } static 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] = 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]); } static void draw_table_caption(struct html_context *html_context, struct table *table, int x, int y) { unsigned char *start = table->caption.start; unsigned char *end = table->caption.end; struct part *part; if (!start || !end) return; while (start < end && isspace(*start)) start++; while (start < end && isspace(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) { 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]; unsigned char *start = html->start; unsigned char *end = html->end; while (start < end && isspace(*start)) start++; while (start < end && isspace(end[-1])) end--; if (start >= end) continue; parse_html(start, end, table->part, NULL, html_context); } } void format_table(unsigned char *attr, unsigned char *html, unsigned char *eof, unsigned 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. tags between and are broken. */ draw_table_bad_html(html_context, table); state = init_html_parser_state(html_context, ELEMENT_DONT_KILL, ALIGN_LEFT, 0, 0); margins = par_format.leftmargin + par_format.rightmargin; if (get_table_cellpadding(html_context, table)) goto ret2; /* DBG("%d %d %d", t->min_width, t->max_width, table->width); */ if (table->min_width >= table->width) distribute_widths(table, table->min_width); else if (table->max_width < table->width && table->full_width) distribute_widths(table, table->max_width); else distribute_widths(table, table->width); if (!part->document && part->box.x == 1) { int total_width = table->real_width + margins; int_bounds(&total_width, table->real_width, par_format.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 = 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 = 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(); }