/* General element handlers */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE /* strcasestr() */ #endif #include #include #include #include #include "elinks.h" #include "config/options.h" #include "bfu/listmenu.h" #include "bookmarks/bookmarks.h" #include "document/css/apply.h" #include "document/html/frames.h" #include "document/html/parser/link.h" #include "document/html/parser/stack.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 "document/xml/renderer2.h" #include "document/xml/tables.h" #include "document/xml/tags.h" #include "ecmascript/spidermonkey/element.h" #include "globhist/globhist.h" #include "intl/charsets.h" #include "protocol/uri.h" #include "terminal/draw.h" #include "util/align.h" #include "util/box.h" #include "util/color.h" #include "util/conv.h" #include "util/error.h" #include "util/memdebug.h" #include "util/memory.h" #include "util/string.h" /* Unsafe macros */ #include "document/html/internal.h" #include #include static void get_bordercolor2(struct source_renderer *renderer, void *no, color_T *rgb) { struct html_context *html_context = renderer->html_context; if (!use_document_fg_colors(html_context->options)) return; xmlpp::Element *node = no; xmlpp::ustring color = node->get_attribute_value("bordercolor"); if (color == "") { color = node->get_attribute_value("bordercolorlight"); } if (color == "") { color = node->get_attribute_value("bordercolordark"); } if (color == "") { return; } char *at = memacpy(color.c_str(), color.size()); if (!at) return; decode_color(at, strlen(at), rgb); mem_free(at); } static void tags_set_table_frame(struct source_renderer *renderer, void *no, struct table *table) { struct html_context *html_context = renderer->html_context; char *al; if (!table->border) { table->frame = TABLE_FRAME_VOID; return; } table->frame = TABLE_FRAME_BOX; xmlpp::Element *node = no; xmlpp::ustring frame_value = node->get_attribute_value("frame"); al = memacpy(frame_value.c_str(), frame_value.size()); if (!al) return; if (!c_strcasecmp(al, "void")) table->frame = TABLE_FRAME_VOID; else if (!c_strcasecmp(al, "above")) table->frame = TABLE_FRAME_ABOVE; else if (!c_strcasecmp(al, "below")) table->frame = TABLE_FRAME_BELOW; else if (!c_strcasecmp(al, "hsides")) table->frame = TABLE_FRAME_HSIDES; else if (!c_strcasecmp(al, "vsides")) table->frame = TABLE_FRAME_VSIDES; else if (!c_strcasecmp(al, "lhs")) table->frame = TABLE_FRAME_LHS; else if (!c_strcasecmp(al, "rhs")) table->frame = TABLE_FRAME_RHS; /* Following tests are useless since TABLE_FRAME_BOX is the default. * else if (!c_strcasecmp(al, "box")) table->frame = TABLE_FRAME_BOX; * else if (!c_strcasecmp(al, "border")) table->frame = TABLE_FRAME_BOX; */ mem_free(al); } static void tags_set_table_rules(struct source_renderer *renderer, void *no, struct table *table) { char *al; table->rules = table->border ? TABLE_RULE_ALL : TABLE_RULE_NONE; xmlpp::Element *node = no; xmlpp::ustring frame_value = node->get_attribute_value("rules"); al = memacpy(frame_value.c_str(), frame_value.size()); if (!al) return; if (!c_strcasecmp(al, "none")) table->rules = TABLE_RULE_NONE; else if (!c_strcasecmp(al, "groups")) table->rules = TABLE_RULE_GROUPS; else if (!c_strcasecmp(al, "rows")) table->rules = TABLE_RULE_ROWS; else if (!c_strcasecmp(al, "cols")) table->rules = TABLE_RULE_COLS; else if (!c_strcasecmp(al, "all")) table->rules = TABLE_RULE_ALL; mem_free(al); } static void tags_get_align(struct source_renderer *renderer, void *no, int *a) { xmlpp::Element *node = no; xmlpp::ustring align_value = node->get_attribute_value("align"); char *al = memacpy(align_value.c_str(), align_value.size()); if (!al) return; if (!c_strcasecmp(al, "left")) *a = ALIGN_LEFT; else if (!c_strcasecmp(al, "right")) *a = ALIGN_RIGHT; else if (!c_strcasecmp(al, "center")) *a = ALIGN_CENTER; else if (!c_strcasecmp(al, "justify")) *a = ALIGN_JUSTIFY; else if (!c_strcasecmp(al, "char")) *a = ALIGN_RIGHT; /* NOT IMPLEMENTED */ mem_free(al); } static void tags_get_valign(struct source_renderer *renderer, void *no, int *a) { xmlpp::Element *node = no; xmlpp::ustring valign_value = node->get_attribute_value("valign"); char *al = memacpy(valign_value.c_str(), valign_value.size()); if (!al) return; if (!c_strcasecmp(al, "top")) *a = VALIGN_TOP; else if (!c_strcasecmp(al, "middle")) *a = VALIGN_MIDDLE; else if (!c_strcasecmp(al, "bottom")) *a = VALIGN_BOTTOM; else if (!c_strcasecmp(al, "baseline")) *a = VALIGN_BASELINE; /* NOT IMPLEMENTED */ mem_free(al); } static void tags_get_column_width(struct source_renderer *renderer, void *no, int *width, int sh) { struct html_context *html_context = renderer->html_context; xmlpp::Element *node = no; xmlpp::ustring width_value = node->get_attribute_value("width"); char *al = memacpy(width_value.c_str(), width_value.size()); int len; if (!al) return; len = strlen(al); if (len && al[len - 1] == '*') { char *en; int n; al[len - 1] = '\0'; errno = 0; n = strtoul(al, (char **) &en, 10); if (!errno && n >= 0 && (!*en || *en == '.')) *width = WIDTH_RELATIVE - n; } else { int w = get_width2(al, sh, html_context); if (w >= 0) *width = w; } mem_free(al); } int tags_get_bgcolor(struct source_renderer *renderer, void *no, color_T *rgb) { struct html_context *html_context = renderer->html_context; if (!use_document_bg_colors(html_context->options)) return -1; xmlpp::Element *node = no; xmlpp::ustring bgcolor_value = node->get_attribute_value("bgcolor"); char *at = memacpy(bgcolor_value.c_str(), bgcolor_value.size()); if (!at) return -1; int res = decode_color(at, strlen(at), rgb); mem_free(at); return res; } static void tags_parse_table_attributes(struct source_renderer *renderer, struct table *table, void *no, int real) { struct html_context *html_context = renderer->html_context; xmlpp::Element *node = no; xmlpp::ustring id_value = node->get_attribute_value("id"); if (id_value != "") { table->fragment_id = memacpy(id_value.c_str(), id_value.size()); } get_bordercolor2(renderer, no, &table->color.border); xmlpp::ustring width_value = node->get_attribute_value("width"); char *width = memacpy(width_value.c_str(), width_value.size()); table->width = get_width2(width, real, html_context); if (table->width == -1) { table->width = get_html_max_width(); table->full_width = 1; } /* From http://www.w3.org/TR/html4/struct/tables.html#adef-border-TABLE * The following settings should be observed by user agents for * backwards compatibility. * Setting border="0" implies frame="void" and, unless otherwise * specified, rules="none". * Other values of border imply frame="border" and, unless otherwise * specified, rules="all". * The value "border" in the start tag of the TABLE element should be * interpreted as the value of the frame attribute. It implies * rules="all" and some default (non-zero) value for the border * attribute. */ xmlpp::ustring border_value = node->get_attribute_value("border"); if (border_value != "") { char *at = memacpy(border_value.c_str(), border_value.size()); table->border = get_num2(at); } if (table->border == -1) { xmlpp::Attribute *attr = dynamic_cast(node->get_attribute("border")); if (!attr) { attr = dynamic_cast(node->get_attribute("rules")); } if (!attr) { attr = dynamic_cast(node->get_attribute("frames")); } table->border = (attr != nullptr); } if (table->border) { int_upper_bound(&table->border, 2); xmlpp::ustring cellspacing_value = node->get_attribute_value("cellspacing"); if (cellspacing_value != "") { char *at = memacpy(cellspacing_value.c_str(), cellspacing_value.size()); table->cellspacing = get_num2(at); } int_bounds(&table->cellspacing, 1, 2); } tags_set_table_frame(renderer, no, table); /* TODO: cellpadding may be expressed as a percentage, this is not * handled yet. */ xmlpp::ustring cellpadding_value = node->get_attribute_value("cellpadding"); char *at = memacpy(cellpadding_value.c_str(), cellpadding_value.size()); table->cellpadding = get_num2(at); if (table->cellpadding == -1) { table->vcellpadding = 0; table->cellpadding = !!table->border; } else { table->vcellpadding = (table->cellpadding >= HTML_CHAR_HEIGHT / 2 + 1); table->cellpadding = (table->cellpadding >= HTML_CHAR_WIDTH / 2 + 1); } tags_set_table_rules(renderer, no, table); table->align = par_elformat.align; tags_get_align(renderer, no, &table->align); table->color.background = par_elformat.color.background; tags_get_bgcolor(renderer, no, &table->color.background); } #define realloc_bad_html(bad_html, size) \ mem_align_alloc(bad_html, size, (size) + 1, 0xFF) static void tags_add_table_bad_html_start(struct table *table, void *start) { if (table->caption.start_node && !table->caption.end_node) return; /* Either no bad html or last one not needing @end pointer */ if (table->bad_html_size && !table->bad_html[table->bad_html_size - 1].end_node) return; if (realloc_bad_html(&table->bad_html, table->bad_html_size)) table->bad_html[table->bad_html_size++].start_node = start; } static void tags_add_table_bad_html_end(struct table *table, void *end) { if (table->caption.start_node && !table->caption.end_node) { table->caption.end_node = end; return; } if (table->bad_html_size && !table->bad_html[table->bad_html_size - 1].end_node) table->bad_html[table->bad_html_size - 1].end_node = end; } static struct table * tags_parse_table(struct source_renderer *renderer, void *no, int sh) { struct html_context *html_context = renderer->html_context; xmlpp::Element *node = no; struct table *table; struct table_cell *cell; char *t_attr, *en, *name; char *l_fragment_id = NULL; color_T last_bgcolor; int namelen; int in_cell = 0; int l_al = ALIGN_LEFT; int l_val = VALIGN_MIDDLE; int colspan, rowspan; int group = 0; int i, j, k; int c_al = ALIGN_TR, c_val = VALIGN_TR, c_width = WIDTH_AUTO, c_span = 0; int cols, rows; int col = 0, row = -1; int maxj; int closing_tag, is_header; unsigned char c; char *colspa = NULL; char *rowspa = NULL; xmlpp::ustring colspan_value; xmlpp::ustring rowspan_value; xmlpp::ustring id_value; xmlpp::ustring name_value; // *end = html; table = new_table(); if (!table) return NULL; tags_parse_table_attributes(renderer, table, no, sh); last_bgcolor = table->color.background; se: // en = html; see: // html = en; if (!in_cell) { tags_add_table_bad_html_start(table, node); } /// while (html < eof && *html != '<') html++; if (false) { //&& html >= eof) { if (in_cell) CELL(table, col, row)->end_node = node; tags_add_table_bad_html_end(table, node); goto scan_done; } // if (html + 2 <= eof && (html[1] == '!' || html[1] == '?')) { // html = skip_comment(html, eof); // goto se; // } /// if (parse_element(html, eof, &name, &namelen, &t_attr, &en)) { /// html++; /// goto se; /// } name_value = node->get_name(); if (name_value == "") goto see; name = name_value.c_str(); namelen = name_value.size(); // if (!namelen) goto see; // if (name[0] == '/') { // namelen--; // if (!namelen) goto see; // name++; // closing_tag = 1; // // } else { // closing_tag = 0; // } closing_tag = 0; // if (!c_strlcasecmp(name, namelen, "TABLE", 5)) { // if (!closing_tag) { // en = skip_table(en, eof); // goto see; if (!c_strlcasecmp(name_value.c_str(), name_value.size(), "TABLE", 5)) { if (!closing_tag) { //en = tags_skip_table(renderer, no); node = node->get_next_sibling(); goto see; } else { // if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); // if (in_cell) CELL(table, col, row)->end = html; // add_table_bad_html_end(table, html); if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); if (in_cell) CELL(table, col, row)->end_node = node; tags_add_table_bad_html_end(table, node); goto scan_done; } } // if (!c_strlcasecmp(name, namelen, "CAPTION", 7)) { // if (!closing_tag) { // add_table_bad_html_end(table, html); // if (!table->caption.start) // table->caption.start = html; // } else { // if (table->caption.start && !table->caption.end) // table->caption.end = html; // } // goto see; // } if (!c_strlcasecmp(name_value.c_str(), name_value.size(), "CAPTION", 7)) { if (!closing_tag) { tags_add_table_bad_html_end(table, node); if (!table->caption.start_node) table->caption.start_node = node; } else { if (table->caption.start_node && !table->caption.end_node) table->caption.end_node = node; } goto see; } // if (!c_strlcasecmp(name, namelen, "COLGROUP", 8)) { // if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); // // add_table_bad_html_end(table, html); // // c_al = ALIGN_TR; // c_val = VALIGN_TR; // c_width = WIDTH_AUTO; // // if (!closing_tag) { // get_align(html_context, t_attr, &c_al); // get_valign(html_context, t_attr, &c_val); // get_column_width(t_attr, &c_width, sh, html_context); // c_span = get_num(t_attr, "span", html_context->doc_cp); // if (c_span == -1) // c_span = 1; // else if (c_span > HTML_MAX_COLSPAN) // c_span = HTML_MAX_COLSPAN; // // } else { // c_span = 0; // } // goto see; // } if (!c_strlcasecmp(name_value.c_str(), name_value.size(), "COLGROUP", 8)) { if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); tags_add_table_bad_html_end(table, node); c_al = ALIGN_TR; c_val = VALIGN_TR; c_width = WIDTH_AUTO; if (!closing_tag) { tags_get_align(renderer, no, &c_al); tags_get_valign(renderer, no, &c_val); tags_get_column_width(renderer, no, &c_width, sh); xmlpp::ustring span_value = node->get_attribute_value("span"); char *spa = memacpy(span_value.c_str(), span_value.size()); c_span = get_num2(spa); if (c_span == -1) c_span = 1; else if (c_span > HTML_MAX_COLSPAN) c_span = HTML_MAX_COLSPAN; } else { c_span = 0; } goto see; } // if (!closing_tag && !c_strlcasecmp(name, namelen, "COL", 3)) { // int sp, width, al, val; // // add_table_bad_html_end(table, html); // // sp = get_num(t_attr, "span", html_context->doc_cp); // if (sp == -1) sp = 1; // else if (sp > HTML_MAX_COLSPAN) sp = HTML_MAX_COLSPAN; // // width = c_width; // al = c_al; // val = c_val; // get_align(html_context, t_attr, &al); // get_valign(html_context, t_attr, &val); // get_column_width(t_attr, &width, sh, html_context); // new_columns(table, sp, width, al, val, !!c_span); // c_span = 0; // goto see; // } if (!closing_tag && !c_strlcasecmp(name_value.c_str(), name_value.size(), "COL", 3)) { int sp, width, al, val; tags_add_table_bad_html_end(table, node); xmlpp::ustring span_value = node->get_attribute_value("span"); char *spa = memacpy(span_value.c_str(), span_value.size()); sp = get_num2(spa); if (sp == -1) sp = 1; else if (sp > HTML_MAX_COLSPAN) sp = HTML_MAX_COLSPAN; width = c_width; al = c_al; val = c_val; tags_get_align(renderer, no, &al); tags_get_valign(renderer, no, &val); tags_get_column_width(renderer, no, &width, sh); new_columns(table, sp, width, al, val, !!c_span); c_span = 0; goto see; } /* All following tags have T as first letter. */ if (c_toupper(name[0]) != 'T') goto see; name++; namelen--; if (namelen == 0) goto see; c = c_toupper(name[0]); /* /TR /TD /TH */ if (closing_tag && namelen == 1) { if (c == 'R' || c == 'D' || c == 'H') { if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); if (in_cell) { CELL(table, col, row)->end_node = node; in_cell = 0; } tags_add_table_bad_html_end(table, node); goto see; } } /* Beyond that point, opening tags only. */ if (closing_tag) goto see; /* THEAD TBODY TFOOT */ if (namelen == 4 && ((!c_strlcasecmp(name, namelen, "HEAD", 4)) || (!c_strlcasecmp(name, namelen, "BODY", 4)) || (!c_strlcasecmp(name, namelen, "FOOT", 4)))) { if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); tags_add_table_bad_html_end(table, node); group = 2; goto see; } /* Beyond this point, only two letters tags. */ if (namelen != 1) goto see; /* TR */ if (c == 'R') { if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); if (in_cell) { CELL(table, col, row)->end_node = node; in_cell = 0; } tags_add_table_bad_html_end(table, node); if (group) group--; l_al = ALIGN_LEFT; l_val = VALIGN_MIDDLE; last_bgcolor = table->color.background; tags_get_align(renderer, no, &l_al); tags_get_valign(renderer, no, &l_val); tags_get_bgcolor(renderer, no, &last_bgcolor); xmlpp::ustring id_value = node->get_attribute_value("id"); mem_free_set(&l_fragment_id, memacpy(id_value.c_str(), id_value.size())); row++; col = 0; goto see; } /* TD TH */ is_header = (c == 'H'); if (!is_header && c != 'D') goto see; if (c_span) new_columns(table, c_span, c_width, c_al, c_val, 1); tags_add_table_bad_html_end(table, node); if (in_cell) { CELL(table, col, row)->end_node = node; in_cell = 0; } if (row == -1) { row = 0; col = 0; } for (;;col++) { cell = new_cell(table, col, row); if (!cell) goto see; if (!cell->is_used) break; if (cell->colspan == -1) goto see; } in_cell = 1; cell->col = col; cell->row = row; cell->is_used = 1; cell->start = en; cell->align = l_al; cell->valign = l_val; id_value = node->get_attribute_value("id"); mem_free_set(&cell->fragment_id, memacpy(id_value.c_str(), id_value.size())); // cell->fragment_id = get_attr_val(t_attr, "id", html_context->doc_cp); if (!cell->fragment_id && l_fragment_id) { cell->fragment_id = l_fragment_id; l_fragment_id = NULL; } cell->is_header = is_header; if (cell->is_header) cell->align = ALIGN_CENTER; if (group == 1) cell->is_group = 1; if (col < table->columns_count) { if (table->columns[col].align != ALIGN_TR) cell->align = table->columns[col].align; if (table->columns[col].valign != VALIGN_TR) cell->valign = table->columns[col].valign; } cell->bgcolor = last_bgcolor; tags_get_align(renderer, no, &cell->align); tags_get_valign(renderer, no, &cell->valign); tags_get_bgcolor(renderer, no, &cell->bgcolor); colspan_value = node->get_attribute_value("colspan"); colspa = memacpy(colspan_value.c_str(), colspan_value.size()); colspan = get_num2(colspa); if (colspan == -1) colspan = 1; else if (!colspan) colspan = -1; else if (colspan > HTML_MAX_COLSPAN) colspan = HTML_MAX_COLSPAN; rowspan_value = node->get_attribute_value("rowspan"); rowspa = memacpy(rowspan_value.c_str(), rowspan_value.size()); rowspan = get_num2(rowspa); if (rowspan == -1) rowspan = 1; else if (!rowspan) rowspan = -1; else if (rowspan > HTML_MAX_ROWSPAN) rowspan = HTML_MAX_ROWSPAN; cell->colspan = colspan; cell->rowspan = rowspan; if (colspan == 1) { int width = WIDTH_AUTO; tags_get_column_width(renderer, no, &width, sh); if (width != WIDTH_AUTO) set_td_width(table, col, width, 0); } cols = table->cols; for (i = 1; colspan != -1 ? i < colspan : i < cols; i++) { struct table_cell *span_cell = new_cell(table, col + i, row); if (!span_cell) goto abort; if (span_cell->is_used) { colspan = i; for (k = 0; k < i; k++) CELL(table, col + k, row)->colspan = colspan; break; } span_cell->is_used = span_cell->is_spanned = 1; span_cell->rowspan = rowspan; span_cell->colspan = colspan; span_cell->col = col; span_cell->row = row; } rows = table->rows; maxj = rowspan != -1 ? rowspan : rows; /* Out of memory prevention, limit allocated memory to HTML_MAX_CELLS_MEMORY. * Not perfect but better than nothing. */ if (maxj * i > HTML_MAX_CELLS_MEMORY / sizeof(*cell)) goto abort; for (j = 1; j < maxj; j++) { for (k = 0; k < i; k++) { struct table_cell *span_cell = new_cell(table, col + k, row + j); if (!span_cell) goto abort; if (span_cell->is_used) { int l, m; if (span_cell->col == col && span_cell->row == row) continue; for (l = 0; l < k; l++) memset(CELL(table, col + l, row + j), 0, sizeof(*span_cell)); rowspan = j; for (l = 0; l < i; l++) for (m = 0; m < j; m++) CELL(table, col + l, row + m)->rowspan = j; goto see; } span_cell->is_used = span_cell->is_spanned = 1; span_cell->rowspan = rowspan; span_cell->colspan = colspan; span_cell->col = col; span_cell->row = row; } } goto see; scan_done: // *end = html; mem_free_if(l_fragment_id); 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) { if (cell->colspan == -1) cell->colspan = table->cols - col; if (cell->rowspan == -1) cell->rowspan = table->rows - row; } } if (table->rows) { table->rows_heights = (int *)mem_calloc(table->rows, sizeof(*table->rows_heights)); if (!table->rows_heights) goto abort; } else { table->rows_heights = NULL; } for (col = 0; col < table->columns_count; col++) if (table->columns[col].width != WIDTH_AUTO) set_td_width(table, col, table->columns[col].width, 1); set_td_width(table, table->cols, WIDTH_AUTO, 0); return table; abort: // *end = eof; free_table(table); return NULL; } static void tags_draw_table_bad_html(struct source_renderer *renderer, struct table *table) { struct html_context *html_context = renderer->html_context; int i; for (i = 0; i < table->bad_html_size; i++) { struct html_start_end *html = &table->bad_html[i]; void *start_node = html->start_node; void *end_node = html->end_node; dump_dom_structure(renderer, start_node, 0); } } void tags_format_table(struct source_renderer *renderer, void *no) { struct html_context *html_context = renderer->html_context; struct part *part = html_context->part; struct table *table; struct node *node, *new_node; struct html_element *state; int indent, margins; struct string html_string; init_string(&html_string); walk_tree(&html_string, no, false); char *attr = html_string.source + 1; char *html = html_string.source; char *eof = html_string.source + html_string.length; char *end; format_table(attr, html, eof, &end, html_context); // done_string(&html_string); #if 0 html_context->table_level++; table = tags_parse_table(renderer, no, (part->document || part->box.x)); 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. */ tags_draw_table_bad_html(renderer, table); state = 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 = 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(); #endif }