/* Render.cc

Copyright © 2003 David Reveman.

This file is part of Waimea.

Waimea 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 2, or (at your option) any later
version.

Waimea is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Waimea; see the file COPYING. If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */

#ifdef    HAVE_CONFIG_H
#  include "../config.h"
#endif // HAVE_CONFIG_H

extern "C" {
    
#ifdef    STDC_HEADERS
#  include <stdlib.h>
#  include <string.h>
#endif // STDC_HEADERS    

#ifdef    HAVE_LIBGEN_H
#  include <libgen.h>
#else
    char *dirname(char *name) {
        int i = strlen(name);
        for (; i >= 0 && name[i] != '/'; i--);
        name[i] = '\0';
        return name;
    }
#endif // HAVE_LIBGEN_H

}

#include "Render.hh"
#include "Screen.hh"

#ifdef    PNG
#  include "Png.hh"
#endif // PNG

Tst<cairo_filter_t>      *filter_tst;
Tst<cairo_extend_t>      *extend_tst;
Tst<PatternType>         *pattern_type_tst;
Tst<cairo_line_cap_t>    *line_cap_tst;
Tst<cairo_line_join_t>   *line_join_tst;
Tst<cairo_fill_rule_t>   *fill_rule_tst;
Tst<DrawingOrder>        *drawing_order_tst;
Tst<cairo_font_weight_t> *weight_tst;
Tst<cairo_font_slant_t>  *slant_tst;
Tst<ImageScaleType>      *image_scale_type_tst;
Tst<int>                 *gravity_type_tst;
Tst<HorizontalAlignment> *horizontal_alignment_tst;
Tst<VerticalAlignment>   *vertical_alignment_tst;
Tst<cairo_operator_t>    *operator_tst;

WaSurface::WaSurface(Display *_display,
                     cairo_surface_t *sp, Pixmap p, Pixmap b,
                     unsigned char *_data, unsigned int w, unsigned int h) :
    RefCounted<WaSurface>(this) {
    display = _display;
    crsurface = sp;
    pixmap = p;
    bitmap = b;
    width = w;
    height = h;
    data = _data;
}

WaSurface::~WaSurface(void) {
    if (crsurface) cairo_surface_destroy(crsurface);
    if (pixmap) XFreePixmap(display, pixmap);
    if (bitmap) XFreePixmap(display, bitmap);
    if (data) delete [] data;
}

CacheSurface::CacheSurface(Display *_display, cairo_surface_t *sp, Pixmap p,
                           unsigned char *_data, unsigned int w,
                           unsigned int h, map<int, RenderGroup *> *dg) :
    WaSurface(_display, sp, p, None, _data, w, h) {
    map<int, RenderGroup *>::iterator it = dg->begin();
    for (; it != dg->end(); it++)
        dynamic_groups.insert(
            make_pair((*it).first, (RenderGroup *) ((*it).second)->ref()));
    group = NULL;
}

CacheSurface::~CacheSurface(void) {
    if (group) {
        group->cache.remove(this);
        group->unref();
    }
    MAPUNREFSECOND(dynamic_groups);
}

bool CacheSurface::dynamicGroupsMatch(DWindowObject *dwo) {
    if (dwo->sb->dynamic_groups.size() != dynamic_groups.size())
        return false;
    
    map<int, RenderGroup *>::iterator it = dynamic_groups.begin();
    for (; it != dynamic_groups.end(); it++) {
        map<int, RenderGroup *>::iterator dwo_it =
            dwo->sb->dynamic_groups.find((*it).first);
        if (dwo_it == dwo->sb->dynamic_groups.end()) return false;
        else if ((*dwo_it).second != (*it).second) return false;
    }
    
    return true;
}

WaColor::WaColor(double r, double g, double b, double a) :
    RefCounted<WaColor>(this) {
    alpha = a; red = r; green = g; blue = b;
}

bool WaColor::parseColor(WaScreen *ws, const char *spec) {
    XColor core_color;
    if (! XParseColor(ws->display, ws->colormap, spec, &core_color))
        return false;
    red = core_color.red / 65535.0;
    green = core_color.green / 65535.0;
    blue = core_color.blue / 65535.0;
    return true;
}

RenderOp::RenderOp(RenderOpType _type) : RefCounted<RenderOp>(this) {
    xrop_set = false;
    rotation = RENDER_ROTATION_DEFAULT;
    type = _type;
    _x = _x2 = RENDER_X_DEFAULT;
    _y = _y2 = RENDER_Y_DEFAULT;
    _xu = _yu = _x2u = _y2u = PXLenghtUnitType;
    _w = _h = 100;
    _wu = _hu = PCTLenghtUnitType;
    halign = RENDER_HALIGN_DEFAULT;
    valign = RENDER_VALIGN_DEFAULT;
    gravity = gravity2 = RENDER_GRAVITY_DEFAULT;
    x2_set = y2_set = false;
}

void RenderOp::inheritAttributes(RenderOp *inherit) {
    xrop_set = inherit->xrop_set;
    rotation = inherit->rotation;
    _x = inherit->_x;
    _x2 = inherit->_x2;
    _y = inherit->_y;
    _y2 = inherit->_y2;
    _xu = inherit->_xu;
    _yu = inherit->_yu;
    _x2u = inherit->_x2u;
    _y2u = inherit->_y2u;
    _w = inherit->_w;
    _h = inherit->_h;
    _wu = inherit->_wu;
    _hu = inherit->_hu;
    halign = inherit->halign;
    valign = inherit->valign;
    gravity = inherit->gravity;
    gravity2 = inherit->gravity2;
    x2_set = inherit->x2_set;
    y2_set = inherit->y2_set;
}

void RenderOp::applyAttributes(Parser *parser, Tst<char *> *attr) {
    attributes_get_render(parser, attr, &xrop, &xrop_set, &rotation);
    attributes_get_area_default(parser, attr, &_x, &_y, &_xu, &_yu, &gravity,
                                &_x2, &_y2, &_x2u, &_y2u,
                                &gravity2, &x2_set, &y2_set,
                                &_w, &_h, &_wu, &_hu, &halign, &valign);
}

void RenderOp::calcWidth(double parent_width, double hdpi,
                         unsigned int *return_width) {
    double rw;
    calc_length(_w, _wu, hdpi, parent_width, &rw);
    *return_width = WA_ROUND_U(rw);
}

void RenderOp::fcalcWidth(double parent_width, double hdpi,
                          double *return_width) {
    calc_length(_w, _wu, hdpi, parent_width, return_width);
}

void RenderOp::calcHeight(double parent_height, double vdpi,
                          unsigned int *return_height) {
    double rh;
    calc_length(_h, _hu, vdpi, parent_height, &rh);
    *return_height = WA_ROUND_U(rh);
}

void RenderOp::calcPositionAndSize(double parent_width,
                                   double parent_height,
                                   double hdpi, double vdpi,
                                   double *return_x, double *return_y,
                                   double *return_width,
                                   double *return_height) {
    calc_position_and_size(halign, valign, gravity,
                           _xu, _yu, _wu, _hu,
                           parent_width, parent_height, hdpi, vdpi,
                           _w, _h, _x, _y, return_x, return_y,
                           return_width, return_height);
    if (x2_set || y2_set) {
        double tmp_x2, tmp_y2;
        calc_position(gravity2, _x2u, _y2u, parent_width, parent_height,
                      hdpi, vdpi, _x2, _y2, &tmp_x2, &tmp_y2);
        if (x2_set) *return_width = tmp_x2 - *return_x;
        if (y2_set) *return_height = tmp_y2 - *return_y;
    }
}

void RenderOp::calcPositionFromSize(double parent_width, double parent_height,
                                    double width, double height,
                                    LenghtUnitType wu, LenghtUnitType hu,
                                    double hdpi, double vdpi,
                                    double *return_x, double *return_y) {
    calc_position_and_size(halign, valign, gravity, _xu, _yu,
                           wu, hu, parent_width, parent_height, hdpi, vdpi,
                           width, height, _x, _y, return_x, return_y,
                           NULL, NULL);
}

RenderGroup::RenderGroup(WaScreen *_ws, char *_name) :
    RenderOp(RenderOpGroupType) {
    opacity = RENDER_OPACITY_DEFAULT;
    has_dynamic_op = is_a_style = false;
    cacheable = true;
    ws = _ws;
    if (_name) name = WA_STRDUP(_name);
    else name = NULL;
    return_surface = NULL;
    parent_surface = NULL;
}

RenderGroup::~RenderGroup(void) {
    clear();
    if (name) delete [] name;
}

void RenderGroup::clear(void) {
    clearCache();
    
    while (! operations.empty()) {
        operations.back()->unref();
        operations.pop_back();
    }
}

void RenderGroup::clearCache(void) {
    while (! cache.empty())
        cache.pop_back();
}

void RenderGroup::inheritContent(RenderGroup *inherit_group) {
    list<RenderOp *>::iterator oit = inherit_group->operations.begin();
    for (; oit != inherit_group->operations.end(); oit++)
        operations.push_back((*oit)->ref());

    if (inherit_group->has_dynamic_op) has_dynamic_op = true;
    if (! inherit_group->cacheable) cacheable = false;
}

void RenderGroup::inheritAttributes(RenderGroup *inherit_group) {
    opacity = inherit_group->opacity;
    if (! inherit_group->cacheable) cacheable = false;

    ((RenderOp *) this)->inheritAttributes((RenderOp *) inherit_group);
}

void RenderGroup::applyAttributes(Parser *parser, Tst<char *> *attr) {
    char *value = parser->attrGetString(attr, "inherit", NULL);
    if (value) {
        RenderGroup *inherit_group = ws->getRenderGroupNamed(value, false);
        if (inherit_group == this) {
            parser->warning("group %s cannot inherit itself\n", name);
        } else if (inherit_group) {
            inheritAttributes(inherit_group);
        } else {
            if (! parser->attrGetBool(attr, "ignore_missing", false))
                parser->warning("undefined group: %s\n", value);
        }
    }
    
    ((RenderOp *) this)->applyAttributes(parser, attr);
    
    opacity = parser->attrGetDouble(attr, "opacity", opacity);
    cacheable = parser->attrGetBool(attr, "cache", cacheable);
}

void RenderGroup::addToCache(CacheSurface *cache_item, DWindowObject *dwo) {
    dwo->sb->cache_table.insert(
        make_pair(this, (CacheSurface *) cache_item->ref()));

    cache.push_front(cache_item);
    cache_item->group = (RenderGroup *) ref();
}

CacheSurface *RenderGroup::findCachedSurface(DWindowObject *dwo,
                                             unsigned int w, unsigned int h) {
    CacheSurface *new_cache_item = NULL;
    list<CacheSurface *>::iterator it = cache.begin();
    for (; it != cache.end(); it++) {
        if ((*it)->width == w && (*it)->height == h &&
            (*it)->dynamicGroupsMatch(dwo)) {
            new_cache_item = (CacheSurface *) (*it)->ref();
            break;
        }
    }
    
    map<RenderGroup *, CacheSurface *>::iterator mit =
        dwo->sb->cache_table.find(this);
    if (mit != dwo->sb->cache_table.end()) {
        ((*mit).second)->unref();
        dwo->sb->cache_table.erase(mit);
    }

    if (new_cache_item) {
        dwo->sb->cache_table.insert(
            make_pair(this, (CacheSurface *) new_cache_item->ref()));
    }
    
    return new_cache_item;
}

