+#define forward(v,id,args...) rb_respond_to((v), (id))?rb_funcall((v), (id), args):0
+
+VALUE convert_line(gfxline_t*line)
+{
+ int len = 0;
+ gfxline_t*l = line;
+ while(l) {l=l->next;len++;}
+
+ volatile VALUE array = rb_ary_new2(len);
+
+ int pos = 0;
+ l = line;
+ while(l) {
+ volatile VALUE e;
+ if(l->type == gfx_moveTo) {
+ e = rb_ary_new3(3, ID2SYM(id_move), Qfalse, Qfalse);
+ rb_ary_store(array, pos, e);
+ rb_ary_store(e, 1, rb_float_new(l->x));
+ rb_ary_store(e, 2, rb_float_new(l->y));
+ } else if(l->type == gfx_lineTo) {
+ e = rb_ary_new3(3, ID2SYM(id_line), Qfalse, Qfalse);
+ rb_ary_store(array, pos, e);
+ rb_ary_store(e, 1, rb_float_new(l->x));
+ rb_ary_store(e, 2, rb_float_new(l->y));
+ } else {
+ e = rb_ary_new3(5, ID2SYM(id_spline), Qfalse, Qfalse, Qfalse, Qfalse);
+ rb_ary_store(array, pos, e);
+ rb_ary_store(e, 1, rb_float_new(l->x));
+ rb_ary_store(e, 2, rb_float_new(l->y));
+ rb_ary_store(e, 3, rb_float_new(l->sx));
+ rb_ary_store(e, 4, rb_float_new(l->sy));
+ }
+ pos++;
+ l=l->next;
+ }
+ return array;
+}
+VALUE convert_color(gfxcolor_t*color)
+{
+ return rb_ary_new3(4, INT2FIX(color->a), INT2FIX(color->r), INT2FIX(color->g), INT2FIX(color->b));
+}
+VALUE convert_matrix(gfxmatrix_t*matrix)
+{
+ volatile VALUE array = rb_ary_new2(3);
+ volatile VALUE a = rb_ary_new2(2);
+ rb_ary_store(array, 0, a);
+ rb_ary_store(a, 0, rb_float_new(matrix->m00));
+ rb_ary_store(a, 1, rb_float_new(matrix->m01));
+ a = rb_ary_new2(2);
+ rb_ary_store(array, 1, a);
+ rb_ary_store(a, 0, rb_float_new(matrix->m10));
+ rb_ary_store(a, 1, rb_float_new(matrix->m11));
+ a = rb_ary_new2(2);
+ rb_ary_store(array, 2, a);
+ rb_ary_store(a, 0, rb_float_new(matrix->tx));
+ rb_ary_store(a, 1, rb_float_new(matrix->ty));
+ return array;
+}
+static VALUE font_is_cached(device_internal_t*i, gfxfont_t*font)
+{
+ return (VALUE)gfxfontlist_getuserdata(i->doc->fontlist, font->id);
+}
+static void cache_font(device_internal_t*i, gfxfont_t*font, VALUE v)
+{
+ i->doc->fontlist = gfxfontlist_addfont2(i->doc->fontlist, font, (void*)v);
+}
+static VALUE convert_font(gfxfont_t*font)
+{
+ volatile VALUE v2 = font_allocate(Font);
+ Get_Font(f, v2);
+ f->font = font;
+ f->glyph_array = rb_ary_new2(font->num_glyphs);
+
+ int t;
+ for(t=0;t<font->num_glyphs;t++) {
+ volatile VALUE a = glyph_allocate(Glyph);
+ rb_ary_store(f->glyph_array, t, a);
+ Get_Glyph(g, a);
+ g->font = f;
+ g->nr = t;
+ }
+ return v2;
+}
+#define HEAD \
+ device_internal_t*i = (device_internal_t*)dev->internal; \
+ VALUE v = i->v;
+int rb_setparameter(gfxdevice_t*dev, const char*key, const char*value)
+{
+ HEAD
+ volatile VALUE v_key = rb_tainted_str_new2(key);
+ volatile VALUE v_value = rb_tainted_str_new2(value);
+ VALUE ret = forward(v,id_setparameter,2,v_key,v_value);
+ return 0;
+}
+void rb_startpage(gfxdevice_t*dev, int width, int height)
+{
+ HEAD
+ VALUE ret = forward(v,id_startpage,2,INT2FIX(width),INT2FIX(height));
+}
+void rb_startclip(gfxdevice_t*dev, gfxline_t*line)
+{
+ HEAD
+ volatile VALUE v_line = convert_line(line);
+ VALUE ret = forward(v,id_startclip,1,v_line);
+}
+void rb_endclip(gfxdevice_t*dev)
+{
+ HEAD
+ VALUE ret = forward(v,id_endclip,0);
+}
+void rb_stroke(gfxdevice_t*dev, gfxline_t*line, gfxcoord_t width, gfxcolor_t*color, gfx_capType cap_style, gfx_joinType joint_style, gfxcoord_t miterLimit)
+{
+ HEAD
+
+ ID cap = 0;
+ if(cap_style == gfx_capButt) cap = id_butt;
+ else if(cap_style == gfx_capRound) cap = id_round;
+ else if(cap_style == gfx_capSquare) cap = id_square;
+
+ ID joint = 0;
+ if(joint_style == gfx_joinRound) joint = id_round;
+ else if(joint_style == gfx_joinMiter) joint = id_miter;
+ else if(joint_style == gfx_joinBevel) joint = id_bevel;
+
+ volatile VALUE v_line = convert_line(line);
+ volatile VALUE v_width = rb_float_new(width);
+ volatile VALUE v_color = convert_color(color);
+ volatile VALUE v_miter = rb_float_new(miterLimit);
+ forward(v, id_stroke, 6, v_line, v_width, v_color, ID2SYM(cap), ID2SYM(joint), v_miter);
+}
+void rb_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color)
+{
+ HEAD
+
+ volatile VALUE v_line = convert_line(line);
+ volatile VALUE v_color = convert_color(color);
+ forward(v, id_fill, 2, v_line, v_color);
+}
+void rb_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
+{
+ HEAD
+ volatile VALUE v_image = convert_image(i->doc, img);
+ volatile VALUE v_line = convert_line(line);
+ volatile VALUE v_matrix = convert_matrix(matrix);
+ forward(v, id_fillbitmap, 4, v_line, v_image, v_matrix, Qnil);
+ invalidate_image(v_image);
+}
+void rb_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
+{
+ HEAD
+ ID typeid = (type == gfxgradient_linear)? id_linear : id_radial;
+
+ volatile VALUE v_line = convert_line(line);
+ volatile VALUE v_matrix = convert_matrix(matrix);
+ volatile VALUE v_gradient = convert_gradient(gradient);
+ forward(v, id_fillgradient, 4, v_line, v_gradient, ID2SYM(typeid), v_matrix);
+}
+void rb_addfont(gfxdevice_t*dev, gfxfont_t*font)
+{
+ HEAD
+
+ volatile VALUE f = font_is_cached(i, font);
+ if(!f) {f=convert_font(font);cache_font(i,font,f);}
+
+ forward(v, id_addfont, 1, f);
+}
+void rb_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyphnr, gfxcolor_t*color, gfxmatrix_t*matrix)
+{
+ HEAD
+ volatile VALUE f = font_is_cached(i, font);
+ if(!f) {f=convert_font(font);cache_font(i,font,f);}
+
+ volatile VALUE v_color = convert_color(color);
+ volatile VALUE v_matrix = convert_matrix(matrix);
+ forward(v, id_drawchar, 4, f, INT2FIX(glyphnr), v_color, v_matrix);
+}
+void rb_drawlink(gfxdevice_t*dev, gfxline_t*line, const char*action)
+{
+ HEAD
+ volatile VALUE v_line = convert_line(line);
+ volatile VALUE v_action = rb_tainted_str_new2(action);
+ forward(v, id_drawlink, v_line, v_action);
+}
+void rb_endpage(gfxdevice_t*dev)
+{
+ HEAD
+ forward(v, id_endpage, 0);
+}
+gfxresult_t* rb_finish(gfxdevice_t*dev)
+{
+ HEAD
+ VALUE ret = forward(v, id_endpage, 0);
+ gfxresult_t*r = (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
+ r->internal = (void*)(ptroff_t)ret;
+ return r;
+}
+
+static VALUE page_render(VALUE cls, VALUE device)
+{
+ Check_Type(device, T_OBJECT);
+ Get_Page(page,cls)
+
+ gfxdevice_t dev;
+ device_internal_t i;
+ i.v = device;
+ i.doc = page->doc;
+
+ dev.internal = &i;
+ dev.setparameter = rb_setparameter;
+ dev.startpage = rb_startpage;
+ dev.startclip = rb_startclip;
+ dev.endclip = rb_endclip;
+ dev.stroke = rb_stroke;
+ dev.fill = rb_fill;
+ dev.fillbitmap = rb_fillbitmap;
+ dev.fillgradient = rb_fillgradient;
+ dev.addfont = rb_addfont;
+ dev.drawchar = rb_drawchar;
+ dev.drawlink = rb_drawlink;
+ dev.endpage = rb_endpage;
+ dev.finish = rb_finish;
+
+ dev.startpage(&dev, page->page->width, page->page->height);
+ page->page->render(page->page, &dev);
+ dev.endpage(&dev);
+
+ return cls;
+}
+
+// ---------------------- global functions ----------------------------------
+
+VALUE gfx_setparameter(VALUE module, VALUE _key, VALUE _value)
+{
+ Check_Type(_key, T_STRING);
+ Check_Type(_value, T_STRING);
+ const char*key = StringValuePtr(_key);
+ const char*value = StringValuePtr(_value);
+ pdfdriver->setparameter(pdfdriver, key, value);
+ swfdriver->setparameter(swfdriver, key, value);
+ imagedriver->setparameter(imagedriver, key, value);
+ return GFX;
+}
+