view nodejs/svg.js @ 990:8dd42310dd79 refine_backend_if

Change signature of callback for IO and timer manager
author Thinker K.F. Li <thinker@codemud.net>
date Mon, 22 Nov 2010 00:42:29 +0800
parents a9abcdac0ae5
children 78312b44f48c
line wrap: on
line source

// -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 4; -*-
// vim: sw=4:ts=8:sts=4
var libxml = require('libxmljs');
var sys=require('sys');
var mbfly = require("mbfly");
var ldr = mbfly.img_ldr_new(".");


var _std_colors = {
    "white": [1, 1, 1],
    "black": [0, 0, 0],
    "red": [1, 0, 0]
};

function parse_color(color) {
    var r, g, b;
    var c;
    
    if (color[0] == "#") {
	r = parseInt(color.substring(1, 3), 16) / 255;
	g = parseInt(color.substring(3, 5), 16) / 255;
	b = parseInt(color.substring(5, 7), 16) / 255;
    } else if(_std_colors[color]) {
	c = _std_colors[color];
	r = c[0];
	g = c[1];
	b = c[2];
    } else {
	r = g = b = 0;
    }
    
    return [r, g, b];
}

exports.loadSVG=loadSVG;

function loadSVG(mb_rt, root, filename) {
    this.pgstack=[];
    if (filename)
        this.load(mb_rt,root,filename);
}

loadSVG.prototype.load=function(mb_rt, root, filename) {
    var doc = libxml.parseXmlFile(filename);
    var _root = doc.root();
    var nodes = _root.childNodes();
    var coord = mb_rt.coord_new(root);
    var k;
    var accu=[1,0,0,0,1,0];
    this.mb_rt = mb_rt;
    this.stop_ref={};
    this.gradients={};
    this.radials = {};
    coord.center=new Object();
    coord.center.x = 10000;
    coord.center.y = 10000;
    if (this.pgstack.length > 0)
        this.pgstack[this.pgstack.length-1].hide();
    this.pgstack.push(coord);

    
    if(_root.attr("width")) {
	k = _root.attr("width").value();
	this.width = parseFloat(k);
    }
    if(_root.attr("height")) {
	k = _root.attr("height").value();
	this.height = parseFloat(k);
    }
    
    for(k in nodes) {
	var n = nodes[k].name();
        if (n == "defs") {
            this.parseDefs(coord,nodes[k]);
        } else if (n == "g") {
            this.parseGroup(accu,coord,'root_coord',nodes[k]);
        } 
    }
}

loadSVG.prototype.leaveSVG=function()
{
    var p = this.pgstack.pop();
    p.hide();
    if (this.pgstack.length > 0)
	this.pgstack[this.pgstack.length-1].show();
}

function make_mbnames(mb_rt, n, obj) {
    var mbname;
    var name;
    
    if(!mb_rt.mbnames)
	mb_rt.mbnames = {};
    
    mbname = n.attr("mbname");
    if(mbname) {
	name = mbname.value();
	mb_rt.mbnames[name] = obj;
	return;
    }
    mbname = n.attr("label");
    if(mbname&&(mbname.value()!="")) {
	name = mbname.value();
	mb_rt.mbnames[name] = obj;
    }
}

function getInteger(n,name)
{
    if (n == null) return 0;
    var a = n.attr(name);
    if (a==null) return 0;
    return parseInt(a.value());
}

function parsePointSize(s)
{
    var fs=0;
    var i;

    for(i=0;i<s.length;i++) {
        if (s[i]<'0' || s[i] > '9') break;
        fs = fs*10 + (s[i]-'0');
    }
    return fs;
	
}

function parse_style(node) {
    var style_attr;
    var style;
    var parts, part;
    var kv, key, value;
    var content = {};
    var i;
    
    style_attr = node.attr('style');
    if(!style_attr)
	return content;

    style = style_attr.value();
    parts = style.split(';');
    for(i = 0; i < parts.length; i++) {
	part = parts[i].trim();
	if(part) {
	    kv = part.split(':');
	    key = kv[0].trim();
	    value = kv[1].trim();
	    content[key] = value;
	}
    }

    return content;
}