void RenderGroup::render(DWindowObject *dwo, cairo_t *cr,
                         cairo_surface_t *crsurface,
                         unsigned int w, unsigned int h) {
    double x, y, _width, _height;
    unsigned int width, height;
    Pixmap pixmap = None;
    unsigned char *data = NULL;

    if (is_a_style) {
        width = w;
        height = h;
        x = y = 0.0;
    } else {
        calcPositionAndSize(w, h, ws->hdpi, ws->vdpi, &x, &y,
                            &_width, &_height);
        width = WA_ROUND_U(_width);
        height = WA_ROUND_U(_height);
    }

    CacheSurface *cache_surface = findCachedSurface(dwo, width, height);
        
    if (! cache_surface) {
        cairo_surface_t *groupsurface;

        if (crsurface) {
            groupsurface =
                cairo_surface_create_similar(crsurface,
                                             CAIRO_CONTENT_COLOR_ALPHA,
                                             width, height);
        } else if (parent_surface) {
          groupsurface =
                cairo_surface_create_similar(parent_surface,
                                             CAIRO_CONTENT_COLOR_ALPHA,
                                             width, height);
        } else {
            if (ws->waimea->client_side_rendering) {
                data = new unsigned char[width * height * sizeof(WaPixel)];
                memset (data, 0, width * height * sizeof(WaPixel));
                groupsurface =
                    cairo_image_surface_create_for_data(data,
                                                   CAIRO_FORMAT_ARGB32,
                                                   width, height, width * 4);
            } else {
                GC gc;
                XGCValues gcv;
                Visual *visual;
                Colormap colormap;
                int depth = ws->screen_depth;

                if (dwo->type == RootType) {
                  XWindowAttributes attr;
                  
                  XGetWindowAttributes (ws->display, ws->id, &attr);
                  visual = attr.visual;
                  depth = attr.depth;
                  colormap = attr.colormap;
                } else {
                  visual = ws->visual;
                  depth = ws->screen_depth;
                  colormap = ws->colormap;
                }
                
                pixmap = XCreatePixmap(ws->display, ws->id,
                                       width, height, depth);

                gcv.foreground = 0x00000000;
                gc = XCreateGC(ws->display, pixmap, GCForeground, &gcv);
                XFillRectangle(ws->display, pixmap, gc, 0, 0, w, h);
                XFreeGC(ws->display, gc);

                groupsurface =
                  cairo_xlib_surface_create(ws->display, pixmap,
                                            visual,
                                            (depth == 32)?
                                            CAIRO_FORMAT_ARGB32:
                                            CAIRO_FORMAT_RGB24,
                                            colormap);
            }
        }
        
        cairo_destroy(cr);
        cr = cairo_create(groupsurface);
        cairo_identity_matrix(cr);
        cairo_set_operator(cr, RENDER_OPERATOR_DEFAULT);
        list<RenderOp *>::iterator oit = operations.begin();
        for (; oit != operations.end(); oit++) {
            cairo_save(cr);
            (*oit)->render(dwo, cr, groupsurface, width, height);
            cairo_restore(cr);
        }

        cache_surface = new CacheSurface(ws->display, groupsurface, pixmap,
                                         data, width, height,
                                         &dwo->sb->dynamic_groups);
        
        if (cacheable)
            addToCache(cache_surface, dwo);
    }

    if (crsurface) {
        cairo_destroy(cr);
        cr = cairo_create(crsurface);
        cairo_set_operator(cr, RENDER_OPERATOR_DEFAULT);
        if (xrop_set) cairo_set_operator(cr, xrop);
        cairo_translate(cr, x, y);
        cairo_rotate(cr, rotation);
        // XXX: I think I may've screwed this up, at least as to whether there
        // should be any clipping. -mjmt
        cairo_set_source_surface(cr, cache_surface->crsurface, 0., 0.);
        cairo_rectangle(cr, 0., 0., width, height);
        cairo_clip(cr);
        cairo_paint_with_alpha(cr, opacity);
    }

    if (return_surface)
        *return_surface = cache_surface;
    else
        cache_surface->unref();
}

void RenderGroup::flattenTextOps(list<RenderOpText *> **textopsptr) {
    if (is_a_style) {
        Style *s = (Style *) this;
        if (s->textops) {
            while (! s->textops->empty()) {
                s->textops->back()->unref();
                s->textops->pop_back();
            }
            delete s->textops;
            s->textops = NULL;
        }
    }
    list<RenderOp *>::iterator oit = operations.begin();
    for (; oit != operations.end(); oit++) {
        if ((*oit)->type == RenderOpTextType) {
            if (! *textopsptr) *textopsptr = new list<RenderOpText *>;
            (*textopsptr)->push_back((RenderOpText *) (*oit)->ref());
        } else if ((*oit)->type == RenderOpGroupType) {
            ((RenderGroup *) *oit)->flattenTextOps(textopsptr);
        }
    }
    if (is_a_style) {
        if (((Style *) this)->shapemask)
            ((Style *) this)->shapemask->flattenTextOps(textopsptr);
    }
}

RenderOpDynamic::RenderOpDynamic(void) : RenderOp(RenderOpDynamicType) {}

void RenderOpDynamic::applyAttributes(Parser *parser, Tst<char *> *attr) {
    rotation =  _x = _y = _w = _h = gravity = gravity2 = INT_MAX;
    halign = HNullAlignType;
    valign = VNullAlignType;
    
    opacity = parser->attrGetDouble(attr, "opacity", INT_MAX);
    ((RenderOp *) this)->applyAttributes(parser, attr);
}

void RenderOpDynamic::render(DWindowObject *dwo, cairo_t *cr,
                             cairo_surface_t *surface,
                             unsigned int w, unsigned int h) {
    RenderGroup saved_attributes;
    RenderGroup set_attributes;

    RenderGroup *dynamic = NULL;

    list<int>::iterator it = dynamic_order.begin();
    for (; it != dynamic_order.end(); it++) {
        map<int, RenderGroup *>::iterator dit =
            dwo->sb->dynamic_groups.find(*it);
        if (dit != dwo->sb->dynamic_groups.end()) {
            dynamic = (*dit).second;
            break;
        }
    }
    if (! dynamic) return;

    saved_attributes.inheritAttributes(dynamic);
    set_attributes.inheritAttributes(dynamic);

    if (_x != INT_MAX) {
        set_attributes._x = _x;
        set_attributes._xu = _xu;
    }
    if (_y != INT_MAX) {
        set_attributes._y = _y;
        set_attributes._yu = _yu;
    }
    if (_w != INT_MAX) {
        set_attributes._w = _w;
        set_attributes._wu = _wu;
    }
    if (_h != INT_MAX) {
        set_attributes._h = _h;
        set_attributes._hu = _hu;
    }
    if (x2_set) {
        set_attributes.x2_set = x2_set;
        set_attributes._x2 = _x2;
        set_attributes._x2u = _x2u;
    }
    if (y2_set) {
        set_attributes.y2_set = y2_set;
        set_attributes._y2 = _y2;
        set_attributes._y2u = _y2u;
    }
    if (gravity != INT_MAX)
        set_attributes.gravity = gravity;
    if (gravity2 != INT_MAX)
        set_attributes.gravity2 = gravity2;
    if (xrop_set) {
        set_attributes.xrop_set = xrop_set;
        set_attributes.xrop = xrop;
    }
    if (valign != VNullAlignType)
        set_attributes.valign = valign;
    if (halign != HNullAlignType)
        set_attributes.halign = halign;
    if (rotation != INT_MAX)
        set_attributes.rotation = rotation;
    if (opacity != INT_MAX)
        set_attributes.opacity = opacity;

    dynamic->inheritAttributes(&set_attributes);

    dynamic->render(dwo, cr, surface, w, h);

    dynamic->inheritAttributes(&saved_attributes);
}

PathOperator::PathOperator(PathOperatorType _type) :
    RefCounted<PathOperator>(this) {
    type = _type;
    if (type == PathOperatorCloseType) size = 0;
    else if (type == PathOperatorCurveToType ||
             type == PathOperatorRelCurveToType ||
             type == PathOperatorArcToType ||
             type == PathOperatorRelArcToType) size = 3;
    else size = 1;
    
    if (size) {
        _a = new double[size];
        _b = new double[size];
        _au = new LenghtUnitType[size];
        _bu = new LenghtUnitType[size];
        gravity = new int[size];
        
        for (int i = 0; i < size; i++) {
            _a[i] = _b[i] = 0;
            _au[i] = _bu[i] = PXLenghtUnitType;
            gravity[i] = RENDER_GRAVITY_DEFAULT;
        }
    }
}

PathOperator::~PathOperator(void) {
    if (size) {
        delete [] _a;
        delete [] _b;
        delete [] _au;
        delete [] _bu;
        delete [] gravity;
    }
}

void PathOperator::applyAttributes(Parser *parser, Tst<char *> *attr) {
    switch (type) {
        case PathOperatorArcToType:
        case PathOperatorCurveToType:
            attributes_get_position(parser, attr, "x1", "y1", "gravity1",
                                    &_a[1], &_b[1], &_au[1], &_bu[1],
                                    &gravity[1]);
            attributes_get_position(parser, attr, "x2", "y2", "gravity2",
                                    &_a[2], &_b[2], &_au[2], &_bu[2],
                                    &gravity[2]);
            if (type == PathOperatorArcToType) {
                attributes_get_size(parser, attr, "radius", "radius",
                                    &_a[0], &_b[0], &_au[0], &_bu[0]);
                break;
            }
        case PathOperatorMoveToType:
        case PathOperatorLineToType:
            attributes_get_position_default(parser, attr, &_a[0], &_b[0],
                                            &_au[0], &_bu[0], &gravity[0]);
            break;
        case PathOperatorRelArcToType:
        case PathOperatorRelCurveToType:
            attributes_get_size(parser, attr, "width1", "height1",
                                &_a[1], &_b[1], &_au[1], &_bu[1]);
            attributes_get_size(parser, attr, "width2", "height2",
                                &_a[2], &_b[2], &_au[2], &_bu[2]);
            if (type == PathOperatorRelArcToType) {
                attributes_get_size(parser, attr, "radius", "radius",
                                    &_a[0], &_b[0], &_au[0], &_bu[0]);
                break;
            }
        case PathOperatorRelLineToType:
        case PathOperatorRelMoveToType:
            attributes_get_size_default(parser, attr, &_a[0], &_b[0],
                                        &_au[0], &_bu[0]);
            break;
            
    }
}

WaColorStop::WaColorStop(double _offset, WaColor *_color)
    : RefCounted<WaColorStop>(this) {
    color = _color;
    offset = _offset;
}

WaColorStop::~WaColorStop(void) {
    color->unref();
}

static const struct FilterMap {
    char *name;
    cairo_filter_t filter;
} filter_map[] = {
    { "fast", CAIRO_FILTER_FAST },
    { "good", CAIRO_FILTER_GOOD },
    { "best", CAIRO_FILTER_BEST },
    { "nearest", CAIRO_FILTER_NEAREST },
    { "bilinear", CAIRO_FILTER_BILINEAR },
    { "gaussian", CAIRO_FILTER_GAUSSIAN }
};

