Canvas: Improve API and SVG parser.
- API: * Rename setSize to setFontSize * Add method getElementById to Group * Rename name of element to id to show more prominent that it should be unique. * Add methods for hide/show elements. * Fix: set correct id for cloned elements (parsesvg) * Fix: retrieving group with getElementById failed. * Parse SVG inkscape:transform-center-[xy] * Allow rotation around given point (use values from inkscape by default for SVG files) * Use breadth-first search instead of depth-first search for Element::getElementById (large speedup :)) * Add more convenience functions for path drawing. * Add version check (useful if API files manually copied) - SVG: * Support <use> element * Parse font-size * Parse stroke-linecap
This commit is contained in:
parent
2e3aa2673b
commit
7e641735cb
2 changed files with 459 additions and 110 deletions
|
@ -1,10 +1,10 @@
|
|||
# Helper function to create a node with the first available index for the given
|
||||
# path relative to the given node
|
||||
#
|
||||
var _createNodeWithIndex = func(node, path)
|
||||
var _createNodeWithIndex = func(node, path, min_index = 0)
|
||||
{
|
||||
# TODO do we need an upper limit? (50000 seems already seems unreachable)
|
||||
for(var i = 0; i < 50000; i += 1)
|
||||
for(var i = min_index; i < 50000; i += 1)
|
||||
{
|
||||
var p = path ~ "[" ~ i ~ "]";
|
||||
if( node.getNode(p) == nil )
|
||||
|
@ -53,10 +53,11 @@ var _setColorNodes = func(nodes, color)
|
|||
|
||||
var _arg2valarray = func
|
||||
{
|
||||
while ( typeof(arg) == "vector"
|
||||
and size(arg) == 1 and typeof(arg[0]) == "vector" )
|
||||
arg = arg[0];
|
||||
return arg;
|
||||
var ret = arg;
|
||||
while ( typeof(ret) == "vector"
|
||||
and size(ret) == 1 and typeof(ret[0]) == "vector" )
|
||||
ret = ret[0];
|
||||
return ret;
|
||||
}
|
||||
|
||||
# Transform
|
||||
|
@ -105,15 +106,30 @@ var Transform = {
|
|||
|
||||
return me;
|
||||
},
|
||||
# Set rotation (Optionally around a specified point instead of (0,0))
|
||||
#
|
||||
# setRotation(rot)
|
||||
# setRotation(rot, cx, cy)
|
||||
#
|
||||
# @note If using with rotation center different to (0,0) don't use
|
||||
# #setTranslation as it would interfere with the rotation.
|
||||
setRotation: func(angle)
|
||||
{
|
||||
var center = _arg2valarray(arg);
|
||||
|
||||
var s = math.sin(angle);
|
||||
var c = math.cos(angle);
|
||||
|
||||
me.a.setValue(c);
|
||||
me.b.setValue(s);
|
||||
me.c.setValue(-s);
|
||||
me.d.setValue(c);
|
||||
me.a.setDoubleValue(c);
|
||||
me.b.setDoubleValue(s);
|
||||
me.c.setDoubleValue(-s);
|
||||
me.d.setDoubleValue(c);
|
||||
|
||||
if( size(center) == 2 )
|
||||
{
|
||||
me.e.setDoubleValue( (-center[0] * c) + (center[1] * s) + center[0] );
|
||||
me.f.setDoubleValue( (-center[0] * s) - (center[1] * c) + center[1] );
|
||||
}
|
||||
|
||||
return me;
|
||||
},
|
||||
|
@ -147,16 +163,31 @@ var Element = {
|
|||
#
|
||||
# @param parent Parent node (In the property tree)
|
||||
# @param type Type string (Used as node name)
|
||||
# @param name Name (Should be unique)
|
||||
new: func(parent, type, name)
|
||||
# @param id ID/Name (Should be unique)
|
||||
new: func(parent, type, id)
|
||||
{
|
||||
# arg can contain the node to be used instead of creating a new one
|
||||
var args = _arg2valarray(arg);
|
||||
if( size(args) == 1 )
|
||||
{
|
||||
var node = args[0];
|
||||
if( !isa(node, props.Node) )
|
||||
return debug.warn("Not a props.Node!");
|
||||
}
|
||||
else
|
||||
var node = _createNodeWithIndex(parent, type);
|
||||
|
||||
var m = {
|
||||
parents: [Element],
|
||||
_node: _createNodeWithIndex(parent, type),
|
||||
_node: node,
|
||||
_center: [
|
||||
node.getNode("center[0]"),
|
||||
node.getNode("center[1]")
|
||||
]
|
||||
};
|
||||
|
||||
if( name != nil )
|
||||
m._node.getNode("name", 1).setValue(name);
|
||||
if( id != nil )
|
||||
m._node.getNode("id", 1).setValue(id);
|
||||
|
||||
return m;
|
||||
},
|
||||
|
@ -165,6 +196,26 @@ var Element = {
|
|||
{
|
||||
me._node.remove();
|
||||
},
|
||||
set: func(key, value)
|
||||
{
|
||||
me._node.getNode(key, 1).setValue(value);
|
||||
return me;
|
||||
},
|
||||
setBool: func(key, value)
|
||||
{
|
||||
me._node.getNode(key, 1).setBoolValue(value);
|
||||
return me;
|
||||
},
|
||||
setDouble: func(key, value)
|
||||
{
|
||||
me._node.getNode(key, 1).setDoubleValue(value);
|
||||
return me;
|
||||
},
|
||||
setInt: func(key, value)
|
||||
{
|
||||
me._node.getNode(key, 1).setIntValue(value);
|
||||
return me;
|
||||
},
|
||||
# Trigger an update of the element
|
||||
#
|
||||
# Elements are automatically updated once a frame, with a delay of one frame.
|
||||
|
@ -172,45 +223,118 @@ var Element = {
|
|||
# this method.
|
||||
update: func()
|
||||
{
|
||||
me._node.getNode("update", 1).setValue(1);
|
||||
me.setInt("update", 1);
|
||||
},
|
||||
# Hide/Show element
|
||||
#
|
||||
# @param visible Whether the element should be visible
|
||||
setVisible: func(visible = 1)
|
||||
{
|
||||
me.setBool("visible", visible);
|
||||
},
|
||||
# Hide element (Shortcut for setVisible(0))
|
||||
hide: func me.setVisible(0),
|
||||
# Show element (Shortcut for setVisible(1))
|
||||
show: func me.setVisible(1),
|
||||
#
|
||||
setGeoPosition: func(lat, lon)
|
||||
{
|
||||
me._getTf()._node.getNode("m-geo[4]", 1).setValue("N" ~ lat);
|
||||
me._getTf()._node.getNode("m-geo[5]", 1).setValue("E" ~ lon);
|
||||
return me;
|
||||
},
|
||||
# Create a new transformation matrix
|
||||
#
|
||||
# @param vals Default values (Vector of 6 elements)
|
||||
createTransform: func(vals = nil)
|
||||
{
|
||||
var node = _createNodeWithIndex(me._node, "tf");
|
||||
var node = _createNodeWithIndex(me._node, "tf", 1); # tf[0] is reserved for
|
||||
# setRotation
|
||||
return Transform.new(node, vals);
|
||||
},
|
||||
# Shortcut for setting translation
|
||||
setTranslation: func me._getTf().setTranslation(arg),
|
||||
# Shortcut for setting rotation
|
||||
setRotation: func(rot) me._getTf().setRotation(rot),
|
||||
setTranslation: func { me._getTf().setTranslation(arg); return me; },
|
||||
# Set rotation around transformation center (see #setCenter).
|
||||
#
|
||||
# @note This replaces the the existing transformation. For additional scale or
|
||||
# translation use additional transforms (see #createTransform).
|
||||
setRotation: func(rot)
|
||||
{
|
||||
if( me['_tf_rot'] == nil )
|
||||
# always use the first matrix slot to ensure correct rotation
|
||||
# around transformation center.
|
||||
me['_tf_rot'] = Transform.new(me._node.getNode("tf[0]", 1));
|
||||
|
||||
me._tf_rot.setRotation(rot, me.getCenter());
|
||||
return me;
|
||||
},
|
||||
# Shortcut for setting scale
|
||||
setScale: func me._getTf().setScale(arg),
|
||||
setScale: func { me._getTf().setScale(arg); return me; },
|
||||
# Shortcut for getting scale
|
||||
getScale: func me._getTf().getScale(),
|
||||
# Set the line/text color
|
||||
#
|
||||
# @param color Vector of 3 or 4 values in [0, 1]
|
||||
setColor: func _setColorNodes(me.color, arg),
|
||||
setColor: func { _setColorNodes(me.color, arg); return me; },
|
||||
# Set the fill/background/boundingbox color
|
||||
#
|
||||
# @param color Vector of 3 or 4 values in [0, 1]
|
||||
setColorFill: func _setColorNodes(me.color_fill, arg),
|
||||
setColorFill: func { _setColorNodes(me.color_fill, arg); return me; },
|
||||
#
|
||||
getBoundingBox: func()
|
||||
{
|
||||
var bb = me._node.getNode("bounding-box");
|
||||
var min_x = bb.getNode("min-x").getValue();
|
||||
|
||||
if( min_x != nil )
|
||||
return [ min_x,
|
||||
bb.getNode("min-y").getValue(),
|
||||
bb.getNode("max-x").getValue(),
|
||||
bb.getNode("max-y").getValue() ];
|
||||
else
|
||||
return [0, 0, 0, 0];
|
||||
},
|
||||
# Set transformation center (currently only used for rotation)
|
||||
setCenter: func()
|
||||
{
|
||||
var center = _arg2valarray(arg);
|
||||
if( size(center) != 2 )
|
||||
return debug.warn("invalid arg");
|
||||
|
||||
if( me._center[0] == nil )
|
||||
me._center[0] = me._node.getNode("center[0]", 1);
|
||||
if( me._center[1] == nil )
|
||||
me._center[1] = me._node.getNode("center[1]", 1);
|
||||
|
||||
me._center[0].setDoubleValue(center[0] or 0);
|
||||
me._center[1].setDoubleValue(center[1] or 0);
|
||||
|
||||
return me;
|
||||
},
|
||||
# Get transformation center
|
||||
getCenter: func()
|
||||
{
|
||||
var bb = me.getBoundingBox();
|
||||
var center = [0, 0];
|
||||
|
||||
if( me._center[0] != nil )
|
||||
center[0] = me._center[0].getValue() or 0;
|
||||
if( me._center[1] != nil )
|
||||
center[1] = me._center[1].getValue() or 0;
|
||||
|
||||
if( bb[0] >= bb[2] or bb[1] >= bb[3] )
|
||||
return center;
|
||||
|
||||
return [ 0.5 * (bb[0] + bb[2]) + center[0],
|
||||
0.5 * (bb[1] + bb[3]) + center[1] ];
|
||||
},
|
||||
# Internal Transform for convenience transform functions
|
||||
_getTf: func
|
||||
{
|
||||
if( me['_tf'] == nil )
|
||||
me['_tf'] = me.createTransform();
|
||||
return me._tf;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
# Group
|
||||
|
@ -218,13 +342,21 @@ var Element = {
|
|||
# Class for a group element on a canvas
|
||||
#
|
||||
var Group = {
|
||||
new: func(parent, name, type = "group")
|
||||
new: func(parent, id, type = "group")
|
||||
{
|
||||
return { parents: [Group, Element.new(parent, type, name)] };
|
||||
# special case: if called from #getElementById the third argument is the
|
||||
# existing node so we need to rearange the variables a bit.
|
||||
if( typeof(type) != "scalar" )
|
||||
{
|
||||
var arg = [type];
|
||||
var type = "group";
|
||||
}
|
||||
|
||||
return { parents: [Group, Element.new(parent, type, id, arg)] };
|
||||
},
|
||||
# Create a child of given type with specified name.
|
||||
# Create a child of given type with specified id.
|
||||
# type can be group, text
|
||||
createChild: func(type, name = nil)
|
||||
createChild: func(type, id = nil)
|
||||
{
|
||||
var factory = me._element_factories[type];
|
||||
|
||||
|
@ -234,24 +366,62 @@ var Group = {
|
|||
return nil;
|
||||
}
|
||||
|
||||
return factory(me._node, name);
|
||||
return factory(me._node, id);
|
||||
},
|
||||
# Get first child with given id (breadth-first search)
|
||||
#
|
||||
# @note Use with care as it can take several miliseconds (for me eg. ~2ms).
|
||||
getElementById: func(id)
|
||||
{
|
||||
# TODO can we improve the queue or better port this to C++ or use some kind
|
||||
# of lookup hash? Searching is really slow now...
|
||||
var stack = [me._node];
|
||||
var index = 0;
|
||||
|
||||
while( index < size(stack) )
|
||||
{
|
||||
var node = stack[index];
|
||||
index += 1;
|
||||
|
||||
if( node != me._node )
|
||||
{
|
||||
var node_id = node.getNode("id");
|
||||
if( node_id != nil and node_id.getValue() == id )
|
||||
return me._element_factories[ node.getName() ]
|
||||
(
|
||||
nil,
|
||||
nil,
|
||||
# use the existing node
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
foreach(var c; node.getChildren())
|
||||
# element nodes have type NONE and valid element names (those in the the
|
||||
# factor list)
|
||||
if( c.getType() == "NONE"
|
||||
and me._element_factories[ c.getName() ] != nil )
|
||||
append(stack, c);
|
||||
}
|
||||
},
|
||||
# Remove all children
|
||||
removeAllChildren: func()
|
||||
{
|
||||
foreach(var type; keys(me._element_factories))
|
||||
me._node.removeChildren(type, 0);
|
||||
return me;
|
||||
}
|
||||
};
|
||||
|
||||
# Group
|
||||
# Map
|
||||
# ==============================================================================
|
||||
# Class for a group element on a canvas
|
||||
# Class for a group element on a canvas with possibly geopgraphic positions
|
||||
# which automatically get projected according to the specified projection.
|
||||
#
|
||||
var Map = {
|
||||
new: func(parent, name)
|
||||
new: func(parent, id)
|
||||
{
|
||||
return { parents: [Map, Group.new(parent, name, "map")] };
|
||||
return { parents: [Map, Group.new(parent, id, "map", arg)] };
|
||||
}
|
||||
# TODO
|
||||
};
|
||||
|
@ -261,10 +431,10 @@ var Map = {
|
|||
# Class for a text element on a canvas
|
||||
#
|
||||
var Text = {
|
||||
new: func(parent, name)
|
||||
new: func(parent, id)
|
||||
{
|
||||
var m = {
|
||||
parents: [Text, Element.new(parent, "text", name)]
|
||||
parents: [Text, Element.new(parent, "text", id, arg)]
|
||||
};
|
||||
m.color = _createColorNodes(m._node, "color");
|
||||
m.color_fill = _createColorNodes(m._node, "color-fill");
|
||||
|
@ -274,7 +444,7 @@ var Text = {
|
|||
setText: func(text)
|
||||
{
|
||||
# add space because osg seems to remove last character if its a space
|
||||
me._node.getNode("text", 1).setValue(typeof(text) == 'scalar' ? text ~ ' ' : "");
|
||||
me.set("text", typeof(text) == 'scalar' ? text ~ ' ' : "");
|
||||
},
|
||||
# Set alignment
|
||||
#
|
||||
|
@ -297,18 +467,18 @@ var Text = {
|
|||
#
|
||||
setAlignment: func(align)
|
||||
{
|
||||
me._node.getNode("alignment", 1).setValue(align);
|
||||
me.set("alignment", align);
|
||||
},
|
||||
# Set the font size
|
||||
setSize: func(size, aspect = 1)
|
||||
setFontSize: func(size, aspect = 1)
|
||||
{
|
||||
me._node.getNode("character-size", 1).setDoubleValue(size);
|
||||
me._node.getNode("character-aspect-ratio", 1).setDoubleValue(aspect);
|
||||
me.setDouble("character-size", size);
|
||||
me.setDouble("character-aspect-ratio", aspect);
|
||||
},
|
||||
# Set font (by name of font file)
|
||||
setFont: func(name)
|
||||
{
|
||||
me._node.getNode("font", 1).setValue(name);
|
||||
me.set("font", name);
|
||||
},
|
||||
# Enumeration of values for drawing mode:
|
||||
TEXT: 1, # The text itself
|
||||
|
@ -321,21 +491,16 @@ var Text = {
|
|||
# eg. my_text.setDrawMode(Text.TEXT + Text.BOUNDINGBOX);
|
||||
setDrawMode: func(mode)
|
||||
{
|
||||
me._node.getNode("draw-mode", 1).setValue(mode);
|
||||
me.setInt("draw-mode", mode);
|
||||
},
|
||||
# Set bounding box padding
|
||||
setPadding: func(pad)
|
||||
{
|
||||
me._node.getNode("padding", 1).setValue(pad);
|
||||
me.setDouble("padding", pad);
|
||||
},
|
||||
#
|
||||
getBoundingBox: func()
|
||||
setMaxWidth: func(w)
|
||||
{
|
||||
var bb = me._node.getNode("bounding-box");
|
||||
return [ bb.getChild("min-x").getValue(),
|
||||
bb.getChild("min-y").getValue(),
|
||||
bb.getChild("max-x").getValue(),
|
||||
bb.getChild("max-y").getValue() ];
|
||||
me.setDouble("max-width", w);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -370,76 +535,169 @@ var Path = {
|
|||
VG_SCUBIC_TO: 16,
|
||||
VG_SCUBIC_TO_ABS: 16,
|
||||
VG_SCUBIC_TO_REL: 17,
|
||||
VG_SCCWARC_TO: 18,
|
||||
VG_SCCWARC_TO_ABS:18,
|
||||
VG_SCCWARC_TO_REL:19,
|
||||
VG_SCWARC_TO: 20,
|
||||
VG_SCWARC_TO_ABS: 20,
|
||||
VG_SCWARC_TO_REL: 21,
|
||||
VG_LCCWARC_TO: 22,
|
||||
VG_LCCWARC_TO_ABS:22,
|
||||
VG_LCCWARC_TO_REL:23,
|
||||
VG_LCWARC_TO: 24,
|
||||
VG_LCWARC_TO_ABS: 24,
|
||||
VG_LCWARC_TO_REL: 25,
|
||||
|
||||
VG_SCCWARC_TO: 20, # Note that CC and CCW commands are swapped. This is
|
||||
VG_SCCWARC_TO_ABS:20, # needed due to the different coordinate systems used.
|
||||
VG_SCCWARC_TO_REL:21, # In OpenVG values along the y-axis increase from bottom
|
||||
VG_SCWARC_TO: 18, # to top, whereas in the Canvas system it is flipped.
|
||||
VG_SCWARC_TO_ABS: 18,
|
||||
VG_SCWARC_TO_REL: 19,
|
||||
VG_LCCWARC_TO: 24,
|
||||
VG_LCCWARC_TO_ABS:24,
|
||||
VG_LCCWARC_TO_REL:25,
|
||||
VG_LCWARC_TO: 22,
|
||||
VG_LCWARC_TO_ABS: 22,
|
||||
VG_LCWARC_TO_REL: 23,
|
||||
|
||||
# Number of coordinates per command
|
||||
num_coords: {
|
||||
0: 0, # VG_CLOSE_PATH
|
||||
2: 2, # VG_MOVE_TO
|
||||
4: 2, # VG_LINE_TO
|
||||
6: 1, # VG_HLINE_TO
|
||||
8: 1, # VG_VLINE_TO
|
||||
10: 4, # VG_QUAD_TO
|
||||
12: 6, # VG_CUBIC_TO
|
||||
14: 2, # VG_SQUAD_TO
|
||||
16: 4, # VG_SCUBIC_TO
|
||||
18: 5, # VG_SCCWARC_TO
|
||||
20: 5, # VG_SCWARC_TO
|
||||
22: 5, # VG_LCCWARC_TO
|
||||
24: 5 # VG_LCWARC_TO
|
||||
},
|
||||
num_coords: [
|
||||
0, 0, # VG_CLOSE_PATH
|
||||
2, 2, # VG_MOVE_TO
|
||||
2, 2, # VG_LINE_TO
|
||||
1, 1, # VG_HLINE_TO
|
||||
1, 1, # VG_VLINE_TO
|
||||
4, 4, # VG_QUAD_TO
|
||||
6, 6, # VG_CUBIC_TO
|
||||
2, 2, # VG_SQUAD_TO
|
||||
4, 4, # VG_SCUBIC_TO
|
||||
5, 5, # VG_SCCWARC_TO
|
||||
5, 5, # VG_SCWARC_TO
|
||||
5, 5, # VG_LCCWARC_TO
|
||||
5, 5 # VG_LCWARC_TO
|
||||
],
|
||||
|
||||
#
|
||||
new: func(parent, name)
|
||||
new: func(parent, id)
|
||||
{
|
||||
var m = {
|
||||
parents: [Path, Element.new(parent, "path", name)]
|
||||
parents: [Path, Element.new(parent, "path", id, arg)],
|
||||
_num_cmds: 0,
|
||||
_num_coords: 0
|
||||
};
|
||||
m.color = _createColorNodes(m._node, "color");
|
||||
m.color_fill = _createColorNodes(m._node, "color-fill");
|
||||
return m;
|
||||
},
|
||||
# Set the path data (commands and coordinates)
|
||||
setData: func(cmds, coords)
|
||||
{
|
||||
me._node.removeChildren('cmd', 0);
|
||||
me._node.removeChildren('coord', 0);
|
||||
me._node.setValues({cmd: cmds, coord: coords});
|
||||
},
|
||||
setDataGeo: func(cmds, coords)
|
||||
# Remove all existing path data
|
||||
reset: func
|
||||
{
|
||||
me._node.removeChildren('cmd', 0);
|
||||
me._node.removeChildren('coord', 0);
|
||||
me._node.removeChildren('coord-geo', 0);
|
||||
me._node.setValues({cmd: cmds, 'coord-geo': coords});
|
||||
me._num_cmds = 0;
|
||||
me._num_coords = 0;
|
||||
return me;
|
||||
},
|
||||
# Set the path data (commands and coordinates)
|
||||
setData: func(cmds, coords)
|
||||
{
|
||||
me.reset();
|
||||
me._node.setValues({cmd: cmds, coord: coords});
|
||||
me._num_cmds = size(cmds);
|
||||
me._num_coords = size(coords);
|
||||
return me;
|
||||
},
|
||||
setDataGeo: func(cmds, coords)
|
||||
{
|
||||
me.reset();
|
||||
me._node.setValues({cmd: cmds, 'coord-geo': coords});
|
||||
me._num_cmds = size(cmds);
|
||||
me._num_coords = size(coords);
|
||||
return me;
|
||||
},
|
||||
# Add a path segment
|
||||
addSegment: func(cmd, coords...)
|
||||
{
|
||||
var coords = _arg2valarray(coords);
|
||||
var num_coords = me.num_coords[cmd];
|
||||
if( size(coords) != num_coords )
|
||||
debug.warn
|
||||
(
|
||||
"Invalid number of arguments (expected " ~ (num_coords + 1) ~ ")"
|
||||
);
|
||||
else
|
||||
{
|
||||
me.setInt("cmd[" ~ (me._num_cmds += 1) ~ "]", cmd);
|
||||
for(var i = 0; i < num_coords; i += 1)
|
||||
me.setDouble("coord[" ~ (me._num_coords += 1) ~ "]", coords[i]);
|
||||
}
|
||||
|
||||
return me;
|
||||
},
|
||||
# Move path cursor
|
||||
moveTo: func me.addSegment(me.VG_MOVE_TO_ABS, arg),
|
||||
move: func me.addSegment(me.VG_MOVE_TO_REL, arg),
|
||||
# Add a line
|
||||
lineTo: func me.addSegment(me.VG_LINE_TO_ABS, arg),
|
||||
line: func me.addSegment(me.VG_LINE_TO_REL, arg),
|
||||
# Add a horizontal line
|
||||
horizTo: func me.addSegment(me.VG_HLINE_TO_ABS, arg),
|
||||
horiz: func me.addSegment(me.VG_HLINE_TO_REL, arg),
|
||||
# Add a vertical line
|
||||
vertTo: func me.addSegment(me.VG_VLINE_TO_ABS, arg),
|
||||
vert: func me.addSegment(me.VG_VLINE_TO_REL, arg),
|
||||
# Add a quadratic Bézier curve
|
||||
quadTo: func me.addSegment(me.VG_QUAD_TO_ABS, arg),
|
||||
quad: func me.addSegment(me.VG_QUAD_TO_REL, arg),
|
||||
# Add a cubic Bézier curve
|
||||
cubicTo: func me.addSegment(me.VG_CUBIC_TO_ABS, arg),
|
||||
cubic: func me.addSegment(me.VG_CUBIC_TO_REL, arg),
|
||||
# Add a smooth quadratic Bézier curve
|
||||
quadTo: func me.addSegment(me.VG_SQUAD_TO_ABS, arg),
|
||||
quad: func me.addSegment(me.VG_SQUAD_TO_REL, arg),
|
||||
# Add a smooth cubic Bézier curve
|
||||
cubicTo: func me.addSegment(me.VG_SCUBIC_TO_ABS, arg),
|
||||
cubic: func me.addSegment(me.VG_SCUBIC_TO_REL, arg),
|
||||
# Draw an elliptical arc (shorter counter-clockwise arc)
|
||||
arcSmallCCWTo: func me.addSegment(me.VG_SCCWARC_TO_ABS, arg),
|
||||
arcSmallCCW: func me.addSegment(me.VG_SCCWARC_TO_REL, arg),
|
||||
# Draw an elliptical arc (shorter clockwise arc)
|
||||
arcSmallCWTo: func me.addSegment(me.VG_SCWARC_TO_ABS, arg),
|
||||
arcSmallCW: func me.addSegment(me.VG_SCWARC_TO_REL, arg),
|
||||
# Draw an elliptical arc (longer counter-clockwise arc)
|
||||
arcLargeCCWTo: func me.addSegment(me.VG_LCCWARC_TO_ABS, arg),
|
||||
arcLargeCCW: func me.addSegment(me.VG_LCCWARC_TO_REL, arg),
|
||||
# Draw an elliptical arc (shorter clockwise arc)
|
||||
arcLargeCWTo: func me.addSegment(me.VG_LCWARC_TO_ABS, arg),
|
||||
arcLargeCW: func me.addSegment(me.VG_LCWARC_TO_REL, arg),
|
||||
# Close the path (implicit lineTo to first point of path)
|
||||
close: func me.addSegment(me.VG_CLOSE_PATH),
|
||||
|
||||
setStrokeLineWidth: func(width)
|
||||
{
|
||||
me._node.getNode('stroke-width', 1).setDoubleValue(width);
|
||||
me.setDouble('stroke-width', width);
|
||||
},
|
||||
setStrokeDashPattern: func(pattern)
|
||||
# Set stroke linecap
|
||||
#
|
||||
# @param linecap String, "butt", "round" or "square"
|
||||
#
|
||||
# See http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty for details
|
||||
setStrokeLineCap: func(linecap)
|
||||
{
|
||||
me.set('stroke-linecap', linecap);
|
||||
},
|
||||
# Set stroke dasharray
|
||||
#
|
||||
# @param pattern Vector, Vector of alternating dash and gap lengths
|
||||
# [on1, off1, on2, ...]
|
||||
setStrokeDashArray: func(pattern)
|
||||
{
|
||||
me._node.removeChildren('stroke-dasharray');
|
||||
|
||||
if( typeof(pattern) == 'vector' )
|
||||
me._node.setValues({'stroke-dasharray': pattern});
|
||||
else
|
||||
debug.warn("setStrokeDashPattern: vector expected!");
|
||||
debug.warn("setStrokeDashArray: vector expected!");
|
||||
|
||||
return me;
|
||||
},
|
||||
# Set the fill color and enable filling this path
|
||||
#
|
||||
# @param color Vector of 3 or 4 values in [0, 1]
|
||||
setColorFill: func { _setColorNodes(me.color_fill, arg); me.setFill(1); },
|
||||
# Enable/disable filling this path
|
||||
setFill: func(fill)
|
||||
{
|
||||
me._node.getNode("fill", 1).setValue(fill);
|
||||
me.setBool("fill", fill);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -476,15 +734,15 @@ var Canvas = {
|
|||
},
|
||||
# Create a new group with the given name
|
||||
#
|
||||
# @param name Option name for the group
|
||||
createGroup: func(name = nil)
|
||||
# @param id Optional id/name for the group
|
||||
createGroup: func(id = nil)
|
||||
{
|
||||
return Group.new(me.texture, name);
|
||||
return Group.new(me.texture, id);
|
||||
},
|
||||
# Set the background color
|
||||
#
|
||||
# @param color Vector of 3 or 4 values in [0, 1]
|
||||
setColorBackground: func _setColorNodes(me.color, arg)
|
||||
setColorBackground: func { _setColorNodes(me.color, arg); return me; }
|
||||
};
|
||||
|
||||
# Create a new canvas. Pass parameters as hash, eg:
|
||||
|
@ -545,3 +803,26 @@ var get = func(name)
|
|||
color: _createColorNodes(node_canvas, "color-background")
|
||||
};
|
||||
};
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Show warnings if API used with too old version of FlightGear without Canvas
|
||||
# support (Wrapped in anonymous function do not polute the canvas namespace)
|
||||
|
||||
(func {
|
||||
var version_str = getprop("/sim/version/flightgear");
|
||||
if( string.scanf(version_str, "%u.%u.%u", var fg_version = []) < 1 )
|
||||
debug.warn("Canvas: Error parsing flightgear version (" ~ version_str ~ ")");
|
||||
else
|
||||
{
|
||||
if( fg_version[0] < 2
|
||||
or (fg_version[0] == 2 and fg_version[1] < 8) )
|
||||
{
|
||||
debug.warn("Canvas: FlightGear version too old (" ~ version_str ~ ")");
|
||||
gui.popupTip
|
||||
(
|
||||
"FlightGear v2.8.0 or newer needed for Canvas support!",
|
||||
600,
|
||||
{button: {legend: "Ok", binding: {command: "dialog-close"}}}
|
||||
);
|
||||
}
|
||||
} })();
|
||||
|
|
|
@ -1,15 +1,50 @@
|
|||
# Parse an xml file into a canvas group element
|
||||
#
|
||||
# @param group The canvas.Group instance to append the parsed elements to
|
||||
# @param path The path of the svg file (absolute or relative to FG_ROOT)
|
||||
var parsesvg = func(group, path)
|
||||
# @param group The canvas.Group instance to append the parsed elements to
|
||||
# @param path The path of the svg file (absolute or relative to FG_ROOT)
|
||||
# @param options Optional hash of options
|
||||
var parsesvg = func(group, path, options = nil)
|
||||
{
|
||||
if( !isa(group, Group) )
|
||||
die("Invalid argument group (type != Group)");
|
||||
|
||||
if( options == nil )
|
||||
options = {};
|
||||
|
||||
if( typeof(options) != "hash" )
|
||||
die("Options need to be of type hash!");
|
||||
|
||||
var custom_font_mapper = options['font-mapper'];
|
||||
var font_mapper = func(family, weight)
|
||||
{
|
||||
if( typeof(custom_font_mapper) == 'func' )
|
||||
{
|
||||
var font = custom_font_mapper(family, weight);
|
||||
if( font != nil )
|
||||
return font;
|
||||
}
|
||||
|
||||
return "LiberationFonts/LiberationMono-Bold.ttf";
|
||||
};
|
||||
|
||||
var level = 0;
|
||||
var skip = 0;
|
||||
var stack = [group];
|
||||
var close_stack = []; # helper for check tag closing
|
||||
|
||||
# lookup table for element ids (for <use> element)
|
||||
var id_dict = {};
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Create a new child an push it onto the stack
|
||||
var pushElement = func(type, id = nil)
|
||||
{
|
||||
append(stack, stack[-1].createChild(type, id));
|
||||
append(close_stack, level);
|
||||
|
||||
if( typeof(id) == 'scalar' and size(id) )
|
||||
id_dict[ id ] = stack[-1];
|
||||
};
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Parse a transformation (matrix)
|
||||
|
@ -261,11 +296,11 @@ var parsesvg = func(group, path)
|
|||
}
|
||||
else if( name == "g" )
|
||||
{
|
||||
append(stack, stack[-1].createChild('group', attr['id']));
|
||||
pushElement('group', attr['id']);
|
||||
}
|
||||
else if( name == "text" )
|
||||
{
|
||||
append(stack, stack[-1].createChild('text', attr['id']));
|
||||
pushElement('text', attr['id']);
|
||||
stack[-1].setTranslation(attr['x'], attr['y']);
|
||||
|
||||
# http://www.w3.org/TR/SVG/text.html#TextAnchorProperty
|
||||
|
@ -280,12 +315,19 @@ var parsesvg = func(group, path)
|
|||
# TODO vertical align
|
||||
|
||||
stack[-1].setColor(parseColor(style['fill']));
|
||||
stack[-1].setFont("UbuntuMono-B.ttf");
|
||||
#stack[-1].setFont("LiberationFonts/LiberationMono-Bold.ttf");
|
||||
stack[-1].setFont
|
||||
(
|
||||
font_mapper(style["font-family"], style["font-weight"])
|
||||
);
|
||||
|
||||
var font_size = style["font-size"];
|
||||
if( font_size != nil )
|
||||
# eg. font-size: 123px
|
||||
stack[-1].setFontSize(substr(font_size, 0, size(font_size) - 2));
|
||||
}
|
||||
else if( name == "path" or name == "rect" )
|
||||
{
|
||||
append(stack, stack[-1].createChild('path', attr['id']));
|
||||
pushElement('path', attr['id']);
|
||||
var d = attr['d'];
|
||||
|
||||
if( name == "rect" )
|
||||
|
@ -304,23 +346,46 @@ var parsesvg = func(group, path)
|
|||
stack[-1].setStrokeLineWidth( w != nil ? w : 1 );
|
||||
stack[-1].setColor(parseColor(style['stroke']));
|
||||
|
||||
var linecap = style['stroke-linecap'];
|
||||
if( linecap != nil )
|
||||
stack[-1].setStrokeLineCap(style['stroke-linecap']);
|
||||
|
||||
var fill = style['fill'];
|
||||
if( fill != nil and fill != "none" )
|
||||
{
|
||||
stack[-1].setColorFill(parseColor(fill));
|
||||
stack[-1].setFill(1);
|
||||
}
|
||||
|
||||
# http://www.w3.org/TR/SVG/painting.html#StrokeDasharrayProperty
|
||||
var dash = style['stroke-dasharray'];
|
||||
if( dash and size(dash) > 3 )
|
||||
# at least 2 comma separated values...
|
||||
stack[-1].setStrokeDashPattern(split(',', dash));
|
||||
stack[-1].setStrokeDashArray(split(',', dash));
|
||||
|
||||
var cx = attr['inkscape:transform-center-x'];
|
||||
var cy = attr['inkscape:transform-center-y'];
|
||||
if( cx != nil or cy != nil )
|
||||
stack[-1].setCenter(cx or 0, -(cy or 0));
|
||||
}
|
||||
else if( name == "tspan" )
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if( name == "use" )
|
||||
{
|
||||
var ref = attr["xlink:href"];
|
||||
if( ref == nil or size(ref) < 2 or ref[0] != `#` )
|
||||
return debug.dump("Invalid or missing href", ref);
|
||||
|
||||
var el_src = id_dict[ substr(ref, 1) ];
|
||||
if( el_src == nil )
|
||||
return print("parsesvg: Reference to unknown element (" ~ ref ~ ")");
|
||||
|
||||
# Create new element and copy sub branch from source node
|
||||
pushElement(el_src._node.getName(), attr['id']);
|
||||
props.copy(el_src._node, stack[-1]._node);
|
||||
|
||||
# copying also overrides the id so we need to set it again
|
||||
stack[-1]._node.getNode("id").setValue(attr['id']);
|
||||
}
|
||||
else
|
||||
{
|
||||
print("parsesvg: skipping unknown element '" ~ name ~ "'");
|
||||
|
@ -343,8 +408,11 @@ var parsesvg = func(group, path)
|
|||
return;
|
||||
}
|
||||
|
||||
if( name == 'g' or name == 'text' or name == 'path' or name == 'rect' )
|
||||
if( size(close_stack) and (level + 1) == close_stack[-1] )
|
||||
{
|
||||
pop(stack);
|
||||
pop(close_stack);
|
||||
}
|
||||
};
|
||||
|
||||
# XML parsers element data callback
|
||||
|
|
Loading…
Add table
Reference in a new issue