1 /*
2  * File: table.cc
3  *
4  * Copyright 2008 Jorge Arellano Cid <jcid@dillo.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  */
11 
12 #include "table.hh"
13 #include "html_common.hh"
14 
15 #include "dw/style.hh"
16 #include "dw/textblock.hh"
17 #include "dw/table.hh"
18 
19 #include "prefs.h"
20 #include "msg.h"
21 #include "css.hh"
22 
23 using namespace dw;
24 using namespace dw::core;
25 using namespace dw::core::style;
26 
27 /*
28  * Forward declarations
29  */
30 
31 static void Html_tag_open_table_cell(DilloHtml *html,
32                                      const char *tag, int tagsize,
33                                      dw::core::style::TextAlignType text_align);
34 static void Html_tag_content_table_cell(DilloHtml *html,
35                                         const char *tag, int tagsize);
36 
37 /*
38  * <TABLE>
39  */
Html_tag_open_table(DilloHtml * html,const char * tag,int tagsize)40 void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize)
41 {
42    const char *attrbuf;
43    int32_t border = -1, cellspacing = -1, cellpadding = -1, bgcolor = -1;
44    CssLength cssLength;
45 
46    if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "border")))
47       border = isdigit(attrbuf[0]) ? strtol (attrbuf, NULL, 10) : 1;
48    if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cellspacing"))) {
49       cellspacing = strtol (attrbuf, NULL, 10);
50       if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
51          BUG_MSG("<table> cellspacing attribute is obsolete.");
52    }
53 
54    if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cellpadding"))) {
55       cellpadding = strtol (attrbuf, NULL, 10);
56       if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
57          BUG_MSG("<table> cellpadding attribute is obsolete.");
58    }
59 
60    if (border != -1) {
61       cssLength = CSS_CREATE_LENGTH (border, CSS_LENGTH_TYPE_PX);
62       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_WIDTH,
63                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
64       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_WIDTH,
65                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
66       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_WIDTH,
67                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
68       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_WIDTH,
69                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
70       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_STYLE,
71                                         CSS_TYPE_ENUM, BORDER_OUTSET);
72       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_STYLE,
73                                         CSS_TYPE_ENUM, BORDER_OUTSET);
74       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_STYLE,
75                                         CSS_TYPE_ENUM, BORDER_OUTSET);
76       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_STYLE,
77                                         CSS_TYPE_ENUM, BORDER_OUTSET);
78    }
79 
80    if (cellspacing != -1) {
81       cssLength = CSS_CREATE_LENGTH (cellspacing, CSS_LENGTH_TYPE_PX);
82       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_SPACING,
83                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
84    }
85 
86    if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "width"))) {
87       html->styleEngine->setNonCssHint (CSS_PROPERTY_WIDTH,
88                                         CSS_TYPE_LENGTH_PERCENTAGE,
89                                         a_Html_parse_length (html, attrbuf));
90       if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
91          BUG_MSG("<table> width attribute is obsolete.");
92    }
93 
94    if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "align"))) {
95       if (dStrAsciiCasecmp (attrbuf, "left") == 0)
96          html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,
97                                            CSS_TYPE_ENUM, TEXT_ALIGN_LEFT);
98       else if (dStrAsciiCasecmp (attrbuf, "right") == 0)
99          html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,
100                                            CSS_TYPE_ENUM, TEXT_ALIGN_RIGHT);
101       else if (dStrAsciiCasecmp (attrbuf, "center") == 0)
102          html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,
103                                            CSS_TYPE_ENUM, TEXT_ALIGN_CENTER);
104       if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
105          BUG_MSG("<table> align attribute is obsolete.");
106    }
107 
108    if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) {
109       bgcolor = a_Html_color_parse(html, attrbuf, -1);
110       if (bgcolor != -1)
111          html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR,
112                                            CSS_TYPE_COLOR, bgcolor);
113       if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
114          BUG_MSG("<table> bgcolor attribute is obsolete.");
115    }
116 
117    html->style (); // evaluate now, so we can build non-css hints for the cells
118 
119    /* The style for the cells */
120    html->styleEngine->clearNonCssHints ();
121    if (border > 0) {
122       cssLength = CSS_CREATE_LENGTH (1, CSS_LENGTH_TYPE_PX);
123       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_WIDTH,
124                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
125       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_WIDTH,
126                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
127       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_WIDTH,
128                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
129       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_WIDTH,
130                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
131       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_STYLE,
132                                         CSS_TYPE_ENUM, BORDER_INSET);
133       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_STYLE,
134                                         CSS_TYPE_ENUM, BORDER_INSET);
135       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_STYLE,
136                                         CSS_TYPE_ENUM, BORDER_INSET);
137       html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_STYLE,
138                                         CSS_TYPE_ENUM, BORDER_INSET);
139    }
140 
141    if (cellpadding != -1) {
142       cssLength = CSS_CREATE_LENGTH (cellpadding, CSS_LENGTH_TYPE_PX);
143       html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_TOP,
144                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
145       html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_BOTTOM,
146                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
147       html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_LEFT,
148                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
149       html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_RIGHT,
150                                         CSS_TYPE_LENGTH_PERCENTAGE, cssLength);
151    }
152 
153 }
Html_tag_content_table(DilloHtml * html,const char * tag,int tagsize)154 void Html_tag_content_table(DilloHtml *html, const char *tag, int tagsize)
155 {
156    dw::core::Widget *table;
157 
158    HT2TB(html)->addParbreak (0, html->wordStyle ());
159    table = new dw::Table(prefs.limit_text_width);
160    HT2TB(html)->addWidget (table, html->style ());
161    HT2TB(html)->addParbreak (0, html->wordStyle ());
162 
163    S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TOP;
164    S_TOP(html)->table_border_mode = DILLO_HTML_TABLE_BORDER_SEPARATE;
165    S_TOP(html)->cell_text_align_set = FALSE;
166    S_TOP(html)->table = table;
167 
168 }
169 
170 /*
171  * <TR>
172  */
Html_tag_open_tr(DilloHtml * html,const char * tag,int tagsize)173 void Html_tag_open_tr(DilloHtml *html, const char *tag, int tagsize)
174 {
175    const char *attrbuf;
176    int32_t bgcolor = -1;
177 
178    html->styleEngine->inheritNonCssHints ();
179 
180    switch (S_TOP(html)->table_mode) {
181    case DILLO_HTML_TABLE_MODE_NONE:
182       _MSG("Invalid HTML syntax: <tr> outside <table>\n");
183       return;
184 
185    case DILLO_HTML_TABLE_MODE_TOP:
186    case DILLO_HTML_TABLE_MODE_TR:
187    case DILLO_HTML_TABLE_MODE_TD:
188 
189       if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) {
190          bgcolor = a_Html_color_parse(html, attrbuf, -1);
191          if (bgcolor != -1)
192             html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR,
193                                               CSS_TYPE_COLOR, bgcolor);
194          if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
195             BUG_MSG("<tr> bgcolor attribute is obsolete.");
196       }
197 
198       if (a_Html_get_attr (html, tag, tagsize, "align")) {
199          S_TOP(html)->cell_text_align_set = TRUE;
200          a_Html_tag_set_align_attr (html, tag, tagsize);
201       }
202 
203       html->styleEngine->inheritBackgroundColor ();
204 
205       if (bgcolor != -1) {
206          html->styleEngine->setNonCssHint(CSS_PROPERTY_BACKGROUND_COLOR,
207                                           CSS_TYPE_COLOR, bgcolor);
208       }
209       a_Html_tag_set_valign_attr (html, tag, tagsize);
210       break;
211    default:
212       break;
213    }
214 }
215 
Html_tag_content_tr(DilloHtml * html,const char * tag,int tagsize)216 void Html_tag_content_tr(DilloHtml *html, const char *tag, int tagsize)
217 {
218    switch (S_TOP(html)->table_mode) {
219    case DILLO_HTML_TABLE_MODE_NONE:
220       return;
221    case DILLO_HTML_TABLE_MODE_TOP:
222    case DILLO_HTML_TABLE_MODE_TR:
223    case DILLO_HTML_TABLE_MODE_TD:
224       ((dw::Table*)S_TOP(html)->table)->addRow (html->style ());
225    default:
226       break;
227    }
228 
229    S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TR;
230 }
231 
232 /*
233  * <TD>
234  */
Html_tag_open_td(DilloHtml * html,const char * tag,int tagsize)235 void Html_tag_open_td(DilloHtml *html, const char *tag, int tagsize)
236 {
237    Html_tag_open_table_cell (html, tag, tagsize,
238                              dw::core::style::TEXT_ALIGN_LEFT);
239 }
240 
Html_tag_content_td(DilloHtml * html,const char * tag,int tagsize)241 void Html_tag_content_td(DilloHtml *html, const char *tag, int tagsize)
242 {
243    Html_tag_content_table_cell (html, tag, tagsize);
244 }
245 
246 /*
247  * <TH>
248  */
Html_tag_open_th(DilloHtml * html,const char * tag,int tagsize)249 void Html_tag_open_th(DilloHtml *html, const char *tag, int tagsize)
250 {
251    Html_tag_open_table_cell (html, tag, tagsize,
252                              dw::core::style::TEXT_ALIGN_CENTER);
253 }
254 
Html_tag_content_th(DilloHtml * html,const char * tag,int tagsize)255 void Html_tag_content_th(DilloHtml *html, const char *tag, int tagsize)
256 {
257    Html_tag_content_table_cell (html, tag, tagsize);
258 }
259 
260 /*
261  * Utilities
262  */
263 
264 /*
265  * The table border model is stored in the table's stack item
266  */
Html_table_get_border_model(DilloHtml * html)267 static int Html_table_get_border_model(DilloHtml *html)
268 {
269    static int i_TABLE = -1;
270    if (i_TABLE == -1)
271       i_TABLE = a_Html_tag_index("table");
272 
273    int s_idx = html->stack->size();
274    while (--s_idx > 0 && html->stack->getRef(s_idx)->tag_idx != i_TABLE)
275       ;
276    return html->stack->getRef(s_idx)->table_border_mode;
277 }
278 
279 /*
280  * Set current table's border model
281  */
Html_table_set_border_model(DilloHtml * html,DilloHtmlTableBorderMode mode)282 static void Html_table_set_border_model(DilloHtml *html,
283                                         DilloHtmlTableBorderMode mode)
284 {
285    int s_idx = html->stack->size(), i_TABLE = a_Html_tag_index("table");
286 
287    while (--s_idx > 0 && html->stack->getRef(s_idx)->tag_idx != i_TABLE) ;
288    if (s_idx > 0)
289       html->stack->getRef(s_idx)->table_border_mode = mode;
290 }
291 
292 /* WORKAROUND: collapsing border model requires moving rendering code from
293  *             the cell to the table, and making table-code aware of each
294  *             cell style.
295  * This workaround mimics collapsing model within separate model. This is not
296  * a complete emulation but should be enough for most cases.
297  */
Html_set_collapsing_border_model(DilloHtml * html,Widget * col_tb)298 static void Html_set_collapsing_border_model(DilloHtml *html, Widget *col_tb)
299 {
300    dw::core::style::Style *collapseStyle, *tableStyle;
301    dw::core::style::StyleAttrs collapseCellAttrs, collapseTableAttrs;
302    int borderWidth, marginWidth;
303 
304    tableStyle = ((dw::Table*)S_TOP(html)->table)->getStyle ();
305    borderWidth = html->style ()->borderWidth.top;
306    marginWidth = tableStyle->margin.top;
307 
308    collapseCellAttrs = *(html->style ());
309    collapseCellAttrs.margin.setVal (0);
310    collapseCellAttrs.borderWidth.left = 0;
311    collapseCellAttrs.borderWidth.top = 0;
312    collapseCellAttrs.borderWidth.right = borderWidth;
313    collapseCellAttrs.borderWidth.bottom = borderWidth;
314    collapseCellAttrs.hBorderSpacing = 0;
315    collapseCellAttrs.vBorderSpacing = 0;
316    collapseStyle = Style::create(&collapseCellAttrs);
317    col_tb->setStyle (collapseStyle);
318 
319    if (Html_table_get_border_model(html) != DILLO_HTML_TABLE_BORDER_COLLAPSE) {
320       Html_table_set_border_model(html, DILLO_HTML_TABLE_BORDER_COLLAPSE);
321       collapseTableAttrs = *tableStyle;
322       collapseTableAttrs.margin.setVal (marginWidth);
323       collapseTableAttrs.borderWidth.left = borderWidth;
324       collapseTableAttrs.borderWidth.top = borderWidth;
325       collapseTableAttrs.borderWidth.right = 0;
326       collapseTableAttrs.borderWidth.bottom = 0;
327       collapseTableAttrs.hBorderSpacing = 0;
328       collapseTableAttrs.vBorderSpacing = 0;
329       collapseTableAttrs.borderColor = collapseCellAttrs.borderColor;
330       collapseTableAttrs.borderStyle = collapseCellAttrs.borderStyle;
331       /* CSS2 17.6.2: table does not have padding (in collapsing mode) */
332       collapseTableAttrs.padding.setVal (0);
333       collapseStyle = Style::create(&collapseTableAttrs);
334       ((dw::Table*)S_TOP(html)->table)->setStyle (collapseStyle);
335    }
336 }
337 
338 /*
339  * Adjust style for separate border model.
340  * (Dw uses this model internally).
341  */
Html_set_separate_border_model(DilloHtml * html,Widget * col_tb)342 static void Html_set_separate_border_model(DilloHtml *html, Widget *col_tb)
343 {
344    dw::core::style::Style *separateStyle;
345    dw::core::style::StyleAttrs separateCellAttrs;
346 
347    separateCellAttrs = *(html->style ());
348    /* CSS2 17.5: Internal table elements do not have margins */
349    separateCellAttrs.margin.setVal (0);
350    separateStyle = Style::create(&separateCellAttrs);
351    col_tb->setStyle (separateStyle);
352 }
353 
354 /*
355  * used by <TD> and <TH>
356  */
Html_tag_open_table_cell(DilloHtml * html,const char * tag,int tagsize,dw::core::style::TextAlignType text_align)357 static void Html_tag_open_table_cell(DilloHtml *html,
358                                      const char *tag, int tagsize,
359                                      dw::core::style::TextAlignType text_align)
360 {
361    const char *attrbuf;
362    int32_t bgcolor;
363 
364    html->styleEngine->inheritNonCssHints ();
365 
366    switch (S_TOP(html)->table_mode) {
367    case DILLO_HTML_TABLE_MODE_NONE:
368       return;
369 
370    case DILLO_HTML_TABLE_MODE_TOP:
371       /* a_Dw_table_add_cell takes care that dillo does not crash. */
372       /* continues */
373    case DILLO_HTML_TABLE_MODE_TR:
374    case DILLO_HTML_TABLE_MODE_TD:
375       /* text style */
376       if (!S_TOP(html)->cell_text_align_set) {
377          html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,
378                                            CSS_TYPE_ENUM, text_align);
379       }
380       if (a_Html_get_attr(html, tag, tagsize, "nowrap")) {
381          if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
382             BUG_MSG("<t%c> nowrap attribute is obsolete.",
383                (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');
384          html->styleEngine->setNonCssHint(CSS_PROPERTY_WHITE_SPACE,
385                                           CSS_TYPE_ENUM, WHITE_SPACE_NOWRAP);
386       }
387 
388       a_Html_tag_set_align_attr (html, tag, tagsize);
389 
390       if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "width"))) {
391          html->styleEngine->setNonCssHint (CSS_PROPERTY_WIDTH,
392                                            CSS_TYPE_LENGTH_PERCENTAGE,
393                                            a_Html_parse_length (html, attrbuf));
394          if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
395             BUG_MSG("<t%c> width attribute is obsolete.",
396                (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');
397       }
398 
399       a_Html_tag_set_valign_attr (html, tag, tagsize);
400 
401       if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) {
402          bgcolor = a_Html_color_parse(html, attrbuf, -1);
403          if (bgcolor != -1)
404             html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR,
405                                               CSS_TYPE_COLOR, bgcolor);
406          if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)
407             BUG_MSG("<t%c> bgcolor attribute is obsolete.",
408                (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');
409       }
410 
411    default:
412       /* compiler happiness */
413       break;
414    }
415 }
416 
Html_tag_content_table_cell(DilloHtml * html,const char * tag,int tagsize)417 static void Html_tag_content_table_cell(DilloHtml *html,
418                                      const char *tag, int tagsize)
419 {
420    int colspan = 1, rowspan = 1;
421    const char *attrbuf;
422    Widget *col_tb;
423 
424    switch (S_TOP(html)->table_mode) {
425    case DILLO_HTML_TABLE_MODE_NONE:
426       BUG_MSG("<t%c> outside <table>.",
427               (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');
428       return;
429 
430    case DILLO_HTML_TABLE_MODE_TOP:
431       BUG_MSG("<t%c> outside <tr>.",
432               (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');
433       /* a_Dw_table_add_cell takes care that dillo does not crash. */
434       /* continues */
435    case DILLO_HTML_TABLE_MODE_TR:
436    case DILLO_HTML_TABLE_MODE_TD:
437       if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "colspan"))) {
438          char *invalid;
439          colspan = strtol(attrbuf, &invalid, 10);
440          if ((colspan < 0) || (attrbuf == invalid))
441             colspan = 1;
442       }
443       /* TODO: check errors? */
444       if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "rowspan")))
445          rowspan = MAX(1, strtol (attrbuf, NULL, 10));
446       if (html->style ()->textAlign
447           == TEXT_ALIGN_STRING)
448          col_tb = new dw::TableCell (
449                      ((dw::Table*)S_TOP(html)->table)->getCellRef (),
450                      prefs.limit_text_width);
451       else
452          col_tb = new Textblock (prefs.limit_text_width);
453 
454       if (html->style()->borderCollapse == BORDER_MODEL_COLLAPSE){
455          Html_set_collapsing_border_model(html, col_tb);
456       } else {
457          Html_set_separate_border_model(html, col_tb);
458       }
459 
460       ((dw::Table*)S_TOP(html)->table)->addCell (col_tb, colspan, rowspan);
461       S_TOP(html)->textblock = html->dw = col_tb;
462       break;
463 
464    default:
465       /* compiler happiness */
466       break;
467    }
468 
469    S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TD;
470 }
471