function parseColor(c)
{
    if (c[0] == '#') {
        return parseInt(c.substring(1,3),16)<<16 | parseInt(c.substring(3,5),16)<<8 | parseInt(c.substring(5,7),16);
    }
}

function parseTextStyle(style,n)
{
    var attr;
    if (n) {
        attr = n.attr('style');
    } else {
        attr = null;
    }
    if (attr == null) {
        return;
    }
    var f = attr.value().split(';');

    for(i in f) {
        var kv = f[i].split(':');
        if (kv[0] == 'font-size') {
            style.fs = parsePointSize(kv[1]);
        } else if (kv[0] == "font-style") {
        } else if (kv[0] == "font-weight") {
        } else if (kv[0] == "fill") {
            style.color = parseColor(kv[1]);
        } else if (kv[0] == "fill-opacity") {
        } else if (kv[0] == "stroke-opacity") {
        } else if (kv[0] == "stroke") {
        } else if (kv[0] == "stroke-width") {
        } else if (kv[0] == "stroke-linecap") {
        } else if (kv[0] == "stroke-linejoin") {
        } else if (kv[0] == "stroke-lineopacity") {
        } else if (kv[0] == "font-family") {
            style.family = kv[1];
        } else if (kv[0] == "font-stretch") {
        } else if (kv[0] == "font-variant") {
        } else if (kv[0] == "text-anchor") {
        } else if (kv[0] == "text-align") {
        } else if (kv[0] == "writing-mode") {
        } else if (kv[0] == "line-height") {
        } else {
            sys.puts("Unknown style: "+kv[0]);
        }
    }
}
function tspan_set_text(text)
{
    this.text.set_text(text); 
}

function _parse_font_size(fn_sz_str) {
    var pos;

    pos = fn_sz_str.search("px");
    if(pos >= 0)
	fn_sz_str = fn_sz_str.substring(0, pos);
    pos = fn_sz_str.search("pt");
    if(pos >= 0)
	fn_sz_str = fn_sz_str.substring(0, pos);

    return parseFloat(fn_sz_str);
}

loadSVG.prototype.parseTSpan = function(coord, n, pstyle) {
    var x = getInteger(n,'x');
    var y = getInteger(n,'y');
    var tcoord = this.mb_rt.coord_new(coord);
    var style;
    var family = "Courier";
    var weight = 80;
    var slant = 0;
    var sz = 10;
    var face;
    var k;
    var obj = this.mb_rt.stext_new(n.text(),x,y);
    
    style = parse_style(n);
    for(k in pstyle) {
	if(k in style)
	    continue;
	style[k] = pstyle[k];
    }

    if("font-family" in style)
	family = style["font-family"];
    if("font-size" in style)
	sz = _parse_font_size(style["font-size"]);
    if("font-weight" in style) {
	if(style["font-weight"] == "bold")
	    weight = 200;
    }
    if("font-style" in style) {
	if(style["font-style"] == "oblique")
	    slant = 110;
    }

    face = this.mb_rt.font_face_query(family, slant, weight);
    obj.set_style([[n.text().length, face, sz]]);

    tcoord.add_shape(obj);
    tcoord.set_text = tspan_set_text;
    tcoord.text = obj;
    
    this._set_paint_style(style, obj);
    this._set_bbox(n, obj);
    
    make_mbnames(this.mb_rt, n, tcoord);
};

loadSVG.prototype._prepare_paint_color = function(color, alpha) {
    var paint;
    var c;
    
    if (color[0]=='#') {
	var r,g,b;
	r = parseInt(color.substring(1,3),16)/255;
	g = parseInt(color.substring(3,5),16)/255;
	b = parseInt(color.substring(5,7),16)/255;
	paint = this.mb_rt.paint_color_new(r, g, b, alpha);
    } else if(_std_colors[color]) {
	c = _std_colors[color];
	paint = this.mb_rt.paint_color_new(c[0], c[1], c[2], alpha);
    } else if (color.substring(0,3) == 'url') {
	var id = color.substring(5, color.length-1);
	if(id in this.gradients) {
	    var gr = this.gradients[id];
	    paint = this.mb_rt.paint_linear_new(gr[0],gr[1],gr[2],gr[3]);
	} else {
	    var radial = this.radials[id];
	    paint = this.mb_rt.paint_radial_new(radial[0],
						radial[1],
						radial[2]);
	}
	paint.set_stops(this.stop_ref[id]);
    } else {
	paint = this.mb_rt.paint_color_new(0,0,0,1);
    }
    return paint;
};
	