static const struct ExtendMap {
    char *name;
    cairo_extend_t extend;
} extend_map[] = {
    { "none", CAIRO_EXTEND_NONE },
    { "repeat", CAIRO_EXTEND_REPEAT },
    { "reflect", CAIRO_EXTEND_REFLECT }
};

static const struct PatternTypeMap {
    char *name;
    PatternType pattern_type;
} pattern_type_map[] = {
    { "linear", LinearPatternType },
    { "radial", RadialPatternType },
    { "group", GroupPatternType }
};

RenderPattern::RenderPattern(void) : RefCounted<RenderPattern>(this) {
    filter = RENDER_FILTER_DEFAULT;
    extend = RENDER_EXTEND_DEFAULT;

    type = LinearPatternType;

    start_gravity = end_gravity = RENDER_GRAVITY_DEFAULT;
    start_x = start_y = 0;
    end_x = end_y = 100;
    start_x_u = start_y_u = PXLenghtUnitType;
    end_x_u = end_y_u = PCTLenghtUnitType;

    center_gravity = RENDER_GRAVITY_DEFAULT;
    center_x = center_y = 50;
    radius_dx = radius_dy = 50;
    center_x_u = center_y_u = radius_dx_u = radius_dy_u = PCTLenghtUnitType;

    group = NULL;
}

RenderPattern::~RenderPattern(void) {
    clear();
    if (group) group->unref();
}

void RenderPattern::clear(void) {
    while (! color_stops.empty()) {
        color_stops.back()->unref();
        color_stops.pop_back();
    }
}

void RenderPattern::inheritContent(RenderPattern *inherit_pattern) {
    list<WaColorStop *>::iterator it = inherit_pattern->color_stops.begin();
    for (; it != inherit_pattern->color_stops.end(); it++)
        color_stops.push_back((*it)->ref());
}

void RenderPattern::inheritAttributes(RenderPattern *inherit) {
    filter = inherit->filter;
    extend = inherit->extend;
    type = inherit->type;
    start_x = inherit->start_x;
    start_y = inherit->start_y;
    start_gravity = inherit->start_gravity;
    end_x = inherit->end_x;
    end_y = inherit->end_y;
    end_gravity = inherit->end_gravity;
    start_x_u = inherit->start_x_u;
    start_y_u = inherit->start_y_u;
    end_x_u = inherit->end_x_u;
    end_y_u = inherit->end_y_u;
    center_x = inherit->center_x;
    center_y = inherit->center_y;
    center_gravity = inherit->center_gravity;
    radius_dx = inherit->radius_dx;
    radius_dy = inherit->radius_dy;
    center_x_u = inherit->center_x_u;
    center_y_u = inherit->center_y_u;
    radius_dx_u = inherit->radius_dx_u;
    radius_dy_u = inherit->radius_dy_u;
    if (group) group->unref();
    group = NULL;
    if (inherit->group)
        group = (RenderGroup *) inherit->group->ref();
}

void RenderPattern::applyAttributes(Parser *parser, Tst<char *> *attr) {
    char *value = parser->attrGetString(attr, "inherit", NULL);
    if (value) {
        RenderPattern *inherit_pattern =
            parser->ws->getPatternNamed(value, false);
        if (inherit_pattern == this) {
            parser->warning("pattern %s cannot inherit itself\n", value);
        } else if (inherit_pattern) {
            inheritAttributes(inherit_pattern);
        } else {
            if (! parser->attrGetBool(attr, "ignore_missing", false))
                parser->warning("undefined pattern: %s\n", value);
        }
    }

    value = parser->attrGetString(attr, "type", NULL);
    if (value) {
        Tst<PatternType>::iterator it = pattern_type_tst->find(value);
        if (it != pattern_type_tst->end()) type = *it;
    }

    attributes_get_position(parser, attr, "start_x", "start_y",
                            "start_gravity",
                            &start_x, &start_y, &start_x_u, &start_y_u,
                            &start_gravity);
    attributes_get_position(parser, attr, "end_x", "end_y",
                            "end_gravity",
                            &end_x, &end_y, &end_x_u, &end_y_u,
                            &end_gravity);

    attributes_get_position(parser, attr, "center_x", "center_y",
                            "center_gravity",
                            &center_x, &center_y, &center_x_u, &center_y_u,
                            &center_gravity);
    attributes_get_size(parser, attr, "radius_dx", "radius_dy",
                        &radius_dx, &radius_dy, &radius_dx_u, &radius_dy_u);
    
    value = parser->attrGetString(attr, "filter", NULL);
    if (value) {
        Tst<cairo_filter_t>::iterator it = filter_tst->find(value);
        if (it != filter_tst->end()) filter = *it;
    }

    value = parser->attrGetString(attr, "spread", NULL);
    if (value) {
        Tst<cairo_extend_t>::iterator it = extend_tst->find(value);
        if (it != extend_tst->end()) extend = *it;
    }

    value = parser->attrGetString(attr, "group", NULL);
    if (value) {
        group = parser->ws->getRenderGroupNamed(value, false);
        if (! group) {
            parser->warning("undefined group: %s\n", value);
        } else
            group = (RenderGroup *) group->ref();
    }
}

void RenderPattern::setcairo_pattern(DWindowObject *dwo,
                                     cairo_t *cr,
                                     cairo_surface_t *crsurface,
                                     unsigned int w, unsigned int h) {
  cairo_pattern_t *pattern;

  switch (type) {
    case LinearPatternType: {
      double x0, y0, x1, y1;

      calc_position(start_gravity, start_x_u, start_y_u,
                    w, h, dwo->ws->hdpi, dwo->ws->vdpi,
                    start_x, start_y, &x0, &y0);
      calc_position(end_gravity, end_x_u, end_y_u,
                    w, h, dwo->ws->hdpi, dwo->ws->vdpi,
                    end_x, end_y, &x1, &y1);
      
      pattern = cairo_pattern_create_linear (x0, y0, x1, y1);
    } break;
    case RadialPatternType: {
      double cx, cy, rx, ry;

      calc_position(center_gravity, center_x_u, center_y_u,
                    w, h, dwo->ws->hdpi, dwo->ws->vdpi,
                    center_x, center_y, &cx, &cy);
      
      calc_length(radius_dx, radius_dx_u, dwo->ws->hdpi, w, &rx);
      calc_length(radius_dy, radius_dy_u, dwo->ws->vdpi, h, &ry);

      if (rx != ry)
        cx *= (ry / rx);

      pattern = cairo_pattern_create_radial (cx, cy, 0.0, cx, cy, ry);

      if (rx != ry) {
        cairo_matrix_t matrix;
        cairo_matrix_init_scale (&matrix, ry / rx, 1.0);
        cairo_pattern_set_matrix (pattern, &matrix);
      }
    } break;
    case GroupPatternType: {
      if (group) {
        cairo_save(cr);
        CacheSurface *subsurface = NULL;
        
        group->parent_surface = crsurface;
        group->return_surface = &subsurface;
        cairo_save(cr);
        group->render(dwo, cr, NULL, w, h);
        cairo_restore(cr);
        group->return_surface = NULL;
        group->parent_surface = NULL;

        cairo_identity_matrix(cr);
        cairo_move_to(cr, 0.0, 0.0);
        pattern = cairo_pattern_create_for_surface(subsurface->crsurface);
        subsurface->unref();
        cairo_restore(cr);
      } else {
        cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
        return;
      }
    } break;
  }

  int stops = 0;
  list<WaColorStop *>::iterator it = color_stops.begin();
  for (; it != color_stops.end(); ++it) {
    cairo_pattern_add_color_stop_rgba (pattern, (*it)->offset,
                                  (*it)->color->red,
                                  (*it)->color->green,
                                  (*it)->color->blue,
                                  (*it)->color->alpha);
    stops++;
  }

  if (stops == 0) {
    cairo_pattern_add_color_stop_rgba (pattern, 0.0, 0.0, 0.0, 0.0, 1.0);
    cairo_pattern_add_color_stop_rgba (pattern, 1.0, 1.0, 1.0, 1.0, 1.0);
  }
      
  cairo_pattern_set_filter (pattern, filter);
  cairo_pattern_set_extend (pattern, extend);

  cairo_set_source (cr, pattern);

  cairo_pattern_destroy (pattern);
}

static const struct LineCapMap {
    char *name;
    cairo_line_cap_t line_cap;
} line_cap_map[] = {
    { "butt", CAIRO_LINE_CAP_BUTT },
    { "round", CAIRO_LINE_CAP_ROUND },
    { "square", CAIRO_LINE_CAP_SQUARE }
};

static const struct LineJoinMap {
    char *name;
    cairo_line_join_t line_join;
} line_join_map[] = {
    { "miter", CAIRO_LINE_JOIN_MITER },
    { "round", CAIRO_LINE_JOIN_ROUND },
    { "bevel", CAIRO_LINE_JOIN_BEVEL }
};

static const struct FillRuleMap {
    char *name;
    cairo_fill_rule_t fill_rule;
} fill_rule_map[] = {
    { "winding", CAIRO_FILL_RULE_WINDING },
    { "evenodd", CAIRO_FILL_RULE_EVEN_ODD }
};

static const struct DrawingOrderMap {
    char *name;
    DrawingOrder order;
} drawing_order_map[] = {
    { "fillstroke", FillStrokeDrawingOrder },
    { "strokefill", StrokeFillDrawingOrder }
};

RenderOpDraw::RenderOpDraw(RenderOpType _type) : RenderOp(_type) {
    linewidth = RENDER_LINE_WIDTH_DEFAULT;
    linewidth_u = PXLenghtUnitType;
    linejoin = RENDER_LINE_JOIN_DEFAULT;
    linecap = RENDER_LINE_CAP_DEFAULT;
    fillrule = RENDER_FILL_RULE_DEFAULT;
    tolerance = RENDER_TOLERANCE_DEFAULT;
    miterlimit = RENDER_MITER_LIMIT_DEFAULT;
    stroke_pattern = fill_pattern = NULL;
    dashes = NULL;
    dashes_u = NULL;
    dash_offset = 0.0;
    dash_offset_u = PXLenghtUnitType;
    ndash = 0;
    order = RENDER_DRAWING_ORDER_DEFAULT;
    fill = stroke = false;
}

RenderOpDraw::~RenderOpDraw(void) {
    if (fill_pattern) fill_pattern->unref();
    if (stroke_pattern) stroke_pattern->unref();
    if (dashes) delete [] dashes;
    if (dashes_u) delete [] dashes_u;
}

