+#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;
+}
+static VALUE convert_gradient(gfxgradient_t*gradient)
+{
+ return Qnil; //TODO
+}
+#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, 2, v_line, v_action);
+}
+void rb_endpage(gfxdevice_t*dev)
+{
+ HEAD
+ forward(v, id_endpage, 0);
+}
+void gfxresult_rb_destroy(gfxresult_t*r)
+{
+ free(r);
+}
+gfxresult_t* rb_finish(gfxdevice_t*dev)
+{
+ HEAD
+ VALUE ret = forward(v, id_finish, 0);
+ gfxresult_t*r = (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
+ r->destroy = gfxresult_rb_destroy;
+ r->internal = (void*)(ptroff_t)ret;
+ return r;
+}
+
+#define make_device(dev, idoc, device) \
+ gfxdevice_t dev; \
+ device_internal_t i; \
+ i.v = device; \
+ i.doc = idoc; \
+ 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;
+
+static VALUE page_render(VALUE cls, VALUE device)
+{
+ Check_Type(device, T_OBJECT);
+ Get_Page(page,cls)
+
+ make_device(dev, page->doc, device);
+
+ dev.startpage(&dev, page->page->width, page->page->height);
+ page->page->render(page->page, &dev);
+ dev.endpage(&dev);
+
+ return cls;
+}
+
+static VALUE doc_render(VALUE cls, VALUE device, VALUE _range, VALUE filters)
+{
+ const char*range = 0;
+ if(!NIL_P(_range)) {
+ Check_Type(_range, T_STRING);
+ range = StringValuePtr(_range);
+ }
+ Get_Doc(doc,cls);
+
+ make_device(_dev, doc, device);
+ gfxdevice_t*dev = &_dev;
+
+ if(!NIL_P(filters)) {
+ if(TYPE(filters) != T_ARRAY)
+ rb_raise(rb_eArgError, "third argument of doc->render must be an array of symbols");
+
+ int len = RARRAY(filters)->len;
+ int t=0;
+ while(t<len) {
+ VALUE filter = RARRAY(filters)->ptr[t++];
+ Check_Type(filter, T_SYMBOL);
+ ID id = SYM2ID(filter);
+# define PARAM(x) VALUE x;if(t==len) rb_raise(rb_eArgError, "End of array while parsing arguments for filter %s", rb_id2name(id)); \
+ else x = RARRAY(filters)->ptr[t++];
+ if(id == id_remove_font_transforms) {
+ wrap_filter2(dev, remove_font_transforms);
+ } else if(id == id_maketransparent) {
+ PARAM(alpha);
+ wrap_filter(dev, maketransparent, FIX2INT(alpha));
+ } else {
+ rb_raise(rb_eArgError, "unknown filter %s", rb_id2name(id));
+ }
+ }
+ }
+
+ int pagenr;
+ for(pagenr=1;pagenr<=doc->doc->num_pages;pagenr++) {
+ if(is_in_range(pagenr, (char*)range)) {
+ gfxpage_t*page = doc->doc->getpage(doc->doc, pagenr);
+ dev->startpage(dev, page->width, page->height);
+ page->render(page, dev);
+ dev->endpage(dev);
+ page->destroy(page);
+ }
+ }
+
+
+ gfxresult_t*r = dev->finish(dev);
+ r->destroy(r);
+
+ return Qnil;
+}
+
+static VALUE doc_prepare(VALUE cls, VALUE device)
+{
+ Get_Doc(doc,cls);
+ make_device(dev, doc, device);
+ doc->doc->prepare(doc->doc, &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;
+}
+