Published 2006-02-28 09:50:42

Been having so much fun with D, that I thought I'd add a category to the blog, and write another article.

As usual, the best way to learn a language is to try and write something in it, so for a bit of fun I had a go at writing a SVG renderer (or at least a very simple one). In doing so, I got to see how D can be used to call external libraries, and explore some of the existing libraries and syntax that was available.

To render to the screen I decided to use GnomeCanvas, As I had played with it before in C, and from what I remember even tried writing bindings for PHP-GTK a long time ago. So I had some idea of how the API worked.

.. click on the more link for the full article ...


After stripping down the C example code included with the GnomeCanvas source, to just draw a simple rectangle, I started porting it to D.

Making the API usable

First on the tasks was to make the API available to D, This is done by copying and manipulating the header file into a extern(C) block,

For example:
GtkWidget * gnome_canvas_new_aa(void);
becomes
extern(C) 
{
GtkWidget* function() gnome_canvas_new_aa;
}

enabling you to call the function in D using

GtkWidget *canvas;
canvas = gnome_canvas_new_aa();
Since GtkWidget is defined in the DUI package, I had to add a few imports:
private import dui.DUI;
private import def.Types;
private import def.Constants;
private import lib.gtk;
DUI provides wrappers to a D style interface for GTK, however, to make things easier to start with, I used the native C methods to call gtk, and mixed them up a bit with the gtk object wrapper, so creating a new window became:
GtkWidget *app;
app = gtk_window_new (GtkWindowType.TOPLEVEL);
gtk_window_fullscreen( cast(GtkWindow*) app );

Compiling this little baby

The D compilation is quite simple:
/dmd/bin/dmd  -gc  -c -O -od. canvas.d 
-I/dmd/src/phobos:/usr/src/dyndui/src:/usr/src/dool/src/
However, when it came to linking I took me a while to understand why linking it to the gnomecanvas lib's caused the application to segfault on the first gtk call.
gcc canvas.o -o canvas    -L/usr/src/dool -ldool -L/usr/src/dyndui -ldui  
-lphobos -lstdc++ -ldl -pthread -L/usr/src/dool -ldool -lphobos
`pkg-config --libs libgnomecanvas-2.0`
What I failed to understand until I dug into the DUI code, was that libgtk was being dynamically loaded and linked by DUI. so linking it here caused the library to be doublely linked, and hence segfault.
The correct compilation line does not need the pkg-config, eg.

gcc canvas.o -o canvas    -L/usr/src/dool -ldool -L/usr/src/dyndui -ldui  
-lphobos -lstdc++ -ldl -pthread -L/usr/src/dool -ldool -lphobos

Dynamic Linking

The answer was to duplicate what was being done by DUI, and use the dui linker code. There where 2 parts to this
  • Create a list of symbols that are used
  • Load the dynamic library using dl()

Adding the symbol list is quite easy, first define an array of symbols to send to the linker.

Symbol[] GnomeCanvasLinks = [
{ "gnome_canvas_new_aa", cast(void**)& gnome_canvas_new_aa } ,
.....
];

Then define a constructor (and destructor) for the file:

static this()
{
gnomecanvas_Linker = new Linker( "libgnomecanvas-2.so" );
gnomecanvas_Linker.link( GnomeCanvasLinks );
}

static ~this()
{
delete gnomecanvas_Linker;
}

Then define a constructor (and destructor) for the file:

Using setters to create nice interfaces

One of the key components of canvas is drawing things, this is done using the gnome_canvas_item_new() call.

gnome_canvas_item_new(GnomeCanvasGroup* parent, 
GType type, gchar* first_arg_name,...)