void RenderOpDraw::inheritAttributes(RenderOpDraw *inherit) {
    linewidth = inherit->linewidth;
    linewidth_u = inherit->linewidth_u;
    linejoin = inherit->linejoin;
    linecap = inherit->linecap;
    fillrule = inherit->fillrule;
    tolerance = inherit->tolerance;
    miterlimit = inherit->miterlimit;
    stroke_color = inherit->stroke_color;
    fill_color = inherit->fill_color;
    fill = inherit->fill;
    stroke = inherit->stroke;

    if (fill_pattern) fill_pattern->unref();
    fill_pattern = NULL;
    if (inherit->fill_pattern)
        fill_pattern = inherit->fill_pattern->ref();

    if (stroke_pattern) stroke_pattern->unref();
    stroke_pattern = NULL;
    if (inherit->stroke_pattern)
        stroke_pattern = inherit->stroke_pattern->ref();

    if (dashes) delete [] dashes;
    if (dashes_u) delete [] dashes_u;

    dashes = NULL;
    dashes_u = NULL;
    ndash = inherit->ndash;
    
    if (ndash) {
        dashes = new double[ndash];
        dashes_u = new LenghtUnitType[ndash];
        
        for (int i = 0; i < ndash; i++) {
            dashes[i] = inherit->dashes[i];
            dashes_u[i] = inherit->dashes_u[i];
        }
    }
    dash_offset = inherit->dash_offset;
    dash_offset_u = inherit->dash_offset_u;

    ((RenderOp *) this)->inheritAttributes((RenderOp *) inherit);
}

void RenderOpDraw::applyAttributes(Parser *parser, Tst<char *> *attr) {
    char *value;
    ((RenderOp *) this)->applyAttributes(parser, attr);
    linewidth = parser->attrGetLength(attr, "linewidth", linewidth,
                                      &linewidth_u);
    tolerance = parser->attrGetDouble(attr, "tolerance", tolerance);
    miterlimit = parser->attrGetDouble(attr, "miterlimit", miterlimit);
    value = parser->attrGetString(attr, "linecap", NULL);
    if (value) {
        Tst<cairo_line_cap_t>::iterator it = line_cap_tst->find(value);
        if (it != line_cap_tst->end()) linecap = *it;
    }
    value = parser->attrGetString(attr, "linejoin", NULL);
    if (value) {
        Tst<cairo_line_join_t>::iterator it = line_join_tst->find(value);
        if (it != line_join_tst->end()) linejoin = *it;
    }
    value = parser->attrGetString(attr, "fillrule", NULL);
    if (value) {
        Tst<cairo_fill_rule_t>::iterator it = fill_rule_tst->find(value);
        if (it != fill_rule_tst->end()) fillrule = *it;
    }
    value = parser->attrGetString(attr, "fill_pattern", NULL);
    if (value) {
        fill_pattern = parser->ws->getPatternNamed(value, false);
        if (! fill_pattern) {
            parser->warning("undefined pattern: %s\n", value);
        } else {
            fill = true;
            fill_pattern = (RenderPattern *) fill_pattern->ref();
        }
    }
    value = parser->attrGetString(attr, "stroke_color", NULL);
    if (value) {
        stroke = true;
        if (! stroke_color.parseColor(parser->ws, value))
            parser->warning("error parsing color: %s\n", value);
    }
    stroke_color.setOpacity(
      parser->attrGetDouble(attr, "stroke_opacity",
                            stroke_color.getOpacity()));

    value = parser->attrGetString(attr, "stroke_pattern", NULL);
    if (value) {
        stroke_pattern = parser->ws->getPatternNamed(value, false);
        if (! stroke_pattern) {
            parser->warning("undefined pattern: %s\n", value);
        } else {
            stroke = true;
            stroke_pattern = (RenderPattern *) stroke_pattern->ref();
        }
    }
    value = parser->attrGetString(attr, "fill_color", NULL);
    if (value) {
      fill = true;
      if (! fill_color.parseColor(parser->ws, value))
        parser->warning("error parsing color: %s\n", value);
    }
    
    fill_color.setOpacity(
      parser->attrGetDouble(attr, "fill_opacity",
                            fill_color.getOpacity()));

    value = parser->attrGetString(attr, "stroke_dasharray", NULL);
    if (value) {
        value = WA_STRDUP(value);
        ndash = 1;
        for (int i = 0; value[i] != '\0'; i++) {
            if (value[i] == ',') {
                ndash++;
                value[i] = '\0';
            }
        }

        dashes = new double[ndash];
        dashes_u = new LenghtUnitType[ndash];

        char *dash = value;
        for (int i = 0; i < ndash; i++) {
            dashes[i] = get_double_and_unit(dash, &dashes_u[i]);
            dash += (strlen(dash) + 1);
        }
        
        delete [] value;

        dash_offset = parser->attrGetLength(attr, "stroke_dashoffset",
                                            dash_offset, &dash_offset_u);
    }

    value = parser->attrGetString(attr, "drawing_order", NULL);
    if (value) {
        Tst<DrawingOrder>::iterator it = drawing_order_tst->find(value);
        if (it != drawing_order_tst->end()) order = *it;
    }
}

void RenderOpDraw::draw(DWindowObject *dwo, cairo_t *cr,
                        cairo_surface_t *crsurface,
                        unsigned int w, unsigned int h) {
    cairo_pattern_t *pattern;
    
    if (xrop_set) cairo_set_operator(cr, xrop);
    cairo_rotate(cr, rotation);
    cairo_set_tolerance(cr, tolerance);

    for (int i = 0; i < 2; i++) {
      
      if (order == FillStrokeDrawingOrder && i == 0 ||
          order == StrokeFillDrawingOrder && i == 1) {

        if (fill) {
          cairo_save(cr);
          cairo_set_fill_rule(cr, fillrule);
          fill_color.setcairo_color(cr);
          if (fill_pattern)
            fill_pattern->setcairo_pattern(dwo, cr, NULL, w, h);
          //cairo_set_alpha(cr, fill_color.alpha);
          cairo_set_source_rgba(cr,
                                fill_color.red,
                                fill_color.green,
                                fill_color.blue,
                                fill_color.alpha);
          cairo_fill(cr);
          cairo_restore(cr);
        }
      } else {
        if (stroke) {
          double lwidth;
          calc_length(linewidth, linewidth_u,
                      (dwo->ws->hdpi + dwo->ws->vdpi) / 2, w, &lwidth);
          cairo_set_line_width(cr, lwidth);
          cairo_set_line_join(cr, linejoin);
          cairo_set_line_cap(cr, linecap);
          cairo_set_miter_limit(cr, miterlimit);
          if (dashes) {
            double *pxdashes = new double[ndash];
            double pxdash_offset;
            
            calc_length(dash_offset, dash_offset_u,
                        (dwo->ws->hdpi + dwo->ws->vdpi) / 2, w,
                        &pxdash_offset);
            
            for (int i = 0; i < ndash; i++)
              calc_length(dashes[i], dashes_u[i],
                          (dwo->ws->hdpi + dwo->ws->vdpi) / 2, w,
                          &pxdashes[i]);
            cairo_set_dash(cr, pxdashes, ndash, pxdash_offset);
            delete [] pxdashes;
          }

          cairo_save(cr);
          stroke_color.setcairo_color(cr);
          if (stroke_pattern)
            stroke_pattern->setcairo_pattern(dwo, cr, NULL, w, h);
          cairo_stroke(cr);
          cairo_restore(cr);
        }
      }
    }
}

RenderOpPath::RenderOpPath(char *_name) : RenderOpDraw(RenderOpPathType) {
    if (_name) name = WA_STRDUP(_name);
    else name = NULL;
}

RenderOpPath::~RenderOpPath(void) {
    clear();
}

void RenderOpPath::clear(void) {
    while (! ops.empty()) {
        ops.back()->unref();
        ops.pop_back();
    }
}

void RenderOpPath::inheritContent(RenderOpPath *inherit_path) {
    list<PathOperator *>::iterator it = inherit_path->ops.begin();
    for (; it != inherit_path->ops.end(); it++)
        ops.push_back((*it)->ref());
}

void RenderOpPath::inheritAttributes(RenderOpPath *inherit) {
    ((RenderOpDraw *) this)->inheritAttributes((RenderOpDraw *) inherit);
}

void RenderOpPath::applyAttributes(Parser *parser, Tst<char *> *attr) {
    char *value = parser->attrGetString(attr, "inherit", NULL);
    if (value) {
        RenderOpPath *inherit_path = parser->ws->getPathNamed(value, false);
        if (inherit_path == this) {
            parser->warning("path %s cannot inherit itself\n", name);
        } else if (inherit_path) {
            inheritAttributes(inherit_path);
        } else {
            if (! parser->attrGetBool(attr, "ignore_missing", false))
                parser->warning("undefined path: %s\n", value);
        }
    }
    
    ((RenderOpDraw *) this)->applyAttributes(parser, attr);
}

void RenderOpPath::render(DWindowObject *dwo, cairo_t *cr,
                          cairo_surface_t *crsurface,
                          unsigned int w, unsigned int h) {
    double a[3], b[3], x, y;
    double width, height;
    double vd = dwo->ws->vdpi;
    double hd = dwo->ws->hdpi;

    if (ops.empty()) return;

    calcPositionAndSize(w, h, hd, vd, &x, &y, &width, &height);

    cairo_translate(cr, x, y);
    cairo_new_path(cr);
    list<PathOperator *>::iterator it = ops.begin();
    for (; it != ops.end(); it++) {
        switch ((*it)->type) {
            case PathOperatorMoveToType:
                (*it)->calcPosition(width, height, hd, vd, a, b);
                cairo_move_to(cr, *a, *b);
                break;
            case PathOperatorRelMoveToType:
                (*it)->calcSize(width, height, hd, vd, a, b);
                cairo_rel_move_to(cr, *a, *b);
                break;
            case PathOperatorLineToType:
                (*it)->calcPosition(width, height, hd, vd, a, b);
                cairo_line_to(cr, *a, *b);
                break;
            case PathOperatorRelLineToType:
                (*it)->calcSize(width, height, hd, vd, a, b);
                cairo_rel_line_to(cr, *a, *b);
                break;
            case PathOperatorCurveToType:
                (*it)->calcPosition(width, height, hd, vd, a, b);
                cairo_curve_to(cr, a[1], b[1], a[2], b[2], a[0], b[0]);
                break;
            case PathOperatorRelCurveToType:
                (*it)->calcSize(width, height, hd, vd, a, b);
                cairo_rel_curve_to(cr, a[1], b[1], a[2], b[2], a[0], b[0]);
                break;
            case PathOperatorArcToType:
                (*it)->calcPosition(width, height, hd, vd, a, b);
                /* NYI: not yet implemented in cairo
                   cairo_arc_to(cr, a[1], b[1], a[2], b[2], a[0]);
                */
                break;
            case PathOperatorRelArcToType:
                (*it)->calcSize(width, height, hd, vd, a, b);
                /* NYI: not yet implemented in cairo
                   cairo_rel_arc_to(cr, a[1], b[1], a[2], b[2], a[0]);
                */
                break;
            case PathOperatorCloseType:
                cairo_close_path(cr);
                break;
        }
    }

    cairo_move_to (cr, 0.0, 0.0);
    draw(dwo, cr, crsurface, WA_ROUND_U(width),
         WA_ROUND_U(height));
}

void RenderOpLine::applyAttributes(Parser *parser, Tst<char *> *attr) {
    ((RenderOpDraw *) this)->applyAttributes(parser, attr);
}