function guessPathBoundingBox(coord,d)
{
    return;
    var items = d.split(' ');
    var len = items.length;
    var pair;
    var i;
    var minx,miny;

    minx = 10000;
    miny = 10000;

    for(i=0;i<len;i++) {
	var type = items[i].toLowerCase();
	x = minx;y = miny;
	switch(type) {
	case 'm':
	case 'l':
	case 'a':
	case 'x':
	    pair = items[i+1].split(',');
	    if (pair.length==2) {
		x = parseFloat(pair[0]);
		y = parseFloat(pair[1]);
		i++;
	    } else {
		x = parseFloat(items[i+1]);
		y = parseFloat(items[i+2]);
		i+=2;
	    }
	    break;
	case 'q':
	    // Implement this latter
	    break;
	case 'c':
	    // Implement this latter
	    break;
	case 's':
	    // Implement this latter
	    break;
	case 'h':
	    x = parseFloat(items[i+1]);
	    break;
	case 'v':
	    y = parseFloat(items[i+1]);
	    break;
	default:
	    continue;
	}
	if (x < minx) minx = x;
	if (y < miny) miny = y;
    }
    if (coord.center.x >  minx)
	coord.center.x = minx;
    if (coord.center.y >  miny)
	coord.center.y = miny;
};

function _mul(m1, m2) {
    var res = new Array();

    res.push(m1[0] * m2[0] + m1[1] * m2[3]);
    res.push(m1[0] * m2[1] + m1[1] * m2[4]);
    res.push(m1[0] * m2[2] + m1[1] * m2[5] + m1[2]);
    res.push(m1[3] * m2[0] + m1[4] * m2[3]);
    res.push(m1[3] * m2[1] + m1[4] * m2[4]);
    res.push(m1[3] * m2[2] + m1[4] * m2[5] + m1[5]);

    return res;
}

function _pnt_transform(x, y, matrix) {
    var rx, ry;

    rx = x * matrix[0] + y * matrix[1] + matrix[2];
    ry = x * matrix[3] + y * matrix[4] + matrix[5];
    return new Array(rx, ry);
}

function _shift_transform(x, y, matrix) {
    var rx, ry;

    rx = x * matrix[0] + y * matrix[1];
    ry = x * matrix[3] + y * matrix[4];
    return new Array(rx, ry);
}

function _transform_bbox(bbox, matrix) {
    var min_x, min_y, max_x, max_y;
    var x, y;
    var pnt;
    var pnts = new Array();
    var i;

    pnt = _pnt_transform(bbox.x, bbox.y, matrix);
    pnts.push(pnt);
    pnt = _pnt_transform(bbox.x + bbox.width, bbox.y, matrix);
    pnts.push(pnt);
    pnt = _pnt_transform(bbox.x, bbox.y + bbox.height, matrix);
    pnts.push(pnt);
    pnt = _pnt_transform(bbox.x + bbox.width, bbox.y + bbox.height, matrix);
    pnts.push(pnt);

    min_x = max_x = pnts[0][0];
    min_y = max_y = pnts[0][1];
    for(i = 1; i < pnts.length; i++) {
	pnt = pnts[i];
	if(pnt[0] < min_x)
	    min_x = pnt[0];
	if(pnt[1] < min_y)
	    min_y = pnt[1];
	if(pnt[0] > max_x)
	    max_x = pnt[0];
	if(pnt[1] > max_y)
	    max_y = pnt[1];
    }

    bbox.x = min_x;
    bbox.y = min_y;
    bbox.width = max_x - min_x;
    bbox.height = max_y - min_y;
}