gnome_canvas_item_new(root, gnome_cavnas_rect_get_type(),
"x1", 12.0,
"y1", 12.0, ........etc....

This is a function call that uses varargs, and sends a variety of types down the line to the constructor. Unfortunatly although D will generally autotypecast data to normal function calls, it is asking a bit much for it to guess what random data you may be sending to this varargs function.

To get around this, I eventually worked out that sending null as first_arg_name, and the first argument could be used to create the object.
eg.
this.item = gnome_canvas_item_new ( parent,type,null,null);

After that, using the gnome_canvas_item_set, and correctly casting the data to the correct type can be used to set the values:

gnome_canvas_item_set(this.item, cast(char*) "x1", (cast(double) 123.0);
Since D is a wonderfull hybrid of functional and object orientated language, I decided to try out an object to represent this.

class CanvasItem {
GnomeCanvasItem* item;
this(GnomeCanvasGroup* parent, GType type)
{
this.item = gnome_canvas_item_new ( parent,type,null,null);
}

Set's up the constructor wrapper around the CanvasItem.
now by overloading the set() method, I can send it various combinations of types to set the coordinates and text etc.

	void set(char[] k, char[] v)
{
gnome_canvas_item_set(this.item, cast(char *)k, cast(char *)v,null);
}
void set(char[] k, double v)
{
gnome_canvas_item_set(this.item, cast(char *)k, v,null);
}
void set(char[] k, int v)
{
gnome_canvas_item_set(this.item, cast(char *)k, v,null);
}

However, using this method I would still have to keep calling

canvas_item.set("x1",123.0);

and make sure it was sending the right type for each attribute, so I had a go at using D's setter syntax, by defining a method with the same name as the property you intend to make available, you can make it perform a set operation.

	void x1(double v) { this.set("x1", v); } 
void x2(double v) { this.set("x2", v); }

Meaning that you can now write:

canvas_item = new CanvasItem(parent);
canvas_item.x1 = 123;
canvas_item.y1 = 456;

This follows through to setting colours and fonts

	void font(char[] v) { this.set("font",  v); }
void fillColor(char[] v) { this.set("fill-color", v); }
}
I even got clever allowing fill_color_rgba to accept "#ffccff" string style values, rather than 0xffccff00, by overloading the setter to accept string types:
	void fill_color_rgba(double v) 
{
this.set("fill_color_rgba", cast(int) v);
}
void fill_color_rgba(char[] v)
{
if (v == "none") {
return;
}
int c=0;
int mult = 0x10000000;
for (int i = 1; i < 7; i++) {
int p = ifind(hexdigits, v[i]);
c += (p > -1) ? mult * p : 0;
mult = mult / 0x10;
}
c+=0xff;
this.set("fill_color_rgba", cast(int) c);
}

I'm sure given more time, that could be even cleaner :)

XML Libraries

One of the things that is so beautiful about D, is that the standard library, phobos, is so small and compact, which makes it very easy to learn, and quickly use.

However, it's not long until you start thinking about using a library outside of that. At present, locating these libraries is not 'easy', however, that doesnt mean it's difficult. an XML parser is a classic example.

XML parsing is not included in the core library (thankfully), so I started looking around the D wiki, and found a number of XML libraries listed there. All come with source, and are at various stages of development, (although most can parse and represent XML in some manner or other).

The libraries are also distributed as tarballs usually, often with little in the way of compilation or installation notes. (although given some common sense it's not too dificult to work out). It seems there is a slight gap in the community that could be easily filled to provide a standard installer (like pear), or just make them available as .deb's.

I also found that a number of the larger projects on dsource had written or adapted xml parsers, DOM representations and bundled them with the source of them. eg. mango and DUI(well dool acutally) both contain XML parsers.

For my tiny example program i settled on using the xml library that came with DUI/dool, as it was already installed and did not take much to learn. Unfortunatly I quickly found that it was a touch incomplete as it failed to parse XML comments. But for my simple test program, removing the comments from the SVG file was a simple solution.

to use the library you just have to import the namespace (or you could just use the full path in your code.)

private import dool.xml.xml;
XmlParser x = new XmlParser();
XmlDocument xmldoc = x.parse(testdata);
or
auto x  = new dool.xml.xml.XmlParser();
auto xmldoc = x.parse(testdata);
should work (well I havent tested the second, but it is a little more readable, as the import line ends up at the top of the file.

the xmldoc.children, than then be recursively looped through and used to call the CanvasItem method to add items to the canvas.

File IO

In the simple test example, I also read the XML data from a file, most of the tutorials for D used readLine, however I wanted to read the whole file into a string so after a bit of digging I wrote this.
		File f = new File( r"/tmp/data.svg", FileMode.In ); 
while (!f.eof()) {
testdata ~= f.readString( f.available());
}
almost as simple as file_get_contents(), and since D cleans up the File handle at the end of the method it's in, you dont really need to delete f to close the file handle. [slight correction here thanks to Regan Heath] the destructor will not be called unless f is defined as type auto, and there is another method read(), which can read the whole file in one go.
          char[] testdata  = cast(char[]) std.file.read( r"/tmp/data.svg"); 

String parsing

One of the key parts of parsing a text file (SVG) to create graphical elements, that require integers and floats is how easy it is to work with. D uses standard C calls to convert types, so reading the x and Y coordinates is very simple:
	CanvasItem ci = new CanvasItem(parent,gnome_canvas_text_get_type());
ci.x = atof(node.getAttribute("x").value);
ci.y = atof(node.getAttribute("y").value);
atof and atoi provide the conversion, and since D supports string based switch case, looping through the style component of the SVG file is quite simple. as shown here.
void setStyle(CanvasItem ci)
{
char[][] bits = split(this.currentStyle,";");
foreach(int i, char[] kv; bits) {
if (-1 == find(kv,":")) {
continue;
}
char[][] kv_ar = split(kv,":");
char [] k = kv_ar[0];
char [] v = kv_ar[1];
printf("set: %.*s=> %.*s\n", k,v);
switch(k) {
case "font-family": ci.font=v; break;
case "font-size": ci.size = atoi(replace(v,"px","")); break;
case "font-weight":
if (v == "bold") {
ci.weight = 800;
}
break;
case "fill": ci.fill_color_rgba = v; break;
default: break;
}
}
}
Well, my little SVG renderer can just render rectangles and text at present, but it's interesting to see how simple everything comes together, and how a language designed properly can really make a huge difference.







Mentioned By:
google.com : february (101 referals)
google.com : php atoi (96 referals)
www.planet-php.net : Planet PHP (53 referals)
google.com : atoi php (44 referals)
google.com : php varargs (31 referals)
www.digitalmars.com : Two little blog/tutorials posts. (21 referals)
planet.debian.org.hk : Debian HK : Debian @ Hong Kong (13 referals)
google.com : digital mars d (12 referals)
google.com : varargs php (12 referals)
google.com : atoi in php (10 referals)
google.com : december (10 referals)
google.com : digitalmars (10 referals)
www.digitalmars.com : Digital Mars - digitalmars.D.learn - Two little blog/tutorials posts. (8 referals)
google.com : php svg render (8 referals)
google.com : php svg renderer (8 referals)
google.com : svg renderer (7 referals)
google.com : (6 referals)
google.com : digitalmars d (6 referals)
dstress.kuehne.cn : dstress.kuehne.cn: links (6 referals)
www.phpn.org : More on Digitalmars D - Simple SVG render for GnomeCanvas (5 referals)

Add Your Comment

Follow us on