void RenderOpLine::render(DWindowObject *dwo, cairo_t *cr,
                          cairo_surface_t *crsurface,
                          unsigned int w, unsigned int h) {
    double x, y, width, height;

    calcPositionAndSize(w, h, dwo->ws->hdpi, dwo->ws->vdpi, &x, &y,
                        &width, &height);

    cairo_new_path(cr);
    cairo_move_to(cr, x, y);
    cairo_rel_line_to(cr, width, height);
    cairo_move_to (cr, 0.0, 0.0);
    draw(dwo, cr, crsurface, WA_ROUND_U(width), WA_ROUND_U(height));
}

void RenderOpRectangle::applyAttributes(Parser *parser,
                                        Tst<char *> *attr) {
    ((RenderOpDraw *) this)->applyAttributes(parser, attr);
    
    nw_rx = parser->attrGetLength(attr, "rx", RENDER_DEFAULT_RECT_RX,
                                  &nw_rx_u);
    ne_rx = se_rx = sw_rx = nw_rx;
    ne_rx_u = se_rx_u = sw_rx_u = nw_rx_u;
    nw_ry = parser->attrGetLength(attr, "ry", RENDER_DEFAULT_RECT_RY,
                                  &nw_ry_u);
    ne_ry = se_ry = sw_ry = nw_ry;
    ne_ry_u = se_ry_u = sw_ry_u = nw_ry_u;
    
    nw_rx = parser->attrGetLength(attr, "northwest_rx", nw_rx, &nw_rx_u);
    nw_ry = parser->attrGetLength(attr, "northwest_ry", nw_ry, &nw_ry_u);
    
    ne_rx = parser->attrGetLength(attr, "northeast_rx", ne_rx, &ne_rx_u);
    ne_ry = parser->attrGetLength(attr, "northeast_ry", ne_ry, &ne_ry_u);

    sw_rx = parser->attrGetLength(attr, "southwest_rx", sw_rx, &sw_rx_u);
    sw_ry = parser->attrGetLength(attr, "southwest_ry", sw_ry, &sw_ry_u);

    se_rx = parser->attrGetLength(attr, "southeast_rx", se_rx, &se_rx_u);
    se_ry = parser->attrGetLength(attr, "southeast_ry", se_ry, &se_ry_u);
}

#define M_KAPPA 0.5522847498

void RenderOpRectangle::render(DWindowObject *dwo, cairo_t *cr,
                               cairo_surface_t *crsurface,
                               unsigned int w, unsigned int h) {
    double x, y, width, height;
    double vd = dwo->ws->vdpi;
    double hd = dwo->ws->hdpi;

    calcPositionAndSize(w, h, dwo->ws->hdpi, dwo->ws->vdpi, &x, &y,
                        &width, &height);

    if (nw_rx && nw_ry || ne_rx && ne_ry || sw_rx && sw_ry || se_rx && se_ry) {
        double rx, ry, go_w, go_h;
        cairo_new_path(cr);
        cairo_move_to(cr, x, y);

        go_w = width;
            
        if (nw_rx && nw_ry) {
            calc_length(nw_rx, nw_rx_u, hd, width, &rx);
            calc_length(nw_ry, nw_ry_u, vd, height, &ry);

            cairo_rel_move_to(cr, 0.0, ry);
            cairo_rel_curve_to(cr, 0.0, -ry * M_KAPPA,
                               rx - rx * M_KAPPA, -ry,
                               rx, -ry);
            go_w -= rx;
        }

        go_h = height;
        
        if (ne_rx && ne_ry) {
            calc_length(ne_rx, ne_rx_u, hd, width, &rx);
            calc_length(ne_ry, ne_ry_u, vd, height, &ry);
            
            cairo_rel_line_to(cr, go_w - rx, 0.0);
            cairo_rel_curve_to(cr, rx * M_KAPPA, 0.0,
                               rx, ry - ry * M_KAPPA,
                               rx, ry);
            go_h -= ry;
        } else
            cairo_rel_line_to(cr, go_w, 0.0);

        go_w = width;

        if (se_rx && se_ry) {
            calc_length(se_rx, se_rx_u, hd, width, &rx);
            calc_length(se_ry, se_ry_u, vd, height, &ry);
            
            cairo_rel_line_to(cr, 0.0, go_h - ry);
            cairo_rel_curve_to(cr, 0.0, ry * M_KAPPA,
                               -rx + rx * M_KAPPA, ry,
                               -rx, ry);
            go_w -= rx;
        } else
            cairo_rel_line_to(cr, 0.0, go_h);

        if (sw_rx && sw_ry) {
            calc_length(sw_rx, sw_rx_u, hd, width, &rx);
            calc_length(sw_ry, sw_ry_u, vd, height, &ry);
            
            cairo_rel_line_to(cr, -(go_w - rx), 0.0);
            cairo_rel_curve_to(cr, -rx * M_KAPPA, 0.0,
                               -rx, -ry + ry * M_KAPPA,
                               -rx, -ry);
        } else
            cairo_rel_line_to(cr, -go_w, 0.0);

        cairo_close_path(cr);
        
    } else
        cairo_rectangle(cr, x, y, width, height);

    cairo_translate(cr, x, y);
    cairo_move_to (cr, 0.0, 0.0);
    draw(dwo, cr, crsurface, WA_ROUND_U(width),
         WA_ROUND_U(height));
}

static const struct WeightMap {
    char *name;
    cairo_font_weight_t weight;
} weight_map[] = {
    { "normal", CAIRO_FONT_WEIGHT_NORMAL },
    { "bold", CAIRO_FONT_WEIGHT_BOLD }
};

static const struct SlantMap {
    char *name;
    cairo_font_slant_t slant;
} slant_map[] = {
    { "normal", CAIRO_FONT_SLANT_NORMAL },
    { "italic", CAIRO_FONT_SLANT_ITALIC },
    { "oblique", CAIRO_FONT_SLANT_OBLIQUE }
};

RenderOpText::RenderOpText(char *_name) : RenderOpDraw(RenderOpTextType) {
    shadow_x_offset = shadow_y_offset = 0.0;
    size = RENDER_FONT_SIZE_DEFAULT;
    size_u = _wu = _hu = PXLenghtUnitType;
    shadow = dynamic_area_width = dynamic_area_height = false;
    _w = _h = 0.0;
    line_spacing = RENDER_LINE_SPACING_DEFAULT;
    line_spacing_u = PXLenghtUnitType;
    tab_spacing = RENDER_TAB_SPACE_DEFAULT;
    tab_spacing_u = PXLenghtUnitType;
    left_spacing = right_spacing = top_spacing = bottom_spacing = 0.0;
    left_spacing_u = right_spacing_u = top_spacing_u =
        bottom_spacing_u = PXLenghtUnitType;
    text_halign = LeftAlignType;
    utf8 = NULL;
    is_static = false;
    family = WA_STRDUP(RENDER_FONT_KEY_DEFAULT);
    font = NULL;
    slant = RENDER_FONT_SLANT_DEFAULT;
    weight = RENDER_FONT_WEIGHT_DEFAULT;
    bg_group = NULL;
    if (_name) name = WA_STRDUP(_name);
    else name = NULL;
}

RenderOpText::~RenderOpText(void) {
    delete [] family;
    if (name) delete [] name;
    if (bg_group) bg_group->unref();
    clear();
    if (font) cairo_font_face_destroy(font);
}

void RenderOpText::clear(void) {
    if (utf8) delete [] utf8;
    utf8 = NULL;
}

void RenderOpText::inheritAttributes(RenderOpText *inherit) {
    shadow_x_offset = inherit->shadow_x_offset;
    shadow_y_offset = inherit->shadow_y_offset;
    size = inherit->size;
    size_u = inherit->size_u;
    _wu = inherit->_wu;
    _hu = inherit->_hu;
    shadow = inherit->shadow;
    dynamic_area_width = inherit->dynamic_area_width;
    dynamic_area_height = inherit->dynamic_area_height;
    _w = inherit->_w;
    _h = inherit->_h;
    line_spacing = inherit->line_spacing;
    line_spacing_u = inherit->line_spacing_u;
    tab_spacing = inherit->tab_spacing;
    tab_spacing_u = inherit->tab_spacing_u;
    left_spacing = inherit->left_spacing;
    right_spacing = inherit->right_spacing;
    top_spacing = inherit->top_spacing;
    bottom_spacing = inherit->bottom_spacing;
    left_spacing_u = inherit->left_spacing_u;
    right_spacing_u = inherit->right_spacing_u;
    top_spacing_u = inherit->top_spacing_u;
    bottom_spacing_u = inherit->bottom_spacing_u;
    text_halign = inherit->text_halign;
    family = WA_STRDUP((char *) inherit->family);
    slant = inherit->slant;
    weight = inherit->weight;
    if (bg_group) bg_group->unref();
    bg_group = NULL;
    if (inherit->bg_group)
        bg_group = (RenderGroup *) inherit->bg_group->ref();

    ((RenderOpDraw *) this)->inheritAttributes((RenderOpDraw *) inherit);
}

void RenderOpText::applyAttributes(Parser *parser, Tst<char *> *attr) {
    char *value = parser->attrGetString(attr, "inherit", NULL);
    if (value) {
        RenderOpText *inherit_text = parser->ws->getTextNamed(value, false);
        if (inherit_text == this) {
            parser->warning("text object %s cannot inherit itself\n", name);
        } else if (inherit_text) {
            inheritAttributes(inherit_text);
        } else {
            if (! parser->attrGetBool(attr, "ignore_missing", false))
                parser->warning("undefined text object: %s\n", value);
        }
    }
    
    attributes_get_alignment(parser, attr, "text_halign", NULL,
                             &text_halign, NULL);

    value = parser->attrGetString(attr, "bg_group", NULL);
    if (value) {
        bg_group = parser->ws->getRenderGroupNamed(value, false);
        if (! bg_group) {
            parser->warning("undefined group: %s\n", value);
        } else
            bg_group = (RenderGroup *) bg_group->ref();
    }

    is_static = parser->attrGetBool(attr, "static", is_static);

    dynamic_area_width = parser->attrGetBool(attr, "dynamic_area_width",
                                             dynamic_area_width);
    dynamic_area_height = parser->attrGetBool(attr, "dynamic_area_height",
                                              dynamic_area_height);
    
    size = parser->attrGetLength(attr, "size", size, &size_u);
    tab_spacing = parser->attrGetLength(attr, "tab_spacing", tab_spacing,
                                        &tab_spacing_u);
    line_spacing = parser->attrGetLength(attr, "line_spacing", line_spacing,
                                         &line_spacing_u);
    left_spacing = parser->attrGetLength(attr, "left_spacing", left_spacing,
                                         &left_spacing_u);
    right_spacing = parser->attrGetLength(attr, "right_spacing", right_spacing,
                                          &right_spacing_u);
    top_spacing = parser->attrGetLength(attr, "top_spacing", top_spacing,
                                        &top_spacing_u);
    bottom_spacing = parser->attrGetLength(attr, "bottom_spacing",
                                           bottom_spacing, &bottom_spacing_u);
    
    shadow = parser->attrGetBool(attr, "shadow", shadow);
    if (shadow) {
        shadow_x_offset = parser->attrGetDouble(attr, "shadow_hoffset",
                                                shadow_x_offset);
        shadow_y_offset = parser->attrGetDouble(attr, "shadow_voffset",
                                                shadow_y_offset);
    
        value = parser->attrGetString(attr, "shadow_color", NULL);
        if (value) {
            if (! shadow_color.parseColor(parser->ws, value))
                parser->warning("error parsing color: %s\n", value);
        }
        shadow_color.setOpacity(
            parser->attrGetDouble(attr, "shadow_opacity",
                                  shadow_color.getOpacity()));
    }

    value = parser->attrGetString(attr, "weight", NULL);
    if (value) {
        Tst<cairo_font_weight_t>::iterator it = weight_tst->find(value);
        if (it != weight_tst->end()) weight = *it;
    }

    value = parser->attrGetString(attr, "slant", NULL);
    if (value) {
        Tst<cairo_font_slant_t>::iterator it = slant_tst->find(value);
        if (it != slant_tst->end()) slant = *it;
    }
    
    value = parser->attrGetString(attr, "font", NULL);
    if (value) {
        delete [] family;
        family = WA_STRDUP(value);
    }
    ((RenderOpDraw *) this)->applyAttributes(parser, attr);
}