function _reverse(m1) {
    var rev = new Array(1, 0, 0, 0, 1, 0);
    var m = new Array(m1[0], m1[1], m1[2], m1[3], m1[4], m1[5]);

    rev[3] = -m[3] / m[0];
    m[3] = 0;
    m[4] += rev[3] * m[1];
    m[5] += rev[3] * m[2];
    
    rev[1] = -m[1] / m[4];
    rev[0] += rev[1] * rev[3];
    m[1] = 0;
    m[2] += rev[1] * m[5];
    
    rev[2] = -m[2];
    rev[5] = -m[5];
    
    rev[0] = rev[0] / m[0];
    rev[1] = rev[1] / m[0];
    rev[2] = rev[2] / m[0];
    
    rev[3] = rev[3] / m[4];
    rev[4] = rev[4] / m[4];
    rev[5] = rev[5] / m[4];

    return rev;
}

var _bbox_proto = {
    _get_ac_saved_rev: function() {
	var c = this.owner;
	var mtx;
	
	c = c.parent;
	
	mtx = c._mbapp_saved_rev_mtx;
	while(c.parent && typeof c.parent != "undefined") {
	    c = c.parent;
	    mtx = _mul(mtx, c._mbapp_saved_rev_mtx);
	}

	return mtx;
    },
    
    _get_ac_mtx: function() {
	var c = this.owner;
	var mtx;
	
	c = c.parent;

	mtx = [c[0], c[1], c[2], c[3], c[4], c[5]];
	while(c.parent) {
	    c = c.parent;
	    mtx = _mul(c, mtx);
	}

	return mtx;
    },

    _saved_to_current: function() {
	var r;
	
	r = _mul(this._get_ac_mtx(), this._get_ac_saved_rev());
	
	return r;
    },

    /*! \brief Update x, y, width, and height of the bbox.
     */
    update: function() {
	var mtx;

	this.x = this.orig.x;
	this.y = this.orig.y;
	this.width = this.orig.width;
	this.height = this.orig.height;
	
	mtx = this._saved_to_current();
	_transform_bbox(this, mtx);
    },    
};

var _center_proto = {
    _get_ac_saved_rev: function() {
	var c = this.owner;
	var mtx;
	
	c = c.parent;
	
	mtx = c._mbapp_saved_rev_mtx;
	while(c.parent && typeof c.parent != "undefined") {
	    c = c.parent;
	    mtx = _mul(mtx, c._mbapp_saved_rev_mtx);
	}

	return mtx;
    },
    
    _get_ac_mtx: function() {
	var c = this.owner;
	var mtx;
	
	c = c.parent;

	mtx = [c[0], c[1], c[2], c[3], c[4], c[5]];
	while(c.parent) {
	    c = c.parent;
	    mtx = _mul(c, mtx);
	}

	return mtx;
    },

    _get_ac_rev: function() {
	var c = this.owner;
	var mtx;
	
	if(c.type != "coord")
	    c = c.parent;	// is a shape!

	mtx = _reverse([c[0], c[1], c[2], c[3], c[4], c[5]]);
	while(c.parent) {
	    c = c.parent;
	    mtx = _mul(mtx, _reverse([c[0], c[1], c[2], c[3], c[4], c[5]]));
	}

	return mtx;
    },

    _saved_to_current: function() {
	var r;
	
	r = _mul(this._get_ac_mtx(), this._get_ac_saved_rev());
	
	return r;
    },

    /*! \brief Update x, y of center point of an object.
     */
    update: function() {
	var mtx;
	var xy;

	mtx = this._saved_to_current();
	xy = _pnt_transform(this.orig.x, this.orig.y, mtx);

	this._x = xy[0];
	this._y = xy[1];
    },

    /*! \brief Move owner object to make center at (x, y).
     */
    move: function(x, y) {
	var mtx;
	var xdiff = x - this._x;
	var ydiff = y - this._y;
	var shiftxy;
	var c;

	mtx = this._get_ac_rev();
	shiftxy = _shift_transform(xdiff, ydiff, mtx);

	c = this.owner;
	if(c.type != "coord")
	    c = c.parent;

	c[2] += shiftxy[0];
	c[5] += shiftxy[1];

	this._x = x;
	this._y = y;
    },

    /*! \brief Move owner object to make center at position specified by pnt.
     */
    move_pnt: function(pnt) {
	this.move(pnt.x, pnt.y);
    },

    /*! \brief Prevent user to modify value.
     */
    get x() { return this._x; },
    
    /*! \brief Prevent user to modify value.
     */
    get y() { return this._y; },

    /*! \brief Center position in the relative space defined by parent.
     */
    get rel() {
	var rev;
	var xy;
	
	rev = this._get_ac_rev();
	xy = _pnt_transform(this.orig.x, this.orig.y, rev);

	return {x: xy[0], y: xy[1]};
    },
};

