diff --git a/Nasal/canvas/api.nas b/Nasal/canvas/api.nas
new file mode 100644
index 000000000..ef230bf65
--- /dev/null
+++ b/Nasal/canvas/api.nas
@@ -0,0 +1,520 @@
+# 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)
+{
+  # TODO do we need an upper limit? (50000 seems already seems unreachable)
+  for(var i = 0; i < 50000; i += 1)
+  {
+    var p = path ~ "[" ~ i ~ "]";
+    if( node.getNode(p) == nil )
+      return node.getNode(p, 1);
+  }
+  
+  debug.warn("Unable to get child (already 50000 exist)");
+
+  return nil;
+};
+
+# Internal helper
+var _createColorNodes = func(parent, name)
+{
+  var node = parent.getNode(name, 1);
+  return [ node.getNode("red", 1),
+           node.getNode("green", 1),
+           node.getNode("blue", 1),
+           node.getNode("alpha", 1) ];
+};
+
+var _setColorNodes = func(nodes, color)
+{
+  if( typeof(nodes) != "vector" )
+  {
+    debug.warn("This element doesn't support setting color");
+    return;
+  }
+  
+  if( size(color) == 1 )
+    color = color[0];
+
+  if( typeof(color) != "vector" )
+    return debug.warn("Wrong type for color");
+  
+  if( size(color) < 3 or size(color) > 4 )
+    return debug.warn("Color needs 3 or 4 values (RGB or RGBA)");
+
+  for(var i = 0; i < size(color); i += 1)
+    nodes[i].setDoubleValue( color[i] );
+
+  if( size(color) == 3 )
+    # default alpha is 1
+    nodes[3].setDoubleValue(1);
+};
+
+var _arg2valarray = func
+{
+  while (    typeof(arg) == "vector"
+            and size(arg) == 1 and typeof(arg[0]) == "vector" )
+      arg = arg[0];
+  return arg;
+}
+
+# Transform
+# ==============================================================================
+# A transformation matrix which is used to transform an #Element on the canvas.
+# The dimensions of the matrix are 3x3 where the last row is always 0 0 1:
+#
+#  a c e
+#  b d f
+#  0 0 1
+#
+# See http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined for details.
+#
+var Transform = {
+  new: func(node, vals = nil)
+  {
+    var m = {
+      parents: [Transform],
+      a: node.getNode("a", 1),
+      b: node.getNode("b", 1),
+      c: node.getNode("c", 1),
+      d: node.getNode("d", 1),
+      e: node.getNode("e", 1),
+      f: node.getNode("f", 1)
+    };
+    
+    var use_vals = typeof(vals) == 'vector' and size(vals) == 6;
+    
+    # initialize to identity matrix
+    m.a.setDoubleValue(use_vals ? vals[0] : 1);
+    m.b.setDoubleValue(use_vals ? vals[1] : 0);
+    m.c.setDoubleValue(use_vals ? vals[2] : 0);
+    m.d.setDoubleValue(use_vals ? vals[3] : 1);
+    m.e.setDoubleValue(use_vals ? vals[4] : 0);
+    m.f.setDoubleValue(use_vals ? vals[5] : 0);
+    
+    return m;
+  },
+  setTranslation: func
+  {
+    var trans = _arg2valarray(arg);
+
+    me.e.setDoubleValue(trans[0]);
+    me.f.setDoubleValue(trans[1]);
+    
+    return me;
+  },
+  setRotation: func(angle)
+  {
+    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);
+    
+    return me;
+  },
+  # Set scale (either as parameters or array)
+  #
+  # If only one parameter is given its value is used for both x and y
+  #  setScale(x, y)
+  #  setScale([x, y])
+  setScale: func
+  {
+    var scale = _arg2valarray(arg);
+
+    me.a.setDoubleValue(scale[0]);
+    me.d.setDoubleValue(size(scale) >= 2 ? scale[1] : scale[0]);
+    
+    return me;
+  },
+  getScale: func()
+  {
+    # TODO handle rotation
+    return [me.a.getValue(), me.d.getValue()];
+  }
+};
+
+# Element
+# ==============================================================================
+# Baseclass for all elements on a canvas
+#
+var Element = {
+  # Constructor
+  #
+  # @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)
+  {
+    var m = {
+      parents: [Element],
+      _node: _createNodeWithIndex(parent, type),
+    };
+    
+    if( name != nil )
+      m._node.getNode("name", 1).setValue(name);
+
+    return m;
+  },
+  # Destructor (has to be called manually!)
+  del: func()
+  {
+    me._node.remove();
+  },
+  # Trigger an update of the element
+  # 
+  # Elements are automatically updated once a frame, with a delay of one frame.
+  # If you wan't to get an element updated in the current frame you have to use
+  # this method.
+  update: func()
+  {
+    me._node.getNode("update", 1).setValue(1);
+  },
+  # Create a new transformation matrix
+  #
+  # @param vals Default values (Vector of 6 elements)
+  createTransform: func(vals = nil)
+  {
+    var node = _createNodeWithIndex(me._node, "tf");
+    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),
+  # Shortcut for setting scale
+  setScale: func me._getTf().setScale(arg),
+  # 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),
+  # Set the fill/background/boundingbox color
+  #
+  # @param color  Vector of 3 or 4 values in [0, 1]
+  setColorFill: func _setColorNodes(me.color_fill, arg),
+  # Internal Transform for convenience transform functions
+  _getTf: func
+  {
+    if( me['_tf'] == nil )
+      me['_tf'] = me.createTransform();
+    return me._tf;
+  },
+};
+
+# Group
+# ==============================================================================
+# Class for a group element on a canvas
+#
+var Group = {
+  new: func(parent, name)
+  {
+    return { parents: [Group, Element.new(parent, "group", name)] };
+  },
+  # Create a child of given type with specified name.
+  # type can be group, text
+  createChild: func(type, name = nil)
+  {
+    var factory = me._element_factories[type];
+    
+    if( factory == nil )
+    {
+      debug.dump("canvas.Group.createChild(): unknown type (" ~ type ~ ")");
+      return nil;
+    }
+    
+    return factory(me._node, name);
+  },
+  # Remove all children
+  removeAllChildren: func()
+  {
+    foreach(var type; keys(me._element_factories))
+      me._node.removeChildren(type, 0);
+  }
+};
+
+# Text
+# ==============================================================================
+# Class for a text element on a canvas
+#
+var Text = {
+  new: func(parent, name)
+  {
+    var m = {
+      parents: [Text, Element.new(parent, "text", name)]
+    };
+    m.color = _createColorNodes(m._node, "color");
+    m.color_fill = _createColorNodes(m._node, "color-fill");
+    return m;
+  },
+  # Set the 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 ~ ' ' : "");
+  },
+  # Set alignment
+  #
+  #  @param algin String, one of:
+  #   left-top
+  #   left-center
+  #   left-bottom
+  #   center-top
+  #   center-center
+  #   center-bottom
+  #   right-top
+  #   right-center
+  #   right-bottom
+  #   left-baseline
+  #   center-baseline
+  #   right-baseline
+  #   left-bottom-baseline
+  #   center-bottom-baseline
+  #   right-bottom-baseline
+  #
+  setAlignment: func(align)
+  {
+    me._node.getNode("alignment", 1).setValue(align);
+  },
+  # Set the font size
+  setSize: func(size, aspect = 1)
+  {
+    me._node.getNode("character-size", 1).setDoubleValue(size);
+    me._node.getNode("character-aspect-ratio", 1).setDoubleValue(aspect);
+  },
+  # Set font (by name of font file)
+  setFont: func(name)
+  {
+    me._node.getNode("font", 1).setValue(name);
+  },
+  # Enumeration of values for drawing mode:
+  TEXT:               1, # The text itself
+  BOUNDINGBOX:        2, # A bounding box (only lines)
+  FILLEDBOUNDINGBOX:  4, # A filled bounding box
+  ALIGNMENT:          8, # Draw a marker (cross) at the position of the text
+  # Set draw mode. Binary combination of the values above. Since I haven't found
+  # a bitwise or we have to use a + instead.
+  #
+  #  eg. my_text.setDrawMode(Text.TEXT + Text.BOUNDINGBOX);
+  setDrawMode: func(mode)
+  {
+    me._node.getNode("draw-mode", 1).setValue(mode);
+  },
+  # Set bounding box padding
+  setPadding: func(pad)
+  {
+    me._node.getNode("padding", 1).setValue(pad);
+  },
+  #
+  getBoundingBox: func()
+  {
+    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() ];
+  }
+};
+
+# Path
+# ==============================================================================
+# Class for an (OpenVG) path element on a canvas
+#
+var Path = {
+  # Path segment commands (VGPathCommand)
+  VG_CLOSE_PATH:     0,
+  VG_MOVE_TO:        2,
+  VG_MOVE_TO_ABS:    2,
+  VG_MOVE_TO_REL:    3,
+  VG_LINE_TO:        4,
+  VG_LINE_TO_ABS:    4,
+  VG_LINE_TO_REL:    5,
+  VG_HLINE_TO:       6,
+  VG_HLINE_TO_ABS:   6,
+  VG_HLINE_TO_REL:   7,
+  VG_VLINE_TO:       8,
+  VG_VLINE_TO_ABS:   8,
+  VG_VLINE_TO_REL:   9,
+  VG_QUAD_TO:       10,
+  VG_QUAD_TO_ABS:   10,
+  VG_QUAD_TO_REL:   11,
+  VG_CUBIC_TO:      12,
+  VG_CUBIC_TO_ABS:  12,
+  VG_CUBIC_TO_REL:  13,
+  VG_SQUAD_TO:      14,
+  VG_SQUAD_TO_ABS:  14,
+  VG_SQUAD_TO_REL:  15,
+  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,
+  
+  # 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
+  },
+
+  #
+  new: func(parent, name)
+  {
+    var m = {
+      parents: [Path, Element.new(parent, "path", name)]
+    };
+    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});
+  },
+  setStrokeLineWidth: func(width)
+  {
+    me._node.getNode('stroke-width', 1).setDoubleValue(width);
+  },
+  setStrokeDashPattern: func(pattern)
+  {
+    me._node.removeChildren('stroke-dasharray');
+
+    if( typeof(pattern) == 'vector' )
+      me._node.setValues({'stroke-dasharray': pattern});
+    else
+      debug.warn("setStrokeDashPattern: vector expected!");
+  },
+  setFill: func(fill)
+  {
+    me._node.getNode("fill", 1).setValue(fill);
+  }
+};
+
+# Element factories used by #Group elements to create children
+Group._element_factories = {
+  "group": Group.new,
+  "text": Text.new,
+  "path": Path.new
+};
+
+# Canvas
+# ==============================================================================
+# Class for a canvas
+#
+var Canvas = {
+  # Place this canvas somewhere onto the object. Pass criterions for placement
+  # as a hash, eg:
+  #
+  #  my_canvas.addPlacement({
+  #    "texture": "EICAS.png",
+  #    "node": "PFD-Screen",
+  #    "parent": "Some parent name"
+  #  });
+  # 
+  # Note that we can choose whichever of the three filter criterions we use for
+  # matching the target object for our placement. If none of the three fields is
+  # given every texture of the model will be replaced.
+  addPlacement: func(vals)
+  {
+    var placement = _createNodeWithIndex(me.texture, "placement");
+    placement.setValues(vals);
+    return placement;
+  },
+  # Create a new group with the given name
+  #
+  # @param name Option name for the group
+  createGroup: func(name = nil)
+  {
+    return Group.new(me.texture, name);
+  },
+  # Set the background color
+  #
+  # @param color  Vector of 3 or 4 values in [0, 1]
+  setColorBackground: func _setColorNodes(me.color, arg)
+};
+
+# Create a new canvas. Pass parameters as hash, eg:
+#
+#  var my_canvas = canvas.new({
+#    "name": "PFD-Test",
+#    "size": [512, 512],
+#    "view": [768, 1024],
+#    "mipmapping": 1
+#  });
+var new = func(vals)
+{
+  var m = { parents: [Canvas] };
+
+  m.texture = _createNodeWithIndex
+  (
+    props.globals.getNode("canvas", 1),
+    "texture"
+  );
+  m.color = _createColorNodes(m.texture, "color-background");
+  m.texture.setValues(vals);
+
+  return m;
+};
+
+# Get the first existing canvas with the given name
+#
+# @param name Name of the canvas
+# @return #Canvas, if canvas with #name exists
+#         nil, otherwise
+var get = func(name)
+{
+  var node_canvas = nil;
+  if( isa(name, props.Node) )
+    node_canvas = name;
+  else if( typeof(name) == 'scalar' )
+  {
+    var canvas_root = props.globals.getNode("canvas");
+    if( canvas_root == nil )
+      return nil;
+
+    foreach(var c; canvas_root.getChildren("texture"))
+    {
+      if( c.getValue("name") == name )
+        node_canvas = c;
+    }
+  }
+  
+  if( node_canvas == nil )
+  {
+    debug.warn("Canvas not found: " ~ name);
+    return nil;
+  }
+  
+  return {
+    parents: [Canvas],
+    texture: node_canvas,
+    color: _createColorNodes(node_canvas, "color-background")
+  };
+};