void RenderOpText::calcLinePosition(double width,
                                    unsigned int region_width,
                                    unsigned int region_height,
                                    double *return_x) {
    int textgravity = NorthWestGravity;
    switch (text_halign) {
        case RightAlignType:
            textgravity = NorthEastGravity;
            break;
        case HCenterAlignType:
            textgravity = CenterGravity;
            break;
        default:
            break;
    }
    double d;
    calc_position_and_size(text_halign, TopAlignType, textgravity,
                           PXLenghtUnitType, PXLenghtUnitType,
                           PXLenghtUnitType, PXLenghtUnitType,
                           region_width, region_height, 1.0, 1.0,
                           WA_ROUND_U(width), 1.0,
                           0.0, 0.0, return_x, &d, NULL, NULL);
    if (*return_x < 0.0) *return_x = 0.0;
}

void RenderOpText::render(DWindowObject *dwo, cairo_t *cr,
                          cairo_surface_t *crsurface,
                          unsigned int w, unsigned int h) {
    char *text;
    double x, y, width = 0.0, height = 0.0, font_size, tab_space, line_space,
        left_space, right_space, top_space, bottom_space, stroke_size;
    LenghtUnitType width_u = PXLenghtUnitType, height_u = PXLenghtUnitType;
    list<RenderLineInfo *> lines;
    CacheSurface *subsurface = NULL;
    
    if (! utf8) return;

    calc_length(size, size_u, dwo->ws->hdpi, w, &font_size);
    calc_length(tab_spacing, tab_spacing_u, dwo->ws->hdpi, w, &tab_space);
    calc_length(line_spacing, line_spacing_u, dwo->ws->vdpi, h, &line_space);
    calc_length(left_spacing, left_spacing_u, dwo->ws->hdpi, w, &left_space);
    calc_length(right_spacing, right_spacing_u, dwo->ws->hdpi, w,
                &right_space);
    calc_length(top_spacing, top_spacing_u, dwo->ws->vdpi, h, &top_space);
    calc_length(bottom_spacing, bottom_spacing_u, dwo->ws->vdpi, h,
                &bottom_space);

    cairo_identity_matrix(cr);

    if (!font) {
        cairo_select_font_face (cr, family, slant, weight);
        /* font = cairo_current_font(cr);
        if (!font)
          return;
        
        cairo_font_reference (font);
        */
    } else
        cairo_set_font_face (cr, font);
    
    cairo_set_font_size (cr, font_size);    
    
    if (is_static)
        text = utf8;
    else {
        map<RenderOpText *, char *>::iterator it =
            dwo->sb->text_table->find(this);
        if (it != dwo->sb->text_table->end())
            text = (*it).second;
        else
            text = utf8;
    }

    double space_width = -1.0;
    double line_height = 0.0, y_pos = 0.0, y_off;
    char *textptr = text;
    unsigned int end_offset = 0;
    unsigned int linenr = 0;
    cairo_surface_t *text_region = NULL;
    bool end_of_lines = false;
    do {        
        RenderLineInfo *linfo = new RenderLineInfo;
        linfo->width = 0.0;
        linenr++;
        
        do {
            cairo_text_extents_t extents;
            char *str;
            double str_width, str_height, str_y_pos;
            RenderStringInfo *sinfo = new RenderStringInfo;
                
            textptr += end_offset;
            for (end_offset = 0; textptr[end_offset] != '\n' &&
                     textptr[end_offset] != '\t' &&
                     textptr[end_offset] != '\0'; end_offset++);
            str = new char[end_offset + 1];
            strncpy(str, textptr, end_offset);
            str[end_offset] = '\0';
            sinfo->text = str;
            
            cairo_text_extents(cr, sinfo->text, &extents);
            str_y_pos = extents.height - extents.y_bearing;
            str_width = extents.width;
            str_height = extents.height;
            
            if (str_height > line_height) line_height = str_height;
            if (str_y_pos > y_pos) y_pos = str_y_pos;
            sinfo->x = linfo->width;
            linfo->width += str_width;
            linfo->strings.push_back(sinfo);
            if (textptr[end_offset] == '\t') {
                double tab, with_space;
                if (space_width < 0.0) {
                    cairo_text_extents(cr, "1 1", &extents);
                    with_space = extents.width;
                    cairo_text_extents(cr, "11", &extents);
                    space_width = with_space - extents.width;
                }
                tab = ceil((linfo->width + space_width) / tab_space);
                if (tab == ((linfo->width + space_width) / tab_space))
                    tab += 1.0;
                linfo->width = tab * tab_space;
            }
            if (textptr[end_offset] == '\0') end_of_lines = true;
        } while (textptr[end_offset++] == '\t');

        lines.push_back(linfo);
    } while (! end_of_lines);

    list<RenderLineInfo *>::iterator it = lines.begin();
    for (; it != lines.end(); it++)
        if ((*it)->width > width) width = (*it)->width;

    height = y_pos + y_pos + top_space + bottom_space + 1.0;
    height += (linenr - 1) * line_height + (linenr - 1) * line_space;
    width += (left_space + right_space + 2.0);
    if (shadow) {
        width += shadow_x_offset;
        height += shadow_y_offset;
    }

    if (_w) {
        if (! dynamic_area_width) {
            width = _w;
            width_u = _wu;
        } else {
            double max_w;
            calc_length(_w, _wu, dwo->ws->hdpi, w, &max_w);
            if (width > max_w) {
                width = _w;
                width_u = _wu;
            }
        }
    }
    if (_h)  {
        if (! dynamic_area_height) {
            height = _h;
            height_u = _hu;
        } else {
            double max_h;
            calc_length(_h, _hu, dwo->ws->vdpi, h, &max_h);
            if (height > max_h) {
                height = _h;
                height_u = _hu;
            }
        }
    }

    calc_position_and_size(halign, valign, gravity,
                           _xu, _yu, width_u, height_u, w, h,
                           dwo->ws->hdpi, dwo->ws->vdpi,
                           width, height, _x, _y, &x, &y,
                           &width, &height);
    if (x2_set || y2_set) {
        double tmp_x2, tmp_y2;
        calc_position(gravity2, _x2u, _y2u, w, h, dwo->ws->hdpi, dwo->ws->vdpi,
                      _x2, _y2, &tmp_x2, &tmp_y2);
        if (x2_set) width = tmp_x2 - x;
        if (y2_set) height = tmp_y2 - y;
    }

    if (width <= 0 || height <= 0) goto bail;

    cairo_save(cr);

    text_region = cairo_surface_create_similar(
        cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA,
        (unsigned int) ceil(width), (unsigned int) ceil(height));

    cairo_destroy(cr);
    cr = cairo_create(crsurface);
    
    if (bg_group) {
        bg_group->parent_surface = cairo_get_target(cr);
        bg_group->return_surface = &subsurface;
        cairo_save(cr);
        bg_group->render(dwo, cr, NULL, (unsigned int) ceil(width),
                         (unsigned int) ceil(height));
        cairo_restore(cr);
        bg_group->return_surface = NULL;
        bg_group->parent_surface = NULL;

        if (subsurface) {
            cairo_pattern_t *pattern;
            
            cairo_save(cr);
            pattern = cairo_pattern_create_for_surface(subsurface->crsurface);
            cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
            cairo_set_source(cr, pattern);
            cairo_pattern_destroy(pattern);
            cairo_rectangle(cr, 0.0, 0.0, ceil(width), ceil(height));
            cairo_fill(cr);
            cairo_restore(cr);
            subsurface->unref();
        }
    }
    
    y_off = -line_spacing;
    y_off += top_space;

    it = lines.begin();
    for (; it != lines.end(); it++) {
        double pos_x;

        y_off += line_spacing;
        calcLinePosition((*it)->width, (unsigned int) ceil(width),
                         (unsigned int) ceil(height), &pos_x);

        list<RenderStringInfo *>::iterator sit = (*it)->strings.begin();
        for (; sit != (*it)->strings.end(); sit++) {
            if (stroke || fill_pattern) {
              cairo_move_to(cr, (int) (left_space + (*sit)->x + pos_x),
                            (int) (y_off + y_pos));
              cairo_text_path(cr, (*sit)->text);
              cairo_move_to(cr, 0.0, 0.0);
              draw(dwo, cr, crsurface, WA_ROUND_U(width),
                   WA_ROUND_U(height));
            } else {
              if (shadow) {
                shadow_color.setcairo_color(cr);
                cairo_move_to(cr, (int) (left_space + (*sit)->x + pos_x +
                                         shadow_x_offset),
                              (int) (y_off + y_pos + shadow_y_offset));
                cairo_show_text(cr, (*sit)->text);
              }
              fill_color.setcairo_color(cr);
              cairo_move_to(cr, (int) (left_space + (*sit)->x + pos_x),
                            (int) (y_off + y_pos));
              cairo_show_text(cr, (*sit)->text);
            }
        }
            
        y_off += line_height;
    }

    cairo_restore(cr);

    if (x || y) cairo_translate(cr, x, y);
    if (xrop_set) cairo_set_operator(cr, xrop);
    if (rotation) cairo_rotate(cr, rotation);
    cairo_set_source_surface(cr, text_region, 0., 0.);
    cairo_rectangle(cr, 0., 0., ceil(width), ceil(height));
    cairo_clip(cr);
    cairo_paint(cr);
    
    cairo_surface_destroy(text_region);

 bail:
    while (! lines.empty()) {
        while (! lines.back()->strings.empty()) {
            delete [] lines.back()->strings.back()->text;
            delete lines.back()->strings.back();
            lines.back()->strings.pop_back();
        }
        delete lines.back();
        lines.pop_back();
    }
}

RenderOpFill::RenderOpFill(RenderOpType _type) : RenderOp(_type) {
    fillrule = RENDER_FILL_RULE_DEFAULT;
}

void RenderOpFill::applyAttributes(Parser *parser, Tst<char *> *attr) {
    ((RenderOp *) this)->applyAttributes(parser, attr);
}

void RenderOpSolid::applyAttributes(Parser *parser, Tst<char *> *attr) {
    ((RenderOpFill *) this)->applyAttributes(parser, attr);
    
    char *value = parser->attrGetString(attr, "color", NULL);
    if (value) {
        if (! color.parseColor(parser->ws, value))
            parser->warning("error parsing color: %s\n", value);
    }
    color.setOpacity(parser->attrGetDouble(attr, "opacity",
                                           color.getOpacity()));
}