loadSVG.prototype._set_bbox = function(node, tgt) {
    var a;
    var vstr;
    var bbox, center;
    var orig;
    
    a = node.attr("bbox-x");
    if(!a)
	return 0;
    
    /* bbox.orig is initial values of bbox.  The bbox is recomputed
     * according bbox.orig and current matrix.  See bbox.update().
     */
    tgt.bbox = bbox = new Object();
    bbox.orig = orig = new Object();
    bbox.owner = tgt;
    bbox.__proto__ = _bbox_proto;
    vstr = a.value();
    orig.x = parseFloat(vstr);

    a = node.attr("bbox-y");
    vstr = a.value();
    orig.y = this.height - parseFloat(vstr);

    a = node.attr("bbox-width");
    vstr = a.value();
    orig.width = parseFloat(vstr);

    a = node.attr("bbox-height");
    vstr = a.value();
    orig.height = parseFloat(vstr);
    orig.y -= orig.height;
    
    bbox.update();

    /* center.orig is initial values of center.  The center is
     * recomputed according center.orig and current matrix.  See
     * center.update().
     */
    tgt.center = center = new Object();
    center.orig = orig = new Object();
    
    orig.x = bbox.orig.width / 2 + bbox.orig.x;
    orig.y = bbox.orig.height / 2 + bbox.orig.y;
    a = node.attr("transform-center-x");
    if(a) {
	vstr = a.value();
	orig.x += parseFloat(vstr);
	a = node.attr("transform-center-y");
	vstr = a.value();
	orig.y -= parseFloat(vstr);
    }
    center.__proto__ = _center_proto;
    center.owner = tgt;
    center.update();
    
    return 1;
}

loadSVG.prototype._set_paint_style = function(style, tgt) {
    var paint;
    var fill_alpha = 1;
    var stroke_alpha = 1;
    var alpha = 1;
    var fill_color;
    var stroke_color;
    var stroke_width = 1;
    var i;
    
    if(style) {
	if('opacity' in style)
	    alpha = parseFloat(style['opacity']);
	if('fill' in style)
	    fill_color = style['fill'];
	if('fill-opacity' in style)
	    fill_alpha = parseFloat(style['fill-opacity']);
	if('stroke' in style)
	    stroke_color = style['stroke'];
	if('stroke-width' in style)
	    stroke_width = parseFloat(style['stroke-width']);
	if('stroke-opacity' in style)
	    stroke_alpha = parseFloat(style['stroke-opacity']);
	if('display' in style && style['display'] == 'none')
	    return;
    }

    if(fill_color) {
	if(fill_color != "none") {
	    paint = this._prepare_paint_color(fill_color, fill_alpha);
	    paint.fill(tgt);
	}
    }
    if(stroke_color) {
	if(stroke_color != "none") {
	    paint = this._prepare_paint_color(stroke_color, stroke_alpha);
	    paint.stroke(tgt);
	}
    }

    tgt.stroke_width = stroke_width;
    
    if(alpha < 1)
	tgt.parent.opacity = alpha;
};

loadSVG.prototype._set_paint = function(node, tgt) {
    var style;

    style = parse_style(node);
    this._set_paint_style(style, tgt);
};

function formalize_path_data(d) {
    var posM, posm;
    var pos;
    var nums = "0123456789+-.";
    var rel = false;
    var cmd;

    posM = d.search("M");
    posm = d.search("m");
    pos = posm < posM? posm: posM;
    if(pos == -1)
	pos = posM == -1? posm: posM;
    if(pos == -1)
	return d;

    if(posm == pos)
	rel = true;
    
    pos = pos + 1;
    while(pos < d.length && " ,".search(d[pos]) >= 0)
	pos++;
    while(pos < d.length && nums.search(d[pos]) >= 0)
	pos++;
    while(pos < d.length && " ,".search(d[pos]) >= 0)
	pos++;
    while(pos < d.length && nums.search(d[pos]) >= 0)
	pos++;
    while(pos < d.length && " ,".search(d[pos]) >= 0)
	pos++;
    if(nums.search(d[pos]) >= 0) {
	if(rel)
	    cmd = "l";
	else
	    cmd = "L";
	d = d.substring(0, pos) + cmd + formalize_path_data(d.substring(pos));
    }
    return d;
}

