diff options
author | Jorge Arellano Cid <jcid@dillo.org> | 2010-11-13 13:15:55 -0300 |
---|---|---|
committer | Jorge Arellano Cid <jcid@dillo.org> | 2010-11-13 13:15:55 -0300 |
commit | 415a9c17ea71a0b4b53bbda1c39e495cf7ae2d1b (patch) | |
tree | 9954b9bdb37333591229d1224469269059936a8b | |
parent | a12f6e9121d0049ed5f68b52c5d54ac35802a605 (diff) |
Full CSS border-style implementation
The drawBorder{Top,Bottom,Left,Right} functions are similar. They
use a trapezium as draw polygon, or drawTypedLine() for dots and dashes.
Although the concept is simple, achieving pixel accuracy is laborious [1].
[1] http://www.dillo.org/css_compat/tests/border-style.html
-rw-r--r-- | dw/fltkviewbase.cc | 33 | ||||
-rw-r--r-- | dw/fltkviewbase.hh | 4 | ||||
-rw-r--r-- | dw/style.cc | 442 | ||||
-rw-r--r-- | dw/style.hh | 6 | ||||
-rw-r--r-- | dw/view.hh | 4 | ||||
-rw-r--r-- | src/css.cc | 2 |
6 files changed, 420 insertions, 71 deletions
diff --git a/dw/fltkviewbase.cc b/dw/fltkviewbase.cc index 3fb43cb1..373e5454 100644 --- a/dw/fltkviewbase.cc +++ b/dw/fltkviewbase.cc @@ -369,6 +369,39 @@ void FltkViewBase::drawLine (core::style::Color *color, translateCanvasXToViewX (x2), translateCanvasYToViewY (y2)); } +void FltkViewBase::drawTypedLine (core::style::Color *color, + core::style::Color::Shading shading, + core::style::LineType type, int width, + int x1, int y1, int x2, int y2) +{ + char dashes[3], w, ng, d, gap, len; + const int f = 2; + + w = (width == 1) ? 0 : width; + if (type == core::style::LINE_DOTTED) { + /* customized drawing for dotted lines */ + len = (x2 == x1) ? y2 - y1 + 1 : (y2 == y1) ? x2 - x1 + 1 : 0; + ng = len / f*width; + d = len % f*width; + gap = ng ? d/ng + (w > 3 ? 2 : 0) : 0; + dashes[0] = 1; dashes[1] = f*width-gap; dashes[2] = 0; + line_style(::fltk::DASH + ::fltk::CAP_ROUND, w, dashes); + + /* These formulas also work, but ain't pretty ;) + * line_style(::fltk::DOT + ::fltk::CAP_ROUND, w); + * dashes[0] = 1; dashes[1] = 3*width-2; dashes[2] = 0; + */ + } else if (type == core::style::LINE_DASHED) { + line_style(::fltk::DASH + ::fltk::CAP_ROUND, w); + } + + setcolor(((FltkColor*)color)->colors[shading]); + drawLine (color, shading, x1, y1, x2, y2); + + if (type != core::style::LINE_NORMAL) + line_style(::fltk::SOLID); +} + void FltkViewBase::drawRectangle (core::style::Color *color, core::style::Color::Shading shading, bool filled, diff --git a/dw/fltkviewbase.hh b/dw/fltkviewbase.hh index 5e1981f8..b5c3ab5e 100644 --- a/dw/fltkviewbase.hh +++ b/dw/fltkviewbase.hh @@ -72,6 +72,10 @@ public: void drawLine (core::style::Color *color, core::style::Color::Shading shading, int x1, int y1, int x2, int y2); + void drawTypedLine (core::style::Color *color, + core::style::Color::Shading shading, + core::style::LineType type, int width, + int x1, int y1, int x2, int y2); void drawRectangle (core::style::Color *color, core::style::Color::Shading shading, bool filled, int x, int y, int width, int height); diff --git a/dw/style.cc b/dw/style.cc index b876985b..319ad239 100644 --- a/dw/style.cc +++ b/dw/style.cc @@ -23,6 +23,7 @@ #include <string.h> #include <unistd.h> #include <ctype.h> +#include <math.h> #include "core.hh" #include "../lout/msg.h" @@ -411,18 +412,373 @@ Tooltip *Tooltip::create (Layout *layout, const char *text) // ---------------------------------------------------------------------- -static void drawTriangle (View *view, Color *color, Color::Shading shading, - int x1, int y1, int x2, int y2, int x3, int y3) { - int points[3][2]; +/* + * The drawBorder{Top,Bottom,Left,Right} functions are similar. They + * use a trapezium as draw polygon, or drawTypedLine() for dots and dashes. + * Although the concept is simple, achieving pixel accuracy is laborious [1]. + * + * [1] http://www.dillo.org/css_compat/tests/border-style.html + */ +static void drawBorderTop(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int points[4][2], d, w; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.top) + return; + + switch (style->borderStyle.top) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.top; + view->drawTypedLine(style->borderColor.top, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1+w/2, x2-w/2, y2+w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.top != BORDER_SOLID) + shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + + if (style->borderWidth.top == 1) { + view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2); + } else { + points[0][0] = x1; + points[1][0] = x2 + 1; + points[0][1] = points[1][1] = y1; + points[2][0] = points[1][0] - style->borderWidth.right; + points[3][0] = x1 + style->borderWidth.left; + points[2][1] = points[3][1] = points[0][1] + style->borderWidth.top; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + d = style->borderWidth.top & 1; + points[0][0] = x1; + points[1][0] = x2 + 1; + points[0][1] = points[1][1] = y1; + points[2][0] = x2 - style->borderWidth.right / 2; + points[3][0] = x1 + style->borderWidth.left / 2; + points[2][1] = points[3][1] = y1 + style->borderWidth.top / 2 + d; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + points[0][0] = x1 + style->borderWidth.left / 2 + d; + points[1][0] = x2 - style->borderWidth.right / 2 + 1 - d; + points[0][1] = points[1][1] = y1 + style->borderWidth.top / 2 + d; + points[2][0] = x2 - style->borderWidth.right + 1 - d; + points[3][0] = x1 + style->borderWidth.left; + points[2][1] = points[3][1] = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.top / 3.0); + d = w ? style->borderWidth.top - 2 * w : 0; + int w_l = (int) rint(style->borderWidth.left / 3.0); + int w_r = (int) rint(style->borderWidth.right / 3.0); + if (style->borderWidth.top == 1) { + view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2); + break; + } + points[0][0] = x1; + points[1][0] = x2 + 1; + points[0][1] = points[1][1] = y1; + points[2][0] = points[1][0] - w_r; + points[3][0] = points[0][0] + w_l; + points[2][1] = points[3][1] = points[0][1] + w; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + points[0][0] = x1 + style->borderWidth.left - w_l; + points[1][0] = x2 + 1 - style->borderWidth.right + w_r; + points[0][1] = points[1][1] = y1 + w + d; + points[2][0] = x2 + 1 - style->borderWidth.right; + points[3][0] = x1 + style->borderWidth.left; + points[2][1] = points[3][1] = y1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + break; + } +} + +static void drawBorderBottom(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int points[4][2], d, w; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.bottom) + return; + + switch (style->borderStyle.bottom) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.bottom; + view->drawTypedLine(style->borderColor.top, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1-w/2, x2-w/2, y2-w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.bottom != BORDER_SOLID) + shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + + if (style->borderWidth.bottom == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2); + } else { + points[0][0] = x1 - 1; + points[1][0] = x2 + 2; + points[0][1] = points[1][1] = y1 + 1; + points[2][0] = points[1][0] - style->borderWidth.right; + points[3][0] = points[0][0] + style->borderWidth.left; + points[2][1] = points[3][1] = points[0][1]-style->borderWidth.bottom; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.bottom; + d = w & 1; + points[0][0] = x1 - 1; + points[1][0] = x2 + 2 - d; + points[0][1] = points[1][1] = y1 + 1; + points[2][0] = points[1][0] - style->borderWidth.right / 2; + points[3][0] = points[0][0] + style->borderWidth.left / 2 + d; + points[2][1] = points[3][1] = points[0][1] - w/2 - d; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.bottom, shading, true, points, 4); + // clockwise + points[0][0] = x1 + style->borderWidth.left - 1; + points[1][0] = x2 + 1 - style->borderWidth.right + 1; + points[0][1] = points[1][1] = y1 - w + 1; + points[2][0] = points[1][0] + style->borderWidth.right / 2; + points[3][0] = points[0][0] - style->borderWidth.left / 2; + points[2][1] = points[3][1] = points[0][1] + w/2; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.bottom / 3.0); + d = w ? style->borderWidth.bottom - 2 * w : 0; + int w_l = (int) rint(style->borderWidth.left / 3.0); + int w_r = (int) rint(style->borderWidth.right / 3.0); + if (style->borderWidth.bottom == 1) { + view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2); + break; + } + points[0][0] = x2 + 2; + points[1][0] = x1 - 1; + points[0][1] = points[1][1] = y1 + 1; + points[2][0] = points[1][0] + w_l; + points[3][0] = points[0][0] - w_r; + points[2][1] = points[3][1] = points[0][1] - w; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + points[0][0] = x2 + 2 - style->borderWidth.right + w_r; + points[1][0] = x1 - 1 + style->borderWidth.left - w_l; + points[0][1] = points[1][1] = y1 + 1 - w - d; + points[2][0] = x1 - 1 + style->borderWidth.left; + points[3][0] = x2 + 2 - style->borderWidth.right; + points[2][1] = points[3][1] = y1 + 1 - style->borderWidth.bottom; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + break; + } +} + +static void drawBorderLeft(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int points[4][2], d, w; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.left) + return; - points[0][0] = x1; - points[0][1] = y1; - points[1][0] = x2; - points[1][1] = y2; - points[2][0] = x3; - points[2][1] = y3; + switch (style->borderStyle.left) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.left; + view->drawTypedLine(style->borderColor.left, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1+w/2, x1+w/2, y2-w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.left != BORDER_SOLID) + shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + if (style->borderWidth.left == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2); + } else { + points[0][0] = points[1][0] = x1; + points[0][1] = y1 - 1; + points[1][1] = y2 + 1; + points[2][0] = points[3][0] = points[0][0] + style->borderWidth.left; + points[2][1] = points[1][1] - style->borderWidth.bottom; + points[3][1] = points[0][1] + style->borderWidth.top; + view->drawPolygon (style->borderColor.left, shading, true, points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.left; + d = w & 1; + points[0][0] = points[1][0] = x1; + points[0][1] = y1; + points[1][1] = y2; + points[2][0] = points[3][0] = x1 + w / 2 + d; + points[2][1] = y2 - style->borderWidth.bottom / 2; + points[3][1] = y1 + style->borderWidth.top / 2; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + points[0][0] = points[1][0] = x1 + w / 2 + d; + points[0][1] = y1 + style->borderWidth.top / 2; + points[1][1] = y2 - style->borderWidth.bottom / 2; + points[2][0] = points[3][0] = x1 + w; + points[2][1] = y2 - style->borderWidth.bottom; + points[3][1] = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.top, shading, true, points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.left / 3.0); + d = w ? style->borderWidth.left - 2 * w : 0; + int w_b = (int) rint(style->borderWidth.bottom / 3.0); + int w_t = (int) rint(style->borderWidth.top / 3.0); + if (style->borderWidth.left == 1) { + view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2-1); + break; + } + points[0][0] = points[1][0] = x1; + points[0][1] = y1 - 1; + points[1][1] = y2 + 1; + points[2][0] = points[3][0] = points[0][0] + w; + points[2][1] = points[1][1] - w_b; + points[3][1] = points[0][1] + w_t; + view->drawPolygon (style->borderColor.left, shading, true, points, 4); + points[0][0] = points[1][0] = x1 + w + d; + points[0][1] = y1 - 1 + style->borderWidth.top - w_t; + points[1][1] = y2 + 1 - style->borderWidth.bottom + w_b; + points[2][0] = points[3][0] = points[0][0] + w; + points[2][1] = y2 + 1 - style->borderWidth.bottom; + points[3][1] = y1 - 1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.left, shading, true, points, 4); + break; + } +} - view->drawPolygon (color, shading, true, points, 3); +static void drawBorderRight(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int points[4][2], d, w; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.right) + return; + + switch (style->borderStyle.right) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.right; + view->drawTypedLine(style->borderColor.right, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1 - w/2, y1 + w/2, x1 - w/2, y2 - w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.right != BORDER_SOLID) + shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + if (style->borderWidth.right == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2); + } else { + points[0][0] = points[1][0] = x1 + 1; + points[0][1] = y1 - 1; + points[1][1] = y2 + 1; + points[2][0] = points[3][0] = points[0][0]-style->borderWidth.right; + points[2][1] = points[1][1] - style->borderWidth.bottom; + points[3][1] = points[0][1] + style->borderWidth.top; + view->drawPolygon (style->borderColor.right, shading, true,points,4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.right; + d = w & 1; + points[0][0] = points[1][0] = x1 + 1; + points[0][1] = y1; + points[1][1] = y2; + points[2][0] = points[3][0] = points[0][0] - w / 2 - d; + points[2][1] = y2 - style->borderWidth.bottom / 2; + points[3][1] = points[0][1] + style->borderWidth.top / 2; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.right, shading, true, points, 4); + points[0][0] = points[1][0] = x1 + 1 - w / 2 - d; + points[0][1] = y1 + style->borderWidth.top / 2; + points[1][1] = y2 - style->borderWidth.bottom / 2; + points[2][0] = points[3][0] = x1 + 1 - w; + points[2][1] = y2 - style->borderWidth.bottom; + points[3][1] = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_LIGHT: Color::SHADING_DARK; + view->drawPolygon (style->borderColor.right, shading, true, points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.right / 3.0); + d = w ? style->borderWidth.right - 2 * w : 0; + int w_b = (int) rint(style->borderWidth.bottom / 3.0); + int w_t = (int) rint(style->borderWidth.top / 3.0); + if (style->borderWidth.right == 1) { + view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2); + break; + } + points[0][0] = points[1][0] = x1 + 1; + points[0][1] = y1 - 1; + points[1][1] = y2 + 1; + points[2][0] = points[3][0] = points[0][0] - w; + points[2][1] = points[1][1] - w_b; + points[3][1] = points[0][1] + w_t; + view->drawPolygon (style->borderColor.right, shading, true, points, 4); + points[0][0] = points[1][0] = x1 + 1 - w - d; + points[0][1] = y1 - 1 + style->borderWidth.top - w_t; + points[1][1] = y2 + 1 - style->borderWidth.bottom + w_b; + points[2][0] = points[3][0] = points[0][0] - w; + points[2][1] = y2 + 1 - style->borderWidth.bottom; + points[3][1] = y1 - 1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.right, shading, true, points, 4); + break; + } } /** @@ -436,14 +792,13 @@ void drawBorder (View *view, Rectangle *area, { /** \todo a lot! */ Color::Shading dark, light, normal; - Color::Shading top, right, bottom, left; int xb1, yb1, xb2, yb2, xp1, yp1, xp2, yp2; // top left and bottom right point of outer border boundary xb1 = x + style->margin.left; yb1 = y + style->margin.top; - xb2 = x + width - style->margin.right; - yb2 = y + height - style->margin.bottom; + xb2 = x + (width > 0 ? width - 1 : 0) - style->margin.right; + yb2 = y + (height > 0 ? height - 1 : 0) - style->margin.bottom; // top left and bottom right point of inner border boundary xp1 = xb1 + style->borderWidth.left; @@ -455,63 +810,10 @@ void drawBorder (View *view, Rectangle *area, dark = inverse ? Color::SHADING_LIGHT : Color::SHADING_DARK; normal = inverse ? Color::SHADING_INVERSE : Color::SHADING_NORMAL; - switch (style->borderStyle.top) { - case BORDER_INSET: - top = left = dark; - right = bottom = light; - break; - - case BORDER_OUTSET: - top = left = light; - right = bottom = dark; - break; - - default: - top = right = bottom = left = normal; - break; - } - - if (style->borderStyle.top != BORDER_NONE && style->borderColor.top) - view->drawRectangle(style->borderColor.top, top, true, - xb1, yb1, xb2 - xb1, style->borderWidth.top); - - if (style->borderStyle.bottom != BORDER_NONE && style->borderColor.bottom) - view->drawRectangle(style->borderColor.bottom, bottom, true, - xb1, yb2, xb2 - xb1, - style->borderWidth.bottom); - - if (style->borderStyle.left != BORDER_NONE && style->borderColor.left) - view->drawRectangle(style->borderColor.left, left, true, - xb1, yp1, style->borderWidth.left, yp2 - yp1); - - if (style->borderWidth.left > 1) { - if (style->borderWidth.top > 1 && - (style->borderColor.left != style->borderColor.top || - left != top)) - drawTriangle (view, style->borderColor.left, left, - xb1, yp1, xp1, yp1, xb1, yb1); - if (style->borderWidth.bottom > 1 && - (style->borderColor.left != style->borderColor.bottom || - left != bottom)) - drawTriangle (view, style->borderColor.left, left, - xb1, yp2, xp1, yp2, xb1, yb2); - } - - if (style->borderStyle.right != BORDER_NONE && style->borderColor.right) - view->drawRectangle(style->borderColor.right, right, true, - xb2, yp1, - style->borderWidth.right, yp2 - yp1); - - if (style->borderWidth.right > 1) { - if (style->borderWidth.top > 1 && - (style->borderColor.right != style->borderColor.top || - right != top)) - drawTriangle (view, style->borderColor.right, right, - xb2, yp1, xp2, yp1, xb2, yb1); - if (style->borderWidth.bottom > 1 && - (style->borderColor.right != style->borderColor.bottom || - right != bottom)) - drawTriangle (view, style->borderColor.right, right, - xb2, yp2, xp2, yp2, xb2, yb2); - } + drawBorderRight(view, style, xb2, yb1, xb2, yb2); + drawBorderLeft(view, style, xb1, yb1, xb1, yb2); + drawBorderTop(view, style, xb1, yb1, xb2, yb1); + drawBorderBottom(view, style, xb1, yb2, xb2, yb2); } diff --git a/dw/style.hh b/dw/style.hh index 7a401e7f..2f6a98fc 100644 --- a/dw/style.hh +++ b/dw/style.hh @@ -263,6 +263,12 @@ enum DisplayType { DISPLAY_TABLE_CELL }; +enum LineType { + LINE_NORMAL, + LINE_DOTTED, + LINE_DASHED +}; + enum ListStylePosition { LIST_STYLE_POSITION_INSIDE, LIST_STYLE_POSITION_OUTSIDE @@ -166,6 +166,10 @@ public: virtual void drawLine (style::Color *color, style::Color::Shading shading, int x1, int y1, int x2, int y2) = 0; + virtual void drawTypedLine (style::Color *color, + style::Color::Shading shading, + style::LineType type, int width, + int x1, int y1, int x2, int y2) = 0; virtual void drawRectangle (style::Color *color, style::Color::Shading shading, bool filled, int x, int y, int width, int height) = 0; @@ -609,7 +609,7 @@ void CssContext::buildUserAgentStyle () { "sub {vertical-align: sub}" "sup {vertical-align: super}" "s, strike, del {text-decoration: line-through}" - "table {border-spacing: 1px}" + "table {border-spacing: 2px}" "td, th {padding: 2px}" "thead, tbody, tfoot {vertical-align: middle}" "th {font-weight: bolder; text-align: center}" |