void RenderOpSolid::render(DWindowObject *dwo, cairo_t *cr, cairo_surface_t *,
                           unsigned int w, unsigned int h) {
    double x, y, width, height;
    
    calcPositionAndSize(w, h, dwo->ws->hdpi, dwo->ws->vdpi, &x, &y,
                        &width, &height);
    
    if (color.alpha == 1.0) cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
    if (xrop_set) cairo_set_operator(cr, xrop);
    color.setcairo_color(cr);
    cairo_rectangle(cr, x, y, width, height);
    cairo_rotate(cr, rotation);
    cairo_set_fill_rule(cr, fillrule);
    cairo_fill(cr);
}

RenderOpImage::RenderOpImage(void) : RenderOpFill(RenderOpImageType) {
    scale = RENDER_IMAGE_SCALE_DEFAULT;
    image = NULL;
    filter = RENDER_FILTER_DEFAULT;
}

RenderOpImage::~RenderOpImage(void) {
    if (image) image->unref();
}

static const struct ImageScaleTypeMap {
    char *name;
    ImageScaleType type;
} image_scale_type_map[] = {
    { "scaled", ImageNormalScaleType },
    { "tiled", ImageTileScaleType }
};

bool RenderOpImage::applyAttributes(Parser *parser, Tst<char *> *attr) {
    int image_width, image_height;
    unsigned char *rgba = NULL;

    ((RenderOpFill *) this)->applyAttributes(parser, attr);

    char *value = parser->attrGetString(attr, "src", NULL);
    if (value) {
        char *filename = smartfile(value, parser->filename, false);
        if (filename) {
            rgba = read_image_to_rgba(filename, &image_width, &image_height);
            if (rgba)
                image = parser->ws->rgbaToWaSurface(rgba, image_width,
                                                    image_height);
            delete [] filename;
        } else {
            parser->warning("unable to open image file: %s\n", value);
            return false;
        }
            
        if (! image) {
            parser->warning("failed loading image: %s\n", value);
            return false;
        }
    } else {
        parser->warning("required attribute=src is missing\n");
        return false;
    }

    value = parser->attrGetString(attr, "scaletype", NULL);
    if (value) {
        Tst<ImageScaleType>::iterator it = image_scale_type_tst->find(value);
        if (it != image_scale_type_tst->end()) scale = *it;
    }

    value = parser->attrGetString(attr, "filter", NULL);
    if (value) {
        Tst<cairo_filter_t>::iterator it = filter_tst->find(value);
        if (it != filter_tst->end()) filter = *it;
    }

    return true;
}

void RenderOpImage::render(DWindowObject *dwo, cairo_t *cr, cairo_surface_t *,
                           unsigned int w, unsigned int h) {
    double x, y, width, height;
    WaSurface *img;
    cairo_matrix_t *matrix;
    cairo_pattern_t *pattern;

    if (image) img = image->ref();
    else return;
    
    calcPositionAndSize(w, h, dwo->ws->hdpi, dwo->ws->vdpi,
                        &x, &y, &width, &height);
    
    switch (scale) {
        case ImageNormalScaleType:
            cairo_identity_matrix(cr);
            
            if (width != img->width || height != img->height) {
                if (xrop_set) cairo_set_operator(cr, xrop);
                cairo_operator_t op = cairo_get_operator(cr);
            
                cairo_surface_t *target = cairo_get_target(cr);
                cairo_surface_t *scaled =
                    cairo_surface_create_similar(target, CAIRO_CONTENT_COLOR_ALPHA,
                                                 (int) width + 2,
                                                 (int) height + 2);
                cairo_destroy(cr);
                cr = cairo_create(scaled);
                cairo_scale(cr, (double) width / (img->width -
                                                  ((img->width > 1)? 1: 0)),
                            (double) height / (img->height -
                                               ((img->height > 1)? 1: 0)));
                cairo_pattern_t *img_pattern =
                    cairo_pattern_create_for_surface(img->crsurface);
                cairo_pattern_set_filter(img_pattern, filter);
                cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
                cairo_set_source(cr, img_pattern);
                cairo_rectangle(cr, 0., 0., img->width + 1, img->height + 1);
                cairo_fill(cr);
                cairo_identity_matrix(cr);
                cairo_pattern_destroy(img_pattern);

                cairo_destroy(cr);
                cr = cairo_create(target);
                cairo_translate(cr, x, y);
                cairo_set_operator(cr, op);
                cairo_set_source_surface(cr, scaled, 0., 0.);
                cairo_rectangle(cr, 0., 0., width + 2., height + 2.);
                cairo_fill(cr);                
                cairo_surface_destroy(scaled);
            } else {
                if (xrop_set) cairo_set_operator(cr, xrop);
                cairo_translate(cr, x, y);
                cairo_rotate(cr, rotation);
                cairo_set_source_surface(cr, img->crsurface, 0., 0.);
                cairo_rectangle(cr, 0., 0., img->width + 1., img->height + 1.);
                cairo_fill(cr);
            }
            break;
        case ImageTileScaleType: {
            cairo_identity_matrix(cr);
            cairo_translate(cr, x, y);
            cairo_rectangle(cr, 0, 0, width, height);
            cairo_move_to(cr, 0.0, 0.0);
            cairo_identity_matrix(cr);
            pattern = cairo_pattern_create_for_surface(img->crsurface);
            cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
            cairo_set_source(cr, pattern);
            cairo_pattern_destroy(pattern);
            if (xrop_set) cairo_set_operator(cr, xrop);
            cairo_fill(cr);
            //cairo_surface_set_repeat(img->crsurface, false);
        } break;
    }
    
    img->unref();
}

#ifdef    SVG
RenderOpSvg::RenderOpSvg(void) : RenderOpFill(RenderOpSvgType) {
    cairo_svg = NULL;
}

RenderOpSvg::~RenderOpSvg(void) {
    if (cairo_svg) svg_cairo_destroy(cairo_svg);
}

bool RenderOpSvg::applyAttributes(Parser *parser,
                                  Tst<char *> *attr) {
    ((RenderOpFill *) this)->applyAttributes(parser, attr);

    char *value = parser->attrGetString(attr, "file", NULL);
    if (value) {
        char *filename = smartfile(value, parser->filename, false);
        if (filename) {
            FILE *file = fopen(filename, "r");
            if (file) {
                svg_cairo_status_t status;
                status = svg_cairo_create(&cairo_svg);
                if (status == SVG_CAIRO_STATUS_SUCCESS) {
                    status = svg_cairo_parse_file(cairo_svg, file);
                    if (status == SVG_CAIRO_STATUS_SUCCESS) {
                        return true;
                    } else {
                        parser->warning("parsing of svg file %s failed\n",
                                        value);
                    }
                } else {
                    parser->warning("creation of svg context failed\n");
                }
                fclose(file);
            }
            delete [] filename;
        } else
            parser->warning("unable to open svg file: %s\n", value);
    } else
        parser->warning("missing required attribute=file'\n");

    return false;
}

void RenderOpSvg::render(DWindowObject *dwo, cairo_t *cr,
                         cairo_surface_t *,
                         unsigned int w, unsigned int h) {
    double x, y, width, height;
    unsigned int svg_w, svg_h;
    
    calcPositionAndSize(w, h, dwo->ws->hdpi, dwo->ws->vdpi,
                        &x, &y, &width, &height);
    
    cairo_save(cr);
    if (xrop_set) cairo_set_operator(cr, xrop);
    if (rotation) cairo_rotate(cr, rotation);
    cairo_translate(cr, x, y);

    svg_cairo_get_size(cairo_svg, &svg_w, &svg_h);
    cairo_scale(cr, width / (double) svg_w, height / (double) svg_h);

    svg_cairo_set_viewport_dimension(cairo_svg, (unsigned int) width,
                                     (unsigned int) height);

    if (svg_cairo_render(cairo_svg, cr) != SVG_CAIRO_STATUS_SUCCESS) {
        dwo->ws->showWarningMessage(__FUNCTION__, "svg rendering failed");
    }
    
    cairo_restore(cr);
}
#endif // SVG

void calc_length(double value, LenghtUnitType unit, double dpi,
                 double parent_length, double *return_value) {
    switch (unit) {
        case PXLenghtUnitType:
            *return_value = value;
            break;
        case CMLenghtUnitType:
            *return_value = (value / 2.54) * dpi;
            break;
        case MMLenghtUnitType:
            *return_value = (value / 25.4) * dpi;
            break;
        case INLenghtUnitType:
            *return_value = value * dpi;
            break;
        case PTLenghtUnitType:
            *return_value = (value / 72.0) * dpi;
            break;
        case PCLenghtUnitType:
            *return_value = (value / 6.0) * dpi;
            break;
        case PCTLenghtUnitType:
            *return_value = (value * parent_length) / 100.0;
            break;
    }
}

void calc_position(int gravity, LenghtUnitType _xu, LenghtUnitType _yu,
                   double parent_w, double parent_h, double hdpi, double vdpi,
                   double _x, double _y, double *return_x, double *return_y) {
    double x, y;

    calc_length(_x, _xu, hdpi, parent_w, &x);
    calc_length(_y, _yu, vdpi, parent_h, &y);
    
    switch (gravity) {
        case NorthEastGravity:
            x = parent_w - x;
            break;
        case NorthGravity:
            x = parent_w / 2 + x;
            break;
        case SouthEastGravity:
            x = parent_w - x;
            y = parent_h - y;
            break;
        case SouthWestGravity:
            y = parent_h - y;
            break;
        case SouthGravity:
            x = parent_w / 2 + x;
            y = parent_h - y;
            break;
        case EastGravity:
            x = parent_w - x;
            y = parent_h / 2 + y;
            break;
        case WestGravity:
            y = parent_h / 2 + y;
            break;
        default:
            break;
    }

    *return_x = x;
    *return_y = y;
}

void calc_position_and_size(HorizontalAlignment halign,
                            VerticalAlignment valign, int gravity,
                            LenghtUnitType _xu, LenghtUnitType _yu,
                            LenghtUnitType _wu, LenghtUnitType _hu,
                            double parent_w, double parent_h,
                            double hdpi, double vdpi,
                            double _w, double _h, double _x, double _y,
                            double *return_x, double *return_y,
                            double *return_w, double *return_h) {
    double x, y, w, h;

    calc_length(_w, _wu, hdpi, parent_w, &w);
    calc_length(_h, _hu, vdpi, parent_h, &h);
    calc_length(_x, _xu, hdpi, parent_w, &x);
    calc_length(_y, _yu, vdpi, parent_h, &y);
    
    switch (gravity) {
        case NorthEastGravity:
            x = parent_w - x;
            break;
        case NorthGravity:
            x = parent_w / 2 + x;
            break;
        case SouthEastGravity:
            x = parent_w - x;
            y = parent_h - y;
            break;
        case SouthWestGravity:
            y = parent_h - y;
            break;
        case SouthGravity:
            x = parent_w / 2 + x;
            y = parent_h - y;
            break;
        case EastGravity:
            x = parent_w - x;
            y = parent_h / 2 + y;
            break;
        case WestGravity:
            y = parent_h / 2 + y;
            break;
        case CenterGravity:
            x = parent_w / 2 + x;
            y = parent_h / 2 + y;
            break;
        default:
            break;
    }
    
    switch (halign) {
        case RightAlignType:
            x -= w;
            break;
        case HCenterAlignType:
            x -= w / 2;
            break;
        default:
            break;
    }

    switch (valign) {
        case BottomAlignType:
            y -= h;
            break;
        case VCenterAlignType:
            y -= h / 2;
            break;
        default:
            break;
    }
    
    *return_x = x;
    *return_y = y;

    if (return_w) *return_w = w;
    if (return_h) *return_h = h;
}

