/* * File: styleengine.cc * * Copyright 2008-2009 Johannes Hofmann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ #include "../dlib/dlib.h" #include "msg.h" #include "prefs.h" #include "misc.h" #include "html_common.hh" #include "styleengine.hh" #include "web.hh" #include "capi.h" using namespace lout::misc; using namespace dw::core::style; /** * Signal handler for "delete": This handles the case when an instance * of StyleImage is deleted, possibly when the cache client is still * active. * * \todo Not neccessary for dw::Image? (dw::Image also implements * lout::signal::ObservedObject.) */ class StyleImageDeletionReceiver: public lout::signal::ObservedObject::DeletionReceiver { int clientKey; public: StyleImageDeletionReceiver (int clientKey); ~StyleImageDeletionReceiver (); void deleted (lout::signal::ObservedObject *object); }; StyleImageDeletionReceiver::StyleImageDeletionReceiver (int clientKey) { this->clientKey = clientKey; } StyleImageDeletionReceiver::~StyleImageDeletionReceiver () { } void StyleImageDeletionReceiver::deleted (lout::signal::ObservedObject *object) { a_Capi_stop_client (clientKey, 0); delete this; } // ---------------------------------------------------------------------- StyleEngine::StyleEngine (dw::core::Layout *layout, const DilloUrl *pageUrl, const DilloUrl *baseUrl) { StyleAttrs style_attrs; FontAttrs font_attrs; doctree = new Doctree (); stack = new lout::misc::SimpleVector (1); cssContext = new CssContext (); buildUserStyle (); this->layout = layout; this->pageUrl = pageUrl ? a_Url_dup(pageUrl) : NULL; this->baseUrl = baseUrl ? a_Url_dup(baseUrl) : NULL; importDepth = 0; dpmm = layout->dpiX () / 25.4; /* assume dpiX == dpiY */ stackPush (); Node *n = stack->getLastRef (); /* Create a dummy font, attribute, and tag for the bottom of the stack. */ font_attrs.name = prefs.font_sans_serif; font_attrs.size = roundInt(14 * prefs.font_factor); if (font_attrs.size < prefs.font_min_size) font_attrs.size = prefs.font_min_size; if (font_attrs.size > prefs.font_max_size) font_attrs.size = prefs.font_max_size; font_attrs.weight = 400; font_attrs.style = FONT_STYLE_NORMAL; font_attrs.letterSpacing = 0; font_attrs.fontVariant = FONT_VARIANT_NORMAL; style_attrs.initValues (); style_attrs.font = Font::create (layout, &font_attrs); style_attrs.color = Color::create (layout, 0); style_attrs.backgroundColor = Color::create (layout, prefs.bg_color); n->style = Style::create (&style_attrs); } StyleEngine::~StyleEngine () { while (doctree->top ()) endElement (doctree->top ()->element); stackPop (); // dummy node on the bottom of the stack assert (stack->size () == 0); a_Url_free(pageUrl); a_Url_free(baseUrl); delete stack; delete doctree; delete cssContext; } void StyleEngine::stackPush () { static const Node emptyNode = { NULL, NULL, NULL, NULL, NULL, NULL, false, false, NULL }; stack->setSize (stack->size () + 1, emptyNode); } void StyleEngine::stackPop () { Node *n = stack->getRef (stack->size () - 1); delete n->styleAttrProperties; delete n->styleAttrPropertiesImportant; delete n->nonCssProperties; if (n->style) n->style->unref (); if (n->wordStyle) n->wordStyle->unref (); if (n->backgroundStyle) n->backgroundStyle->unref (); stack->setSize (stack->size () - 1); } /** * \brief tell the styleEngine that a new html element has started. */ void StyleEngine::startElement (int element, BrowserWindow *bw) { style (bw); // ensure that style of current node is computed stackPush (); Node *n = stack->getLastRef (); DoctreeNode *dn = doctree->push (); dn->element = element; n->doctreeNode = dn; if (stack->size () > 1) n->displayNone = stack->getRef (stack->size () - 2)->displayNone; } void StyleEngine::startElement (const char *tagname, BrowserWindow *bw) { startElement (a_Html_tag_index (tagname), bw); } void StyleEngine::setId (const char *id) { DoctreeNode *dn = doctree->top (); assert (dn->id == NULL); dn->id = dStrdup (id); } /** * \brief split a string at sep chars and return a SimpleVector of strings */ static lout::misc::SimpleVector *splitStr (const char *str, char sep) { const char *p1 = NULL; lout::misc::SimpleVector *list = new lout::misc::SimpleVector (1); for (;; str++) { if (*str != '\0' && *str != sep) { if (!p1) p1 = str; } else if (p1) { list->increase (); list->set (list->size () - 1, dStrndup (p1, str - p1)); p1 = NULL; } if (*str == '\0') break; } return list; } void StyleEngine::setClass (const char *klass) { DoctreeNode *dn = doctree->top (); assert (dn->klass == NULL); dn->klass = splitStr (klass, ' '); } void StyleEngine::setStyle (const char *styleAttr) { Node *n = stack->getRef (stack->size () - 1); assert (n->styleAttrProperties == NULL); // parse style information from style="" attribute, if it exists if (styleAttr && prefs.parse_embedded_css) { n->styleAttrProperties = new CssPropertyList (true); n->styleAttrPropertiesImportant = new CssPropertyList (true); CssParser::parseDeclarationBlock (baseUrl, styleAttr, strlen (styleAttr), n->styleAttrProperties, n->styleAttrPropertiesImportant); } } /** * \brief Instruct StyleEngine to use the nonCssHints from parent element * This is only used for tables where nonCssHints on the TABLE-element * (e.g. border=1) also affect child elements like TD. */ void StyleEngine::inheritNonCssHints () { Node *pn = stack->getRef (stack->size () - 2); if (pn->nonCssProperties) { Node *n = stack->getRef (stack->size () - 1); CssPropertyList *origNonCssProperties = n->nonCssProperties; n->nonCssProperties = new CssPropertyList(*pn->nonCssProperties, true); if (origNonCssProperties) // original nonCssProperties have precedence origNonCssProperties->apply (n->nonCssProperties); delete origNonCssProperties; } } void StyleEngine::clearNonCssHints () { Node *n = stack->getRef (stack->size () - 1); delete n->nonCssProperties; n->nonCssProperties = NULL; } /** * \brief Use of the background color of the parent style as default. * This is only used in table code to allow for colors specified for * table rows as table rows are currently no widgets and therefore * don't draw any background. */ void StyleEngine::inheritBackgroundColor () { stack->getRef (stack->size () - 1)->inheritBackgroundColor = true; } dw::core::style::Color *StyleEngine::backgroundColor () { for (int i = 1; i < stack->size (); i++) { Node *n = stack->getRef (i); if (n->style && n->style->backgroundColor) return n->style->backgroundColor; } return NULL; } dw::core::style::StyleImage *StyleEngine::backgroundImage (dw::core::style::BackgroundRepeat *bgRepeat, dw::core::style::BackgroundAttachment *bgAttachment, dw::core::style::Length *bgPositionX, dw::core::style::Length *bgPositionY) { for (int i = 1; i < stack->size (); i++) { Node *n = stack->getRef (i); if (n->style && n->style->backgroundImage) { *bgRepeat = n->style->backgroundRepeat; *bgAttachment = n->style->backgroundAttachment; *bgPositionX = n->style->backgroundPositionX; *bgPositionY = n->style->backgroundPositionY; return n->style->backgroundImage; } } return NULL; } /** * \brief set the CSS pseudo class :link. */ void StyleEngine::setPseudoLink () { DoctreeNode *dn = doctree->top (); dn->pseudo = "link"; } /** * \brief set the CSS pseudo class :visited. */ void StyleEngine::setPseudoVisited () { DoctreeNode *dn = doctree->top (); dn->pseudo = "visited"; } /** * \brief tell the styleEngine that a html element has ended. */ void StyleEngine::endElement (int element) { assert (element == doctree->top ()->element); stackPop (); doctree->pop (); } void StyleEngine::preprocessAttrs (dw::core::style::StyleAttrs *attrs) { /* workaround for styling of inline elements */ if (stack->getRef (stack->size () - 2)->inheritBackgroundColor) { attrs->backgroundColor = stack->getRef (stack->size () - 2)->style->backgroundColor; attrs->backgroundImage = stack->getRef (stack->size () - 2)->style->backgroundImage; attrs->backgroundRepeat = stack->getRef (stack->size () - 2)->style->backgroundRepeat; attrs->backgroundAttachment = stack->getRef (stack->size () - 2)->style->backgroundAttachment; attrs->backgroundPositionX = stack->getRef (stack->size () - 2)->style->backgroundPositionX; attrs->backgroundPositionY = stack->getRef (stack->size () - 2)->style->backgroundPositionY; attrs->valign = stack->getRef (stack->size () - 2)->style->valign; } attrs->borderColor.top = (Color *) -1; attrs->borderColor.bottom = (Color *) -1; attrs->borderColor.left = (Color *) -1; attrs->borderColor.right = (Color *) -1; /* initial value of border-width is 'medium' */ attrs->borderWidth.top = 2; attrs->borderWidth.bottom = 2; attrs->borderWidth.left = 2; attrs->borderWidth.right = 2; } void StyleEngine::postprocessAttrs (dw::core::style::StyleAttrs *attrs) { /* if border-color is not specified, use color as computed value */ if (attrs->borderColor.top == (Color *) -1) attrs->borderColor.top = attrs->color; if (attrs->borderColor.bottom == (Color *) -1) attrs->borderColor.bottom = attrs->color; if (attrs->borderColor.left == (Color *) -1) attrs->borderColor.left = attrs->color; if (attrs->borderColor.right == (Color *) -1) attrs->borderColor.right = attrs->color; /* computed value of border-width is 0 if border-style is 'none' or 'hidden' */ if (attrs->borderStyle.top == BORDER_NONE || attrs->borderStyle.top == BORDER_HIDDEN) attrs->borderWidth.top = 0; if (attrs->borderStyle.bottom == BORDER_NONE || attrs->borderStyle.bottom == BORDER_HIDDEN) attrs->borderWidth.bottom = 0; if (attrs->borderStyle.left == BORDER_NONE || attrs->borderStyle.left == BORDER_HIDDEN) attrs->borderWidth.left = 0; if (attrs->borderStyle.right == BORDER_NONE || attrs->borderStyle.right == BORDER_HIDDEN) attrs->borderWidth.right = 0; } /** * \brief Make changes to StyleAttrs attrs according to CssPropertyList props. */ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, BrowserWindow *bw) { FontAttrs fontAttrs = *attrs->font; Font *parentFont = stack->get (i - 1).style->font; char *c, *fontName; int lineHeight; DilloUrl *imgUrl = NULL; /* Determine font first so it can be used to resolve relative lengths. */ for (int j = 0; j < props->size (); j++) { CssProperty *p = props->getRef (j); switch (p->name) { case CSS_PROPERTY_FONT_FAMILY: // Check font names in comma separated list. // Note, that p->value.strVal is modified, so that in future calls // the matching font name can be used directly. fontName = NULL; while (p->value.strVal) { if ((c = strchr(p->value.strVal, ','))) *c = '\0'; dStrstrip(p->value.strVal); if (dStrAsciiCasecmp (p->value.strVal, "serif") == 0) fontName = prefs.font_serif; else if (dStrAsciiCasecmp (p->value.strVal, "sans-serif") == 0) fontName = prefs.font_sans_serif; else if (dStrAsciiCasecmp (p->value.strVal, "cursive") == 0) fontName = prefs.font_cursive; else if (dStrAsciiCasecmp (p->value.strVal, "fantasy") == 0) fontName = prefs.font_fantasy; else if (dStrAsciiCasecmp (p->value.strVal, "monospace") == 0) fontName = prefs.font_monospace; else if (Font::exists(layout, p->value.strVal)) fontName = p->value.strVal; if (fontName) { // font found fontAttrs.name = fontName; break; } else if (c) { // try next from list memmove(p->value.strVal, c + 1, strlen(c + 1) + 1); } else { // no font found break; } } break; case CSS_PROPERTY_FONT_SIZE: if (p->type == CSS_TYPE_ENUM) { switch (p->value.intVal) { case CSS_FONT_SIZE_XX_SMALL: fontAttrs.size = roundInt(8.1 * prefs.font_factor); break; case CSS_FONT_SIZE_X_SMALL: fontAttrs.size = roundInt(9.7 * prefs.font_factor); break; case CSS_FONT_SIZE_SMALL: fontAttrs.size = roundInt(11.7 * prefs.font_factor); break; case CSS_FONT_SIZE_MEDIUM: fontAttrs.size = roundInt(14.0 * prefs.font_factor); break; case CSS_FONT_SIZE_LARGE: fontAttrs.size = roundInt(16.8 * prefs.font_factor); break; case CSS_FONT_SIZE_X_LARGE: fontAttrs.size = roundInt(20.2 * prefs.font_factor); break; case CSS_FONT_SIZE_XX_LARGE: fontAttrs.size = roundInt(24.2 * prefs.font_factor); break; case CSS_FONT_SIZE_SMALLER: fontAttrs.size = roundInt(fontAttrs.size * 0.83); break; case CSS_FONT_SIZE_LARGER: fontAttrs.size = roundInt(fontAttrs.size * 1.2); break; default: assert(false); // invalid font-size enum } } else { computeValue (&fontAttrs.size, p->value.intVal, parentFont, parentFont->size); } if (fontAttrs.size < prefs.font_min_size) fontAttrs.size = prefs.font_min_size; if (fontAttrs.size > prefs.font_max_size) fontAttrs.size = prefs.font_max_size; break; case CSS_PROPERTY_FONT_STYLE: fontAttrs.style = (FontStyle) p->value.intVal; break; case CSS_PROPERTY_FONT_WEIGHT: if (p->type == CSS_TYPE_ENUM) { switch (p->value.intVal) { case CSS_FONT_WEIGHT_BOLD: fontAttrs.weight = 700; break; case CSS_FONT_WEIGHT_BOLDER: fontAttrs.weight += 300; break; case CSS_FONT_WEIGHT_LIGHT: fontAttrs.weight = 100; break; case CSS_FONT_WEIGHT_LIGHTER: fontAttrs.weight -= 300; break; case CSS_FONT_WEIGHT_NORMAL: fontAttrs.weight = 400; break; default: assert(false); // invalid font weight value break; } } else { fontAttrs.weight = p->value.intVal; } if (fontAttrs.weight < 100) fontAttrs.weight = 100; if (fontAttrs.weight > 900) fontAttrs.weight = 900; break; case CSS_PROPERTY_LETTER_SPACING: if (p->type == CSS_TYPE_ENUM) { if (p->value.intVal == CSS_LETTER_SPACING_NORMAL) { fontAttrs.letterSpacing = 0; } } else { computeValue (&fontAttrs.letterSpacing, p->value.intVal, parentFont, parentFont->size); } /* Limit letterSpacing to reasonable values to avoid overflows e.g, * when measuring word width. */ if (fontAttrs.letterSpacing > 1000) fontAttrs.letterSpacing = 1000; else if (fontAttrs.letterSpacing < -1000) fontAttrs.letterSpacing = -1000; break; case CSS_PROPERTY_FONT_VARIANT: fontAttrs.fontVariant = (FontVariant) p->value.intVal; break; default: break; } } attrs->font = Font::create (layout, &fontAttrs); for (int j = 0; j < props->size (); j++) { CssProperty *p = props->getRef (j); switch (p->name) { /* \todo missing cases */ case CSS_PROPERTY_BACKGROUND_ATTACHMENT: attrs->backgroundAttachment = (BackgroundAttachment) p->value.intVal; break; case CSS_PROPERTY_BACKGROUND_COLOR: if (prefs.allow_white_bg || p->value.intVal != 0xffffff) attrs->backgroundColor = Color::create(layout, p->value.intVal); else attrs->backgroundColor = Color::create(layout, prefs.white_bg_replacement); break; case CSS_PROPERTY_BACKGROUND_IMAGE: // p->value.strVal should be absolute, so baseUrl is not needed imgUrl = a_Url_new (p->value.strVal, NULL); break; case CSS_PROPERTY_BACKGROUND_POSITION: computeLength (&attrs->backgroundPositionX, p->value.posVal->posX, attrs->font); computeLength (&attrs->backgroundPositionY, p->value.posVal->posY, attrs->font); break; case CSS_PROPERTY_BACKGROUND_REPEAT: attrs->backgroundRepeat = (BackgroundRepeat) p->value.intVal; break; case CSS_PROPERTY_BORDER_COLLAPSE: attrs->borderCollapse = (BorderCollapse) p->value.intVal; break; case CSS_PROPERTY_BORDER_TOP_COLOR: attrs->borderColor.top = (p->type == CSS_TYPE_ENUM) ? NULL : Color::create (layout, p->value.intVal); break; case CSS_PROPERTY_BORDER_BOTTOM_COLOR: attrs->borderColor.bottom = (p->type == CSS_TYPE_ENUM) ? NULL : Color::create (layout, p->value.intVal); break; case CSS_PROPERTY_BORDER_LEFT_COLOR: attrs->borderColor.left = (p->type == CSS_TYPE_ENUM) ? NULL : Color::create (layout, p->value.intVal); break; case CSS_PROPERTY_BORDER_RIGHT_COLOR: attrs->borderColor.right = (p->type == CSS_TYPE_ENUM) ? NULL : Color::create (layout, p->value.intVal); break; case CSS_PROPERTY_BORDER_BOTTOM_STYLE: attrs->borderStyle.bottom = (BorderStyle) p->value.intVal; break; case CSS_PROPERTY_BORDER_LEFT_STYLE: attrs->borderStyle.left = (BorderStyle) p->value.intVal; break; case CSS_PROPERTY_BORDER_RIGHT_STYLE: attrs->borderStyle.right = (BorderStyle) p->value.intVal; break; case CSS_PROPERTY_BORDER_TOP_STYLE: attrs->borderStyle.top = (BorderStyle) p->value.intVal; break; case CSS_PROPERTY_BORDER_BOTTOM_WIDTH: computeBorderWidth (&attrs->borderWidth.bottom, p, attrs->font); break; case CSS_PROPERTY_BORDER_LEFT_WIDTH: computeBorderWidth (&attrs->borderWidth.left, p, attrs->font); break; case CSS_PROPERTY_BORDER_RIGHT_WIDTH: computeBorderWidth (&attrs->borderWidth.right, p, attrs->font); break; case CSS_PROPERTY_BORDER_TOP_WIDTH: computeBorderWidth (&attrs->borderWidth.top, p, attrs->font); break; case CSS_PROPERTY_BORDER_SPACING: computeValue (&attrs->hBorderSpacing, p->value.intVal,attrs->font); computeValue (&attrs->vBorderSpacing, p->value.intVal,attrs->font); break; case CSS_PROPERTY_BOTTOM: computeLength (&attrs->bottom, p->value.intVal, attrs->font); break; case CSS_PROPERTY_CLEAR: attrs->clear = (ClearType) p->value.intVal; break; case CSS_PROPERTY_COLOR: attrs->color = Color::create (layout, p->value.intVal); break; case CSS_PROPERTY_CURSOR: attrs->cursor = (Cursor) p->value.intVal; break; case CSS_PROPERTY_DISPLAY: attrs->display = (DisplayType) p->value.intVal; if (attrs->display == DISPLAY_NONE) stack->getRef (i)->displayNone = true; break; case CSS_PROPERTY_FLOAT: attrs->vloat = (FloatType) p->value.intVal; break; case CSS_PROPERTY_LEFT: computeLength (&attrs->left, p->value.intVal, attrs->font); break; case CSS_PROPERTY_LINE_HEIGHT: if (p->type == CSS_TYPE_ENUM) { //only valid enum value is "normal" attrs->lineHeight = dw::core::style::LENGTH_AUTO; } else if (p->type == CSS_TYPE_LENGTH_PERCENTAGE_NUMBER) { if (CSS_LENGTH_TYPE (p->value.intVal) == CSS_LENGTH_TYPE_NONE) { attrs->lineHeight = createPerLength(CSS_LENGTH_VALUE(p->value.intVal)); } else if (computeValue (&lineHeight, p->value.intVal, attrs->font, attrs->font->size)) { attrs->lineHeight = createAbsLength(lineHeight); } } break; case CSS_PROPERTY_LIST_STYLE_POSITION: attrs->listStylePosition = (ListStylePosition) p->value.intVal; break; case CSS_PROPERTY_LIST_STYLE_TYPE: attrs->listStyleType = (ListStyleType) p->value.intVal; break; case CSS_PROPERTY_MARGIN_BOTTOM: computeValue (&attrs->margin.bottom, p->value.intVal, attrs->font); if (attrs->margin.bottom < 0) // \todo fix negative margins in dw/* attrs->margin.bottom = 0; break; case CSS_PROPERTY_MARGIN_LEFT: computeValue (&attrs->margin.left, p->value.intVal, attrs->font); if (attrs->margin.left < 0) // \todo fix negative margins in dw/* attrs->margin.left = 0; break; case CSS_PROPERTY_MARGIN_RIGHT: computeValue (&attrs->margin.right, p->value.intVal, attrs->font); if (attrs->margin.right < 0) // \todo fix negative margins in dw/* attrs->margin.right = 0; break; case CSS_PROPERTY_MARGIN_TOP: computeValue (&attrs->margin.top, p->value.intVal, attrs->font); if (attrs->margin.top < 0) // \todo fix negative margins in dw/* attrs->margin.top = 0; break; case CSS_PROPERTY_OVERFLOW: attrs->overflow = (Overflow) p->value.intVal; break; case CSS_PROPERTY_PADDING_TOP: computeValue (&attrs->padding.top, p->value.intVal, attrs->font); break; case CSS_PROPERTY_PADDING_BOTTOM: computeValue (&attrs->padding.bottom, p->value.intVal,attrs->font); break; case CSS_PROPERTY_PADDING_LEFT: computeValue (&attrs->padding.left, p->value.intVal, attrs->font); break; case CSS_PROPERTY_PADDING_RIGHT: computeValue (&attrs->padding.right, p->value.intVal, attrs->font); break; case CSS_PROPERTY_POSITION: attrs->position = (Position) p->value.intVal; break; case CSS_PROPERTY_RIGHT: computeLength (&attrs->right, p->value.intVal, attrs->font); break; case CSS_PROPERTY_TEXT_ALIGN: attrs->textAlign = (TextAlignType) p->value.intVal; break; case CSS_PROPERTY_TEXT_DECORATION: attrs->textDecoration |= p->value.intVal; break; case CSS_PROPERTY_TEXT_INDENT: computeLength (&attrs->textIndent, p->value.intVal, attrs->font); break; case CSS_PROPERTY_TEXT_TRANSFORM: attrs->textTransform = (TextTransform) p->value.intVal; break; case CSS_PROPERTY_TOP: computeLength (&attrs->top, p->value.intVal, attrs->font); break; case CSS_PROPERTY_VERTICAL_ALIGN: attrs->valign = (VAlignType) p->value.intVal; break; case CSS_PROPERTY_WHITE_SPACE: attrs->whiteSpace = (WhiteSpace) p->value.intVal; break; case CSS_PROPERTY_WIDTH: computeLength (&attrs->width, p->value.intVal, attrs->font); break; case CSS_PROPERTY_HEIGHT: computeLength (&attrs->height, p->value.intVal, attrs->font); break; case CSS_PROPERTY_WORD_SPACING: if (p->type == CSS_TYPE_ENUM) { if (p->value.intVal == CSS_WORD_SPACING_NORMAL) { attrs->wordSpacing = 0; } } else { computeValue(&attrs->wordSpacing, p->value.intVal, attrs->font); } /* Limit to reasonable values to avoid overflows */ if (attrs->wordSpacing > 1000) attrs->wordSpacing = 1000; else if (attrs->wordSpacing < -1000) attrs->wordSpacing = -1000; break; case CSS_PROPERTY_MIN_WIDTH: computeLength (&attrs->minWidth, p->value.intVal, attrs->font); break; case CSS_PROPERTY_MAX_WIDTH: computeLength (&attrs->maxWidth, p->value.intVal, attrs->font); break; case CSS_PROPERTY_MIN_HEIGHT: computeLength (&attrs->minHeight, p->value.intVal, attrs->font); break; case CSS_PROPERTY_MAX_HEIGHT: computeLength (&attrs->maxHeight, p->value.intVal, attrs->font); break; case CSS_PROPERTY_Z_INDEX: if (p->type == CSS_LENGTH_TYPE_AUTO) attrs->zIndex = dw::core::style::Z_INDEX_AUTO; else attrs->zIndex = p->value.intVal; break; case PROPERTY_X_LINK: attrs->x_link = p->value.intVal; break; case PROPERTY_X_LANG: attrs->x_lang[0] = D_ASCII_TOLOWER(p->value.strVal[0]); if (attrs->x_lang[0]) attrs->x_lang[1] = D_ASCII_TOLOWER(p->value.strVal[1]); else attrs->x_lang[1] = 0; break; case PROPERTY_X_IMG: attrs->x_img = p->value.intVal; break; case PROPERTY_X_TOOLTIP: attrs->x_tooltip = Tooltip::create(layout, p->value.strVal); break; default: break; } } if (imgUrl && prefs.load_background_images && !stack->getRef (i)->displayNone && !(URL_FLAGS(pageUrl) & URL_SpamSafe)) { attrs->backgroundImage = StyleImage::create(); DilloImage *image = a_Image_new(layout, (void*)attrs->backgroundImage ->getMainImgRenderer(), 0xffffff); // we use the pageUrl as requester to prevent cross // domain requests as specified in domainrc DilloWeb *web = a_Web_new(bw, imgUrl, pageUrl); web->Image = image; a_Image_ref(image); web->flags |= WEB_Image; int clientKey; if ((clientKey = a_Capi_open_url(web, NULL, NULL)) != 0) { a_Bw_add_client(bw, clientKey, 0); a_Bw_add_url(bw, imgUrl); attrs->backgroundImage->connectDeletion (new StyleImageDeletionReceiver (clientKey)); } } a_Url_free (imgUrl); } /** * \brief Resolve relative lengths to absolute values. */ bool StyleEngine::computeValue (int *dest, CssLength value, Font *font) { switch (CSS_LENGTH_TYPE (value)) { case CSS_LENGTH_TYPE_PX: *dest = (int) CSS_LENGTH_VALUE (value); return true; case CSS_LENGTH_TYPE_MM: *dest = roundInt (CSS_LENGTH_VALUE (value) * dpmm); return true; case CSS_LENGTH_TYPE_EM: *dest = roundInt (CSS_LENGTH_VALUE (value) * font->size); return true; case CSS_LENGTH_TYPE_EX: *dest = roundInt (CSS_LENGTH_VALUE(value) * font->xHeight); return true; case CSS_LENGTH_TYPE_NONE: // length values other than 0 without unit are only allowed // in special cases (line-height) and have to be handled // separately. assert ((int) CSS_LENGTH_VALUE (value) == 0); *dest = 0; return true; default: break; } return false; } bool StyleEngine::computeValue (int *dest, CssLength value, Font *font, int percentageBase) { if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_PERCENTAGE) { *dest = roundInt (CSS_LENGTH_VALUE (value) * percentageBase); return true; } else return computeValue (dest, value, font); } bool StyleEngine::computeLength (dw::core::style::Length *dest, CssLength value, Font *font) { int v; if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_PERCENTAGE) { *dest = createPerLength (CSS_LENGTH_VALUE (value)); return true; } else if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_AUTO) { *dest = dw::core::style::LENGTH_AUTO; return true; } else if (computeValue (&v, value, font)) { *dest = createAbsLength (v); return true; } return false; } void StyleEngine::computeBorderWidth (int *dest, CssProperty *p, dw::core::style::Font *font) { if (p->type == CSS_TYPE_ENUM) { switch (p->value.intVal) { case CSS_BORDER_WIDTH_THIN: *dest = 1; break; case CSS_BORDER_WIDTH_MEDIUM: *dest = 2; break; case CSS_BORDER_WIDTH_THICK: *dest = 3; break; default: assert(false); } } else { computeValue (dest, p->value.intVal, font); } } /** * \brief Similar to StyleEngine::style(), but with backgroundColor set. * A normal style might have backgroundColor == NULL to indicate a transparent * background. This method ensures that backgroundColor is set. */ Style * StyleEngine::backgroundStyle (BrowserWindow *bw) { if (!stack->getRef (stack->size () - 1)->backgroundStyle) { StyleAttrs attrs = *style (bw); for (int i = stack->size () - 1; i >= 0 && ! attrs.backgroundColor; i--) attrs.backgroundColor = stack->getRef (i)->style->backgroundColor; assert (attrs.backgroundColor); stack->getRef (stack->size () - 1)->backgroundStyle = Style::create (&attrs); } return stack->getRef (stack->size () - 1)->backgroundStyle; } /** * \brief Create a new style object based on the previously opened / closed * HTML elements and the nonCssProperties that have been set. * This method is private. Call style() to get a current style object. */ Style * StyleEngine::style0 (int i, BrowserWindow *bw) { CssPropertyList props, *styleAttrProperties, *styleAttrPropertiesImportant; CssPropertyList *nonCssProperties; // get previous style from the stack StyleAttrs attrs = *stack->getRef (i - 1)->style; // Ensure that StyleEngine::style0() has not been called before for // this element. // Style computation is expensive so limit it as much as possible. // If this assertion is hit, you need to rearrange the code that is // doing styleEngine calls to call setNonCssHint() before calling // style() or wordStyle() for each new element. assert (stack->getRef (i)->style == NULL); // reset values that are not inherited according to CSS attrs.resetValues (); preprocessAttrs (&attrs); styleAttrProperties = stack->getRef (i)->styleAttrProperties; styleAttrPropertiesImportant = stack->getRef(i)->styleAttrPropertiesImportant; nonCssProperties = stack->getRef (i)->nonCssProperties; // merge style information cssContext->apply (&props, doctree, stack->getRef(i)->doctreeNode, styleAttrProperties, styleAttrPropertiesImportant, nonCssProperties); // apply style apply (i, &attrs, &props, bw); postprocessAttrs (&attrs); stack->getRef (i)->style = Style::create (&attrs); return stack->getRef (i)->style; } Style * StyleEngine::wordStyle0 (BrowserWindow *bw) { StyleAttrs attrs = *style (bw); attrs.resetValues (); if (stack->getRef (stack->size() - 1)->inheritBackgroundColor) { attrs.backgroundColor = style (bw)->backgroundColor; attrs.backgroundImage = style (bw)->backgroundImage; attrs.backgroundRepeat = style (bw)->backgroundRepeat; attrs.backgroundAttachment = style (bw)->backgroundAttachment; attrs.backgroundPositionX = style (bw)->backgroundPositionX; attrs.backgroundPositionY = style (bw)->backgroundPositionY; } attrs.valign = style (bw)->valign; stack->getRef(stack->size() - 1)->wordStyle = Style::create(&attrs); return stack->getRef (stack->size () - 1)->wordStyle; } /** * \brief Recompute all style information from scratch * This is used to take into account CSS styles for the HTML-element. * The CSS data is only completely available after parsing the HEAD-section * and thereby after the HTML-element has been opened. * Note that restyle() does not change any styles in the widget tree. */ void StyleEngine::restyle (BrowserWindow *bw) { for (int i = 1; i < stack->size (); i++) { Node *n = stack->getRef (i); if (n->style) { n->style->unref (); n->style = NULL; } if (n->wordStyle) { n->wordStyle->unref (); n->wordStyle = NULL; } if (n->backgroundStyle) { n->backgroundStyle->unref (); n->backgroundStyle = NULL; } style0 (i, bw); } } void StyleEngine::parse (DilloHtml *html, DilloUrl *url, const char *buf, int buflen, CssOrigin origin) { if (importDepth > 10) { // avoid looping with recursive @import directives MSG_WARN("Maximum depth of CSS @import reached--ignoring stylesheet.\n"); return; } importDepth++; CssParser::parse (html, url, cssContext, buf, buflen, origin); importDepth--; } /** * \brief Create the user agent style. * * The user agent style defines how dillo renders HTML in the absence of * author or user styles. */ void StyleEngine::init () { const char *cssBuf = "body {margin: 5px}" "big {font-size: 1.17em}" "blockquote, dd {margin-left: 40px; margin-right: 40px}" "center {text-align: center}" "dt {font-weight: bolder}" ":link {color: blue; text-decoration: underline; cursor: pointer}" ":visited {color: #800080; text-decoration: underline; cursor: pointer}" "h1, h2, h3, h4, h5, h6, b, strong {font-weight: bolder}" "address, article, aside, center, div, figure, figcaption, footer," " h1, h2, h3, h4, h5, h6, header, nav, ol, p, pre, section, ul" " {display: block}" "i, em, cite, address, var {font-style: italic}" ":link img, :visited img {border: 1px solid}" "frameset, ul, ol, dir {margin-left: 40px}" /* WORKAROUND: It should be margin: 1em 0 * but as we don't collapse these margins yet, it * look better like this. */ "p {margin: 0.5em 0}" "figure {margin: 1em 40px}" "h1 {font-size: 2em; margin-top: .67em; margin-bottom: 0}" "h2 {font-size: 1.5em; margin-top: .75em; margin-bottom: 0}" "h3 {font-size: 1.17em; margin-top: .83em; margin-bottom: 0}" "h4 {margin-top: 1.12em; margin-bottom: 0}" "h5 {font-size: 0.83em; margin-top: 1.5em; margin-bottom: 0}" "h6 {font-size: 0.75em; margin-top: 1.67em; margin-bottom: 0}" "hr {width: 100%; border: 1px inset}" "li {margin-top: 0.1em; display: list-item}" "pre {white-space: pre}" "ol {list-style-type: decimal}" "ul {list-style-type: disc}" "ul ul {list-style-type: circle}" "ul ul ul {list-style-type: square}" "ul ul ul ul {list-style-type: disc}" "ins, u {text-decoration: underline}" "small, sub, sup {font-size: 0.83em}" "sub {vertical-align: sub}" "sup {vertical-align: super}" "s, strike, del {text-decoration: line-through}" /* HTML5 spec notes that mark styling "is just a suggestion and can be * changed based on implementation feedback" */ "mark {background: yellow; color: black;}" "table {border-spacing: 2px}" "td, th {padding: 2px}" "thead, tbody, tfoot {vertical-align: middle}" "th {font-weight: bolder; text-align: center}" "code, tt, pre, samp, kbd {font-family: monospace}" /* WORKAROUND: Reset font properties in tables as some * pages rely on it (e.g. gmail). * http://developer.mozilla.org/en-US/Fixing_Table_Inheritance_in_Quirks_Mode * has a detailed description of the issue. */ "table, caption {font-size: medium; font-weight: normal}"; CssContext context; CssParser::parse (NULL, NULL, &context, cssBuf, strlen (cssBuf), CSS_ORIGIN_USER_AGENT); } void StyleEngine::buildUserStyle () { Dstr *style; char *filename = dStrconcat(dGethomedir(), "/.dillo/style.css", NULL); if ((style = a_Misc_file2dstr(filename))) { CssParser::parse (NULL,NULL,cssContext,style->str, style->len,CSS_ORIGIN_USER); dStr_free (style, 1); } dFree (filename); }