loadSVG.prototype.parsePath=function(accu, coord,id, n)
{
    var d = formalize_path_data(n.attr('d').value());
    var style = n.attr('style');
    var path = this.mb_rt.path_new(d);
    var pcoord = this.mb_rt.coord_new(coord);

    guessPathBoundingBox(pcoord,d);
    pcoord.add_shape(path);
    this._set_paint(n, path);
    this._set_bbox(n, path);

    make_mbnames(this.mb_rt, n, pcoord);
};

loadSVG.prototype.parseText=function(accu,coord,id, n)
{
    var x = getInteger(n,'x');
    var y = getInteger(n,'y');
    var tcoord = this.mb_rt.coord_new(coord);
    var style;

    if (n.attr('x')) {
	var nx = coord[0]*x+coord[1]*y+coord[2];
	if (coord.center.x > nx)
	    coord.center.x = nx;
    }
    if (n.attr('y')) {
	var ny = coord[3]*x+coord[4]*y+coord[5];
	if (coord.center.y > ny)
	    coord.center.y = ny;
    }
    style = parse_style(n);
    var nodes = n.childNodes();
    var k;
    for(k in nodes) {
	var c= nodes[k].name();
	if (c == "tspan") {
	    this.parseTSpan(tcoord,nodes[k],style);
	} else {
	}
    }
    this._set_bbox(n, tcoord);
	
    make_mbnames(this.mb_rt, n, tcoord);
};


function multiply(s,d) {
    var m=[];
    m[0] = s[0]*d[0]+s[1]*d[3];
    m[1] = s[0]*d[1]+s[1]*d[4];
    m[2] = s[0]*d[2]+s[1]*d[5]+s[2];
    m[3] = s[3]*d[0]+s[4]*d[3];
    m[4] = s[3]*d[1]+s[4]*d[4];
    m[5] = s[3]*d[2]+s[4]*d[5]+s[5];
    s[0] = m[0];
    s[1] = m[1];
    s[2] = m[2];
    s[3] = m[3];
    s[4] = m[4];
    s[5] = m[5];
};

function parseTransform(coord, s)
{
    var off = s.indexOf('translate');
    if (off != -1) {
        var ss = s.substring(off+9);
        for(i=0;i<ss.length;i++) {
            if (ss[i] == '(') break;
        }
        ss = ss.substring(i+1);
        for(i=0;i<ss.length;i++) {
            if (ss[i] == ')') {
                ss = ss.substring(0,i);
                break;
            }
        }
        var f = ss.split(',');
        var x,y;
        x = parseFloat(f[0]);
        y = parseFloat(f[1]);
        coord[2] += x;
        coord[5] += y;
    }
    off = s.indexOf('matrix');
    if (off != -1) {
        var end = s.indexOf(')');
        var m = s.substring(7,end);
        var fields = m.split(',');
        var newm=[];
        newm[0] = parseFloat(fields[0]);
        newm[1] = parseFloat(fields[2]);
        newm[2] = parseFloat(fields[4]);
        newm[3] = parseFloat(fields[1]);
        newm[4] = parseFloat(fields[3]);
        newm[5] = parseFloat(fields[5]);
        multiply(coord,newm);
    }
}

loadSVG.prototype.parseRect=function(accu_matrix,coord, id, n) 
{
    var x = getInteger(n,'x');
    var y = getInteger(n,'y');
    var rx,ry;
    var w = getInteger(n,'width');
    var h = getInteger(n,'height');
    var trans = n.attr('transform');
    var paint;
    var tcoord = this.mb_rt.coord_new(coord);
	
    var style = n.attr('style');

    if (trans)
        parseTransform(tcoord,trans.value());

    var rect = this.mb_rt.rect_new(x,y,w,h,10, 10);
    tcoord.add_shape(rect);
    this._set_paint(n, rect);
    this._set_bbox(n, tcoord);

    make_mbnames(this.mb_rt, n, tcoord);
};

