Howto:Extend the Canvas SVG module

From FlightGear wiki
Revision as of 18:35, 19 April 2016 by Hooray (talk | contribs) (→‎tag)
Jump to navigation Jump to search
This article is a stub. You can help the wiki by expanding it.

Objective

Document and demonstrate how the Canvas/Nasal SVG module can be extended to suport additional SVG features, by mapping SVG/XML markup to the corresponding Canvas primitives, such as mapping the <image> tag to a Canvas.Image.

Background

Test Code

Note  The following code is sufficiently self-contained, so that it can be put into a separate Nasal module, but also executed as a standalone snippet using the Nasal Console
var (width,height) = (320,160);
var svg_filename = "/Nasal/img.svg";
var title = 'SVGTest: '~svg_filename;


# create a new window, dimensions are WIDTH x HEIGHT, using the dialog decoration (i.e. titlebar)
var window = canvas.Window.new([width,height],"dialog")
 .set('title',title);

# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));

# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();

# change the background color 
myCanvas.set("background", "#ffaac0");

# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();

var svg_symbol = root.createChild('group');
canvas.parsesvg(svg_symbol, svg_filename);

svg_symbol.setTranslation(width/2,height/2);

#svg_symbol.setScale(0.2);
#svg_symbol.setRotation(radians)

Examples

<image> tag

Let's assume, we'd like to support raster images, e.g. SVG markup such as the following (for the sake of simplicity, this is simply saved under $FG_ROOT/Nasal/img.svg for testing purposes):

<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
  <image x="160" y="0" width="160" height="160"
     xlink:href="https://forum.flightgear.org/styles/flightgear/theme/images/logoF.png" />
</svg>

By default, the svg parser will bail out with a message saying Skipping unknown element image. Thus, we need to open $FG_ROOT/Nasal/canvas/svg.nas and navigate to the line where that error is triggered:

else
    {
      printlog("info", "Skipping unknown element '" ~ name ~ "'");
      skip = level;
      return;
    }

So, the next step is prepending a new conditional block that looks specifically for the new tag that we'd like to see supported, e.g.:

else if (name == "image")
    {
        printlog("alert", "image tag encountered (not yet implemented)");
    }

Or as a diff/patch:

diff --git a/Nasal/canvas/svg.nas b/Nasal/canvas/svg.nas
index d7fe570..c1b4a90 100644
--- a/Nasal/canvas/svg.nas
+++ b/Nasal/canvas/svg.nas
@@ -578,6 +578,10 @@ var parsesvg = func(group, path, options = nil)
       append(defs_stack, "defs");
       return;
     }
+    else if (name == "image") 
+    {
+       printlog("alert", "image tag encountered (not yet implemented)");
+    }
     else
     {
       printlog("info", "Skipping unknown element '" ~ name ~ "'");

Whenever the parser now sees an image tag, it will tell us so saying: parsesvg: image tag encountered (not yet implemented)

The next step is actually mapping the SVG markup to the corresponding Nasal/Canvas code.

However, for starters, we will keep things simple and add a fixed image from $FG_ROOT/Textures whenever an image tag is encountered, e.g. a splash screen texture.

So, the corresponding Nasal/Canvas magic to load a raster image from the base package looks basically like this:

# create an image child 
var child = root.createChild("image").setFile(URL);

There is another issue here, in that the svg parser is dealing with a stack of operations, so we cannot directly access the root element of the Canvas, but need to push primitives onto the stack, which are applied later on.

To learn how this works, it makes sense to look at existing mappings between SVG and Canvas primitives, such as mappings to Canvas.Text and Canvas.Path (add relevant code snippets below):

pushElement('group', attr['id']);

We also need to take into account that, as per the SVG spec, an image tag may not only refer to raster images, but also other vector graphics (SVG files), which means that we need to look at the file extension - i.e. only add a raster image primitive (Canvas.Image) if the extension is not .svg:

For now, this isn't too useful, as it will only load a fixed texture from the base package, so we need to change things to actually process the proper filename/URL, and other attributes:

debug.dump( attr );