unsigned char *read_image_to_rgba(char *filename,
                                  int *image_width, int *image_height) {
    FILE *fp;
    bool status = false;
    unsigned char *rgba = NULL;

    UNUSED_VARIABLE(image_width);
    UNUSED_VARIABLE(image_height);
    
    if ((fp = fopen(filename, "rb")) == NULL) {
        
        WARNING << "`" << filename << "' "; perror(NULL);
        return NULL;
    }
    
#ifdef    PNG
    if (! status) {
        status = check_if_png(fp);
        rewind(fp);
        if (status)
            rgba = read_png_to_rgba(fp, image_width, image_height);
    }
#endif // PNG
    
    if (! status)
        WARNING << "`" << filename << "' unsupported image format" << endl;
    
    fclose(fp);
    
    return rgba;
}

static const struct GravityTypeMap {
    char *name;
    int gravity;
} gravity_type_map[] = {
    { "north", NorthGravity },
    { "northwest", NorthWestGravity },
    { "northeast", NorthEastGravity },
    { "south", SouthGravity },
    { "southwest", SouthWestGravity },
    { "southeast", SouthEastGravity },
    { "west", WestGravity },
    { "east", SouthGravity },
    { "center", CenterGravity }
};

static const struct HorizontalAlignmentMap {
    char *name;
    HorizontalAlignment align;
} horizontal_alignment_map[] = {
    { "left", LeftAlignType },
    { "right", RightAlignType },
    { "center", HCenterAlignType }
};

static const struct VerticalAlignmentMap {
    char *name;
    VerticalAlignment align;
} vertical_alignment_map[] = {
    { "top", TopAlignType },
    { "bottom", BottomAlignType },
    { "center", VCenterAlignType }
};

static const struct OperatorMap {
    char *name;
    cairo_operator_t crop;
} operator_map[] = {
    { "clear", CAIRO_OPERATOR_CLEAR },
    { "src", CAIRO_OPERATOR_SOURCE },
    { "dst", CAIRO_OPERATOR_DEST },
    { "dst_over", CAIRO_OPERATOR_DEST_OVER },
    { "dst_in", CAIRO_OPERATOR_DEST_IN },
    { "dst_out", CAIRO_OPERATOR_DEST_OUT },
    { "dst_atop", CAIRO_OPERATOR_DEST_ATOP },
    { "over", CAIRO_OPERATOR_OVER },
    { "in", CAIRO_OPERATOR_IN },
    { "out", CAIRO_OPERATOR_OUT },
    { "atop", CAIRO_OPERATOR_ATOP },
    { "xor", CAIRO_OPERATOR_XOR },
    { "add", CAIRO_OPERATOR_ADD },
    { "saturate", CAIRO_OPERATOR_SATURATE }
};

void attributes_get_position(Parser *parser, Tst<char *> *attr,
                             char *xname, char *yname, char *gname,
                             double *x, double *y,
                             LenghtUnitType *_xu, LenghtUnitType *_yu,
                             int *g) {
    double tmp = parser->attrGetLength(attr, xname, INT_MIN, _xu);
    if (tmp != INT_MIN) *x = tmp;
    tmp = parser->attrGetLength(attr, yname, INT_MIN, _yu);
    if (tmp != INT_MIN) *y = tmp;
    char *value = parser->attrGetString(attr, gname, NULL);
    if (value) {
        Tst<int>::iterator it = gravity_type_tst->find(value);
        if (it != gravity_type_tst->end()) *g = *it;
    }
}

void attributes_get_size(Parser *parser, Tst<char *> *attr,
                         char *wname, char *hname,
                         double *w, double *h,
                         LenghtUnitType *_wu, LenghtUnitType *_hu) {
    double tmp = parser->attrGetLength(attr, wname, INT_MIN, _wu);
    if (tmp != INT_MIN) *w = tmp;
    tmp = parser->attrGetLength(attr, hname, INT_MIN, _hu);
    if (tmp != INT_MIN) *h = tmp;
}

void attributes_get_alignment(Parser *parser, Tst<char *> *attr,
                              char *haname, char *vaname,
                              HorizontalAlignment *ha, VerticalAlignment *va) {
    
    char *value;

    if (ha) {
        value = parser->attrGetString(attr, haname, NULL);
        if (value) {
            Tst<HorizontalAlignment>::iterator it =
                horizontal_alignment_tst->find(value);
            if (it != horizontal_alignment_tst->end()) *ha = *it;
        }
    }

    if (va) {
        value = parser->attrGetString(attr, vaname, NULL);
        if (value) {
            Tst<VerticalAlignment>::iterator it =
                vertical_alignment_tst->find(value);
            if (it != vertical_alignment_tst->end()) *va = *it;
        }
    }
}

void attributes_get_area(Parser *parser, Tst<char *> *attr,
                         char *xname, char *yname, char *gname,
                         char *x2name, char *y2name, char *g2name,
                         char *wname, char *hname,
                         char *haname, char *vaname,
                         double *x, double *y,
                         LenghtUnitType *_xu, LenghtUnitType *_yu, int *g,
                         double *x2, double *y2,
                         LenghtUnitType *_x2u, LenghtUnitType *_y2u, int *g2,
                         bool *x2set, bool *y2set,
                         double *w, double *h,
                         LenghtUnitType *_wu, LenghtUnitType *_hu,
                         HorizontalAlignment *ha, VerticalAlignment *va) {
    double tmp_x2 = INT_MIN, tmp_y2 = INT_MIN;
    attributes_get_position(parser, attr, xname, yname, gname, x, y,
                            _xu, _yu, g);
    attributes_get_position(parser, attr, x2name, y2name, g2name, &tmp_x2,
                            &tmp_y2, _x2u, _y2u, g2);
    if (tmp_x2 != INT_MIN) {
        *x2 = tmp_x2;
        *x2set = true;
    }
    if (tmp_y2 != INT_MIN) {
        *y2 = tmp_y2;
        *y2set = true;
    }
    attributes_get_size(parser, attr, wname, hname, w, h, _wu, _hu);
    attributes_get_alignment(parser, attr, haname, vaname, ha, va);
}

void attributes_get_render(Parser *parser, Tst<char *> *attr,
                           cairo_operator_t *o, bool *opset, double *r) {
    char *value = parser->attrGetString(attr, "operator", NULL);
    if (value) {
        Tst<cairo_operator_t>::iterator it = operator_tst->find(value);
        if (it != operator_tst->end()) {
            *o = *it;
            *opset = true;
        }
    }
    double angle = parser->attrGetDouble(attr, "rotation", (double) -INT_MAX);
    if (angle != (double) -INT_MAX) *r = angle / (M_PI * 2.0);
}

void render_create_tsts(void) {
    int size, i;

    filter_tst = new Tst<cairo_filter_t>;
    size = sizeof(filter_map) / sizeof(FilterMap);
    for (i = 0; i < size; i++)
        filter_tst->insert(filter_map[i].name, filter_map[i].filter);

    extend_tst = new Tst<cairo_extend_t>;
    size = sizeof(extend_map) / sizeof(ExtendMap);
    for (i = 0; i < size; i++)
        extend_tst->insert(extend_map[i].name, extend_map[i].extend);

    pattern_type_tst = new Tst<PatternType>;
    size = sizeof(pattern_type_map) / sizeof(PatternTypeMap);
    for (i = 0; i < size; i++)
        pattern_type_tst->insert(pattern_type_map[i].name,
                                 pattern_type_map[i].pattern_type); 
    
    line_cap_tst = new Tst<cairo_line_cap_t>;
    size = sizeof(line_cap_map) / sizeof(LineCapMap);
    for (i = 0; i < size; i++)
        line_cap_tst->insert(line_cap_map[i].name, line_cap_map[i].line_cap);

    line_join_tst = new Tst<cairo_line_join_t>;
    size = sizeof(line_join_map) / sizeof(LineJoinMap);
    for (i = 0; i < size; i++)
        line_join_tst->insert(line_join_map[i].name,
                              line_join_map[i].line_join);

    fill_rule_tst = new Tst<cairo_fill_rule_t>;
    size = sizeof(fill_rule_map) / sizeof(FillRuleMap);
    for (i = 0; i < size; i++)
        fill_rule_tst->insert(fill_rule_map[i].name,
                              fill_rule_map[i].fill_rule);

    drawing_order_tst = new Tst<DrawingOrder>;
    size = sizeof(drawing_order_map) / sizeof(DrawingOrderMap);
    for (i = 0; i < size; i++)
        drawing_order_tst->insert(drawing_order_map[i].name,
                                  drawing_order_map[i].order);
   
    weight_tst = new Tst<cairo_font_weight_t>;
    size = sizeof(weight_map) / sizeof(WeightMap);
    for (i = 0; i < size; i++)
        weight_tst->insert(weight_map[i].name, weight_map[i].weight);

    slant_tst = new Tst<cairo_font_slant_t>;
    size = sizeof(slant_map) / sizeof(SlantMap);
    for (i = 0; i < size; i++)
        slant_tst->insert(slant_map[i].name, slant_map[i].slant);

    image_scale_type_tst = new Tst<ImageScaleType>;
    size = sizeof(image_scale_type_map) / sizeof(ImageScaleTypeMap);
    for (i = 0; i < size; i++)
        image_scale_type_tst->insert(image_scale_type_map[i].name,
                                     image_scale_type_map[i].type);

    gravity_type_tst = new Tst<int>;
    size = sizeof(gravity_type_map) / sizeof(GravityTypeMap);
    for (i = 0; i < size; i++)
        gravity_type_tst->insert(gravity_type_map[i].name,
                                 gravity_type_map[i].gravity);

    horizontal_alignment_tst = new Tst<HorizontalAlignment>;
    size = sizeof(horizontal_alignment_map) / sizeof(HorizontalAlignmentMap);
    for (i = 0; i < size; i++)
        horizontal_alignment_tst->insert(horizontal_alignment_map[i].name,
                                         horizontal_alignment_map[i].align);
    
    vertical_alignment_tst = new Tst<VerticalAlignment>;
    size = sizeof(vertical_alignment_map) / sizeof(VerticalAlignmentMap);
    for (i = 0; i < size; i++)
        vertical_alignment_tst->insert(vertical_alignment_map[i].name,
                                       vertical_alignment_map[i].align);

    operator_tst = new Tst<cairo_operator_t>;
    size = sizeof(operator_map) / sizeof(OperatorMap);
    for (i = 0; i < size; i++)
        operator_tst->insert(operator_map[i].name,
                             operator_map[i].crop);    
}


syntax highlighted by Code2HTML, v. 0.9.1