// When we parse a group, we need to calculate the origin of the group
// so that we can resize the group without changing its origin point.
// This must be done recursively. For text/rect/image, we can get its
// origin point directly by using the (x,y) and apply their
// transformation matrix. For group, we need to send the acculumated
// matrix so that each group can get their origin correctly.
//
// Each element must be responsible to calculate its absolute origin
// point and update the origin of its parent.

function parseGroupStyle(style,n)
{
    var attr;
    if (n) {
        attr = n.attr('style');
    } else {
        attr = null;
    }
    if (attr == null) {
        return;
    }
    var f = attr.value().split(';');

    for(i in f) {
        var kv = f[i].split(':');
        if (kv[0] == 'opacity') {
            style.opacity = parseFloat(kv[1]);
        } else {
            sys.puts("Unknown style: "+kv[0]);
        }
    }
}

loadSVG.prototype.parseGroup=function(accu_matrix,root, group_id, n) {
    var k;
    var nodes = n.childNodes();
    var coord = this.mb_rt.coord_new(root);
    // Parse the transform and style here
    var trans = n.attr('transform');
    var accu=[1,0,0,0,1,0];
    var style;

    coord.center= new Object();
    coord.center.x = 10000;
    coord.center.y = 10000;
    if (trans!=null) {
	parseTransform(coord, trans.value());
    } 
    multiply(accu,accu_matrix);
    multiply(accu,coord);

    style = {};
    parseGroupStyle(style, n);
    if(style.opacity) {
	sys.puts("opacity=" + style.opacity);
	coord.opacity=style.opacity;
    }

    for(k in nodes) {
	var c = nodes[k].name();
	var attr = nodes[k].attr('id');
	var id;
	if (attr) {
	    id = attr.value();
	}
	if (c == "g") {
	    this.parseGroup(accu,coord, id, nodes[k]);
	} else if (c == "path") {
	    this.parsePath(accu,coord, id, nodes[k]);
	} else if (c == "text") {
	    this.parseText(accu,coord, id, nodes[k]);
	} else if (c == "rect") {
	    this.parseRect(accu_matrix,coord, id, nodes[k]);
	} else if (c == "image") {
	    this.parseImage(accu_matrix,coord, id, nodes[k]);
	}
    }
    if (root.center.x > coord.center.x)
	root.center.x = coord.center.x;
    if (root.center.y > coord.center.y)
	root.center.y = coord.center.y;

    this._set_bbox(n, coord);
    
    make_mbnames(this.mb_rt, n, coord);
};

loadSVG.prototype.parseImage=function(accu,coord,id, n)
{
    var ref = n.attr('href').value();
    var tcoord = this.mb_rt.coord_new(coord);
    var trans = n.attr('transform');

    if (ref == null) return;
    if (ref.substr(0,7) == "file://") {
	ref = ref.substring(7);
    } else if (ref.substr(0,5)=="file:") {
	ref = ref.substring(5);
    } else {
    }
    var w;
    var h;
    var x,y,nx,ny;
    coord.center= new Object();
    coord.center.x = 10000;
    coord.center.y = 10000;
    if (trans!=null) {
	parseTransform(coord, trans.value());
    } 

    w = n.attr("width");
    if (w == null) return;
    w = parseFloat(w.value());
    h = n.attr("height");
    if (h == null) return;
    h = parseFloat(h.value());	
    x = n.attr("x");
    if (x == null) return;
    x = parseFloat(x.value());
    y = n.attr("y");
    if (y == null) return;
    y = parseFloat(y.value());
    nx = tcoord[0]*x+tcoord[1]*y+tcoord[2];
    ny = tcoord[3]*x+tcoord[4]*y+tcoord[5];
    if (coord.center.x > nx) 
	coord.center.x = nx;
    if (coord.center.y > ny)
	coord.center.y = ny;
    var img = this.mb_rt.image_new(x,y,w,h);
    var img_data = ldr.load(ref);
    var paint = this.mb_rt.paint_image_new(img_data);
    paint.fill(img);
    tcoord.add_shape(img);
    
    this._set_bbox(n, img);
    
    make_mbnames(this.mb_rt, n, img);
};

function _parse_stops(n) {
    var children;
    var child;
    var style;
    var color;
    var rgb;
    var opacity;
    var r, g, b, a;
    var offset_atr, offset;
    var stops = [];
    var i;

    children = n.childNodes();
    for(i = 0; i < children.length; i++) {
	child = children[i];
	if(child.name() == "stop") {
	    style = parse_style(child);
	    
	    color = style["stop-color"];
	    if(color) {
		rgb = parse_color(color);
		r = rgb[0];
		g = rgb[1];
		b = rgb[2];
	    }
	    
	    opacity = style["stop-opacity"];
	    if(opacity)
		a = parseFloat(opacity);
	    else
		a = 1;
	    
	    offset_attr = child.attr("offset");
	    if(offset_attr)
		offset = parseFloat(offset_attr.value());
	    else
		offset = 0;
	    
	    stops.push([offset, r, g, b, a]);
	}
    }

    return stops;
};

loadSVG.prototype._MB_parseLinearGradient=function(root,n)
{
    var id = n.attr('id');
    var k;
    var nodes = n.childNodes();
    var mtx = [1, 0, 0, 0, 1, 0];

    if (id == null) return;
    id = id.value();

    var x1 = n.attr("x1");
    var y1 = n.attr("y1");
    var x2 = n.attr("x2");
    var y2 = n.attr("y2");
    var xy;
    var gr;
    var color, opacity;
    var stops;
    var r,g,b;
    
    if(x1)
	x1 = parseFloat(x1.value());
    if(x2)
	x2 = parseFloat(x2.value());
    if(y1)
	y1 = parseFloat(y1.value());
    if(y2)
	y2 = parseFloat(y2.value());
    
    stops = _parse_stops(n);
    
    var href = n.attr('href');
    if(href != null) {
	href = href.value();
	var hrefid = href.substring(1);
	pstops = this.stop_ref[hrefid];
	stops = pstops.concat(stops);
	
	var hrefgr = this.gradients[hrefid];
	if(typeof x1 == "undefined")
	    x1 = hrefgr[0];
	if(typeof y1 == "undefined")
	    y1 = hrefgr[1];
	if(typeof x2 == "undefined")
	    x2 = hrefgr[2];
	if(typeof y2 == "undefined")
	    y2 = hrefgr[3];
    }

    if(n.attr("gradientTransform")) {
	parseTransform(mtx, n.attr("gradientTransform").value());
	xy = _pnt_transform(x1, y1, mtx);
	x1 = xy[0];
	y1 = xy[1];
	xy = _pnt_transform(x2, y2, mtx);
	x2 = xy[0];
	y2 = xy[1];
    }

    this.stop_ref[id] = stops;
    this.gradients[id] = [x1,y1,x2,y2];
};

loadSVG.prototype._MB_parseRadialGradient = function(root,n) {
    var stops;
    var cx, cy;
    var xy;
    var mtx = [1, 0, 0, 0, 1, 0];
    var id;
    var href;
    var r;

    id = n.attr("id");
    if(!id)
	throw "Require an id";
    id = id.value();

    stops = _parse_stops(n);
    
    cx = n.attr("cx");
    if(!cx)
	throw "Miss cx attribute";
    cy = n.attr("cy");
    if(!cy)
	throw "Miss cy attribute";
    cx = parseFloat(cx.value());
    cy = parseFloat(cy.value());

    r = n.attr("r");
    if(!r)
	throw "Miss r attribute";
    r = parseFloat(r.value());

    href = n.attr("href");
    if(href) {
	href = href.value().substring(1);
	stops = this.stop_ref[href];
    }

    if(n.attr("gradientTransform")) {
	parseTransform(mtx, n.attr("gradientTransform").value());
	xy = _pnt_transform(cx, cy, mtx);
	cx = xy[0];
	cy = xy[1];
    }

    this.radials[id] = [cx, cy, r];
    this.stop_ref[id] = stops;
}

loadSVG.prototype.parseDefs=function(root,n)
{
    var k;
    var nodes = n.childNodes();
    
    for(k in nodes) {
	var name = nodes[k].name();
	if (name == "linearGradient") {
	    this._MB_parseLinearGradient(root,nodes[k]);
	} else if(name == "radialGradient") {
	    this._MB_parseRadialGradient(root,nodes[k]);
	}
    }
};