/*
 * Lua interface for the cairo graphics library
 *

    Copyright 2007  Niels Martin Hansen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

    Contact:
    E-mail: <jiifurusu@gmail.com>
    IRC: jfs in #aegisub on irc.rizon.net

 */

#include "cairo_wrap.h"
#include "../lua51/src/lauxlib.h"

#include <stdint.h>

#ifdef WIN32
#include <windows.h>
#endif


// Macros to help implementing callables

#define CALLABLE_IMPL(C,N) int C ## ::lua_ ## N (lua_State *L)
#define CALLABLE_NOTIMPL(C,N) CALLABLE_IMPL(C,N) { lua_pushliteral(L, "Method " # N " in class " # C " is not implemented"); lua_error(L); return 0; }
#define CALLABLE_REG(name) AddCallable(lua_ ## name, # name)
#define CALLABLE_REG2(func,name) AddCallable(lua_ ## func, # name)


// Maps from strings to various cairo enum types

static const char *status_names_list[] = {
	"success",
	"no_memory",
	"invalid_restore", "invalid_pop_group", "no_current_point",
	"invalid_matrix", "invalid_status", "null_pointer",
	"invalid_string", "invalid_path_data",
	"read_error", "write_error",
	"surface_type_mismatch", "pattern_type_mismatch",
	"invalid_content", "invalid_format", "invalid_visual",
	"file_not_found",
	"invalid_dash", "invalid_dsc_comment", "invalid_index",
	"clip_not_representable",
	0 };
static const char *fill_rule_list[] = {"winding", "even_odd", 0};
static const char *line_cap_list[] = {"butt", "round", "square", 0};
static const char *line_join_list[] = {"miter", "round", "bevel", 0};
static const char *antialias_types_list[] = {"default", "none", "gray", "subpixel", 0};
static const char *subpixel_order_list[] ={"default", "rgb", "bgr", "vrgb", "vbgr", 0};
static const char *hint_style_list[] = {"default", "none", "slight", "medium", "full", 0};
static const char *hint_metrics_list[] = {"default", "on", "off", 0};
static const char *content_types_list[] = {"c", "a", "ca", 0};
static const char *operators_list[] = {
	"clear",
	"source", "over", "in", "out", "atop",
	"dest", "dest_over", "dest_in", "dest_out", "dest_atop",
	"xor", "add", "saturate",
	0 };
static const char *font_slant_list[] = {"", "italic", "oblique", 0};
static const char *font_weight_list[] = {"", "bold", 0};
static const char *image_formats_list[] = {"argb32", "rgb24", "a8", "a1", "rgb16_565", 0};
static const char *pattern_extend_list[] = {"none", "repeat", "reflect", "pad", 0};
static const char *pattern_filter_list[] = {"fast", "good", "best", "nearest", "bilinear", "gaussian", 0};
static const char *pattern_type_list[] = {"solid", "surface", "linear", "radial", 0};


// Misc. helper functions

static void font_extents_to_lua(lua_State *L, cairo_font_extents_t &extents)
{
	lua_newtable(L);
	lua_pushnumber(L, extents.ascent);
	lua_setfield(L, -2, "ascent");
	lua_pushnumber(L, extents.descent);
	lua_setfield(L, -2, "descent");
	lua_pushnumber(L, extents.height);
	lua_setfield(L, -2, "height");
	lua_pushnumber(L, extents.max_x_advance);
	lua_setfield(L, -2, "max_x_advance");
	lua_pushnumber(L, extents.max_y_advance);
	lua_setfield(L, -2, "max_y_advance");
}

static void text_extents_to_lua(lua_State *L, cairo_text_extents_t &extents)
{
	lua_newtable(L);
	lua_pushnumber(L, extents.x_bearing);
	lua_setfield(L, -2, "x_bearing");
	lua_pushnumber(L, extents.y_bearing);
	lua_setfield(L, -2, "y_bearing");
	lua_pushnumber(L, extents.width);
	lua_setfield(L, -2, "width");
	lua_pushnumber(L, extents.height);
	lua_setfield(L, -2, "height");
	lua_pushnumber(L, extents.x_advance);
	lua_setfield(L, -2, "x_advance");
	lua_pushnumber(L, extents.y_advance);
	lua_setfield(L, -2, "y_advance");
}


// Context (cairo_t)

CALLABLE_IMPL(LuaCairoContext, reference)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoContext *newctx = new LuaCairoContext(L, ctx->context);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, status)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_status_t st = cairo_status(ctx->context);
	lua_pushstring(L, status_names_list[st]);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, save)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_save(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, restore)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_restore(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_target)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_surface_t *surf = cairo_get_target(ctx->context);
	new LuaCairoSurface(L, surf);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, push_group)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_push_group(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, push_group_with_content)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_content_t ct = (cairo_content_t)luaL_checkoption(L, 1, "ca", content_types_list);
	cairo_push_group_with_content(ctx->context, ct);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, pop_group)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_pattern_t *pat = cairo_pop_group(ctx->context);
	LuaCairoPattern *patobj = new LuaCairoPattern(L, pat);
	cairo_pattern_destroy(pat);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, pop_group_to_source)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_pop_group_to_source(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_group_target)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_surface_t *surf = cairo_get_group_target(ctx->context);
	new LuaCairoSurface(L, surf);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_source_rgb)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double r = luaL_checknumber(L, 1);
	double g = luaL_checknumber(L, 2);
	double b = luaL_checknumber(L, 3);
	cairo_set_source_rgb(ctx->context, r, g, b);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, set_source_rgba)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double r = luaL_checknumber(L, 1);
	double g = luaL_checknumber(L, 2);
	double b = luaL_checknumber(L, 3);
	double a = luaL_checknumber(L, 4);
	cairo_set_source_rgba(ctx->context, r, g, b, a);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, set_source)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoPattern *pat = LuaCairoPattern::GetObjPointer(L, 1);
	cairo_set_source(ctx->context, pat->GetPattern());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, set_source_surface)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoSurface *surf = LuaCairoSurface::GetObjPointer(L, 1);
	double surfx = luaL_checknumber(L, 2);
	double surfy = luaL_checknumber(L, 3);
	cairo_set_source_surface(ctx->context, surf->GetSurface(), surfx, surfy);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_source)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_pattern_t *pat = cairo_get_source(ctx->context);
	// The pattern is owned by cairo here, so creating the Lua object will create the needed reference from Lua
	new LuaCairoPattern(L, pat);
	// Meaning that calling pattern_destroy here would be wrong
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_antialias)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_antialias_t aa = (cairo_antialias_t)luaL_checkoption(L, 1, NULL, antialias_types_list);
	cairo_set_antialias(ctx->context, aa);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_antialias)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_antialias_t aa = cairo_get_antialias(ctx->context);
	lua_pushstring(L, antialias_types_list[aa]);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_dash)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	
	if (lua_isnoneornil(L, 1)) {
		// left out parameters, disable dashing
		cairo_set_dash(ctx->context, NULL, 0, 0);
		return 0;

	} else if (lua_istable(L, 1)) {
		// dashing pattern in table, complex case
		double offset = luaL_optnumber(L, 2, 0);
		size_t num_dashes = lua_objlen(L, 1);
		double *dashes = new double[num_dashes];
		size_t i = 0;
		lua_pushnil(L);
		while (lua_next(L, 1)) {
			if (lua_isnumber(L, -1)) {
				dashes[i] = lua_tonumber(L, -1);
			} else {
				dashes[i] = 0;
			}
			lua_pop(L, 1);
		}
		cairo_set_dash(ctx->context, dashes, (int)num_dashes, offset);
		delete[] dashes;
		return 0;

	} else if (lua_isnumber(L, 1)) {
		// single dash, alternating on/off
		double dash = luaL_checknumber(L, 1);
		double offset = luaL_optnumber(L, 2, 0);
		if (dash > 0) {
			cairo_set_dash(ctx->context, &dash, 1, offset);
		} else {
			// ok number was invalid, disable dashing instead of putting context into error state
			cairo_set_dash(ctx->context, NULL, 0, 0);
		}
		return 0;

	} else {
		luaL_error(L, "First argument to set_dash must be nil, table or number, was %s", luaL_typename(L, 1));
		return 0;
	}
}

CALLABLE_IMPL(LuaCairoContext, get_dash_count)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	int dash_count = cairo_get_dash_count(ctx->context);
	lua_pushinteger(L, dash_count);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, get_dash)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	int dash_count = cairo_get_dash_count(ctx->context);

	if (dash_count <= 0) {
		lua_newtable(L);
		lua_pushnumber(L, 0);
		return 2;
	}

	double *dashes = new double[dash_count];
	double offset;
	cairo_get_dash(ctx->context, dashes, &offset);
	lua_newtable(L);
	for (int i = 0; i < dash_count; i++) {
		lua_pushnumber(L, dashes[i]);
		lua_rawseti(L, -2, i+1);
	}
	delete[] dashes;
	lua_pushnumber(L, offset);
	return 2;
}

CALLABLE_IMPL(LuaCairoContext, set_fill_rule)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_fill_rule_t fr = (cairo_fill_rule_t)luaL_checkoption(L, 1, NULL, fill_rule_list);
	cairo_set_fill_rule(ctx->context, fr);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_fill_rule)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_fill_rule_t fr = cairo_get_fill_rule(ctx->context);
	lua_pushstring(L, fill_rule_list[fr]);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_line_cap)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_line_cap_t lc = (cairo_line_cap_t)luaL_checkoption(L, 1, NULL, line_cap_list);
	cairo_set_line_cap(ctx->context, lc);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_line_cap)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_line_cap_t lc = cairo_get_line_cap(ctx->context);
	lua_pushstring(L, line_cap_list[lc]);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_line_join)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_line_join_t lj = (cairo_line_join_t)luaL_checkoption(L, 1, NULL, line_join_list);
	cairo_set_line_join(ctx->context, lj);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_line_join)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_line_join_t lj = cairo_get_line_join(ctx->context);
	lua_pushstring(L, line_join_list[lj]);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_line_width)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double w = luaL_checknumber(L, 1);
	cairo_set_line_width(ctx->context, w);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_line_width)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double w = cairo_get_line_width(ctx->context);
	lua_pushnumber(L, w);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_miter_limit)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double l = luaL_checknumber(L, 1);
	cairo_set_miter_limit(ctx->context, l);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_miter_limit)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double l = cairo_get_miter_limit(ctx->context);
	lua_pushnumber(L, l);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_operator)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_operator_t op = (cairo_operator_t)luaL_checkoption(L, 1, NULL, operators_list);
	cairo_set_operator(ctx->context, op);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_operator)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_operator_t op = cairo_get_operator(ctx->context);
	lua_pushstring(L, operators_list[op]);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_tolerance)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double tol = luaL_checknumber(L, 1);
	cairo_set_tolerance(ctx->context, tol);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_tolerance)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double tol = cairo_get_tolerance(ctx->context);
	lua_pushnumber(L, tol);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, clip)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_clip(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, clip_preserve)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_clip_preserve(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, clip_extents)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x1, y1, x2, y2;
	cairo_clip_extents(ctx->context, &x1, &y1, &x2, &y2);
	lua_pushnumber(L, x1);
	lua_pushnumber(L, y1);
	lua_pushnumber(L, x2);
	lua_pushnumber(L, y2);
	return 4;
}

CALLABLE_IMPL(LuaCairoContext, reset_clip)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_reset_clip(ctx->context);
	return 0;
}

CALLABLE_NOTIMPL(LuaCairoContext, copy_clip_rectangle_list)

CALLABLE_IMPL(LuaCairoContext, fill)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_fill(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, fill_preserve)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_fill_preserve(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, fill_extents)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x1, y1, x2, y2;
	cairo_fill_extents(ctx->context, &x1, &y1, &x2, &y2);
	lua_pushnumber(L, x1);
	lua_pushnumber(L, y1);
	lua_pushnumber(L, x2);
	lua_pushnumber(L, y2);
	return 4;
}

CALLABLE_IMPL(LuaCairoContext, in_fill)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	lua_pushboolean(L, cairo_in_fill(ctx->context, x, y));
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, mask)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoPattern *pat = LuaCairoPattern::GetObjPointer(L, 1);
	cairo_mask(ctx->context, pat->GetPattern());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, mask_surface)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoSurface *surf = LuaCairoSurface::GetObjPointer(L, 1);
	double surfx = luaL_checknumber(L, 2);
	double surfy = luaL_checknumber(L, 3);
	cairo_mask_surface(ctx->context, surf->GetSurface(), surfx, surfy);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, paint)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_paint(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, paint_with_alpha)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double a = luaL_checknumber(L, 1);
	cairo_paint_with_alpha(ctx->context, a);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, stroke)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_stroke(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, stroke_preserve)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_stroke_preserve(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, stroke_extents)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x1, y1, x2, y2;
	cairo_stroke_extents(ctx->context, &x1, &y1, &x2, &y2);
	lua_pushnumber(L, x1);
	lua_pushnumber(L, y1);
	lua_pushnumber(L, x2);
	lua_pushnumber(L, y2);
	return 4;
}

CALLABLE_IMPL(LuaCairoContext, in_stroke)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	lua_pushboolean(L, cairo_in_stroke(ctx->context, x, y));
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, copy_page)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_copy_page(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, show_page)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_show_page(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, copy_path)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_path_t *path = cairo_copy_path(ctx->context);
	new LuaCairoPath(L, path);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, copy_path_flat)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_path_t *path = cairo_copy_path_flat(ctx->context);
	new LuaCairoPath(L, path);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, append_path)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoPath *path = LuaCairoPath::GetObjPointer(L, 1);
	cairo_append_path(ctx->context, path->GetPath());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_current_point)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x, y;
	cairo_get_current_point(ctx->context, &x, &y);
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	return 2;
}

CALLABLE_IMPL(LuaCairoContext, new_path)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_new_path(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, new_sub_path)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_new_sub_path(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, close_path)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_close_path(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, arc)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double xc = luaL_checknumber(L, 1);
	double yc = luaL_checknumber(L, 2);
	double radius = luaL_checknumber(L, 3);
	double angle1 = luaL_checknumber(L, 4);
	double angle2 = luaL_checknumber(L, 5);
	cairo_arc(ctx->context, xc, yc, radius, angle1, angle2);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, arc_negative)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double xc = luaL_checknumber(L, 1);
	double yc = luaL_checknumber(L, 2);
	double radius = luaL_checknumber(L, 3);
	double angle1 = luaL_checknumber(L, 4);
	double angle2 = luaL_checknumber(L, 5);
	cairo_arc_negative(ctx->context, xc, yc, radius, angle1, angle2);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, curve_to)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x1 = luaL_checknumber(L, 1);
	double y1 = luaL_checknumber(L, 2);
	double x2 = luaL_checknumber(L, 3);
	double y2 = luaL_checknumber(L, 4);
	double x3 = luaL_checknumber(L, 5);
	double y3 = luaL_checknumber(L, 6);
	cairo_curve_to(ctx->context, x1, y1, x2, y2, x3, y3);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, line_to)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_line_to(ctx->context, x, y);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, move_to)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_move_to(ctx->context, x, y);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, rectangle)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x1 = luaL_checknumber(L, 1);
	double y1 = luaL_checknumber(L, 2);
	double x2 = luaL_checknumber(L, 3);
	double y2 = luaL_checknumber(L, 4);
	cairo_rectangle(ctx->context, x1, y1, x2, y2);
	return 0;
}

CALLABLE_NOTIMPL(LuaCairoContext, glyph_path)

CALLABLE_IMPL(LuaCairoContext, text_path)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	const char *utf8 = luaL_checkstring(L, 1);
	cairo_text_path(ctx->context, utf8);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, rel_curve_to)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x1 = luaL_checknumber(L, 1);
	double y1 = luaL_checknumber(L, 2);
	double x2 = luaL_checknumber(L, 3);
	double y2 = luaL_checknumber(L, 4);
	double x3 = luaL_checknumber(L, 5);
	double y3 = luaL_checknumber(L, 6);
	cairo_rel_curve_to(ctx->context, x1, y1, x2, y2, x3, y3);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, rel_line_to)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_rel_line_to(ctx->context, x, y);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, rel_move_to)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_rel_move_to(ctx->context, x, y);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, translate)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double tx = luaL_checknumber(L, 1);
	double ty = luaL_checknumber(L, 2);
	cairo_translate(ctx->context, tx, ty);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, scale)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double sx = luaL_checknumber(L, 1);
	double sy = luaL_checknumber(L, 2);
	cairo_scale(ctx->context, sx, sy);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, rotate)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double rads = luaL_checknumber(L, 1);
	cairo_rotate(ctx->context, rads);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, transform)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = LuaCairoMatrix::GetObjPointer(L, 1);
	cairo_transform(ctx->context, mat->GetMatrix());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, set_matrix)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = LuaCairoMatrix::GetObjPointer(L, 1);
	cairo_set_matrix(ctx->context, mat->GetMatrix());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_matrix)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_matrix_t mat;
	cairo_get_matrix(ctx->context, &mat);
	new LuaCairoMatrix(L, &mat);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, identity_matrix)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_identity_matrix(ctx->context);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, user_to_device)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_user_to_device(ctx->context, &x, &y);
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	return 2;
}

CALLABLE_IMPL(LuaCairoContext, user_to_device_distance)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_user_to_device_distance(ctx->context, &x, &y);
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	return 2;
}

CALLABLE_IMPL(LuaCairoContext, device_to_user)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_device_to_user(ctx->context,& x, &y);
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	return 2;
}

CALLABLE_IMPL(LuaCairoContext, device_to_user_distance)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_device_to_user_distance(ctx->context, &x, &y);
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	return 2;

}

CALLABLE_IMPL(LuaCairoContext, select_font_face)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	const char *family = luaL_checkstring(L, 1);
	cairo_font_slant_t slant = (cairo_font_slant_t)luaL_checkoption(L, 2, "", font_slant_list);
	cairo_font_weight_t weight = (cairo_font_weight_t)luaL_checkoption(L, 3, "", font_weight_list);
	cairo_select_font_face(ctx->context, family, slant, weight);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, set_font_size)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	double sz = luaL_checknumber(L, 1);
	cairo_set_font_size(ctx->context, sz);
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, set_font_matrix)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = LuaCairoMatrix::GetObjPointer(L, 1);
	cairo_set_font_matrix(ctx->context, mat->GetMatrix());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_font_matrix)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = new LuaCairoMatrix(L);
	cairo_get_font_matrix(ctx->context, mat->GetMatrix());
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_font_options)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoFontOptions *fo = LuaCairoFontOptions::GetObjPointer(L, 1);
	cairo_set_font_options(ctx->context, fo->GetFontOptions());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_font_options)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoFontOptions *fo = new LuaCairoFontOptions(L);
	cairo_get_font_options(ctx->context, fo->GetFontOptions());
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_font_face)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoFontFace *face = LuaCairoFontFace::GetObjPointer(L, 1);
	cairo_set_font_face(ctx->context, face->GetFontFace());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_font_face)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_font_face_t *face = cairo_get_font_face(ctx->context);
	new LuaCairoFontFace(L, face);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, set_scaled_font)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoScaledFont *sf = LuaCairoScaledFont::GetObjPointer(L, 1);
	cairo_set_scaled_font(ctx->context, sf->GetScaledFont());
	return 0;
}

CALLABLE_IMPL(LuaCairoContext, get_scaled_font)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_scaled_font_t *sf = cairo_get_scaled_font(ctx->context);
	new LuaCairoScaledFont(L, sf);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, show_text)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	const char *utf8 = luaL_checkstring(L, 1);
	cairo_show_text(ctx->context, utf8);
	return 0;
}

CALLABLE_NOTIMPL(LuaCairoContext, show_glyphs)

CALLABLE_IMPL(LuaCairoContext, font_extents)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	cairo_font_extents_t extents;
	cairo_font_extents(ctx->context, &extents);
	font_extents_to_lua(L, extents);
	return 1;
}

CALLABLE_IMPL(LuaCairoContext, text_extents)
{
	LuaCairoContext *ctx = GetObjPointer(L, lua_upvalueindex(1));
	const char *utf8 = luaL_checkstring(L, 1);
	cairo_text_extents_t extents;
	cairo_text_extents(ctx->context, utf8, &extents);
	text_extents_to_lua(L, extents);
	return 1;
}

CALLABLE_NOTIMPL(LuaCairoContext, glyph_extents)

const char *LuaCairoContext::GetTypeName()
{
	return "context";
}

LuaCairoContext::LuaCairoContext(lua_State *L, cairo_t *_context) :
	LuaCairoBase(L)
{
	CALLABLE_REG(reference);
	CALLABLE_REG(status);
	CALLABLE_REG(save);
	CALLABLE_REG(restore);
	CALLABLE_REG(get_target);
	CALLABLE_REG(push_group);
	CALLABLE_REG(push_group_with_content);
	CALLABLE_REG(pop_group);
	CALLABLE_REG(pop_group_to_source);
	CALLABLE_REG(get_group_target);
	CALLABLE_REG(set_source_rgb);
	CALLABLE_REG(set_source_rgba);
	CALLABLE_REG(set_source);
	CALLABLE_REG(set_source_surface);
	CALLABLE_REG(get_source);
	CALLABLE_REG(set_antialias);
	CALLABLE_REG(get_antialias);
	CALLABLE_REG(set_dash);
	CALLABLE_REG(get_dash_count);
	CALLABLE_REG(get_dash);
	CALLABLE_REG(set_fill_rule);
	CALLABLE_REG(get_fill_rule);
	CALLABLE_REG(set_line_cap);
	CALLABLE_REG(get_line_cap);
	CALLABLE_REG(set_line_join);
	CALLABLE_REG(get_line_join);
	CALLABLE_REG(set_line_width);
	CALLABLE_REG(get_line_width);
	CALLABLE_REG(set_miter_limit);
	CALLABLE_REG(get_miter_limit);
	CALLABLE_REG(set_operator);
	CALLABLE_REG(get_operator);
	CALLABLE_REG(set_tolerance);
	CALLABLE_REG(get_tolerance);
	CALLABLE_REG(clip);
	CALLABLE_REG(clip_preserve);
	CALLABLE_REG(clip_extents);
	CALLABLE_REG(reset_clip);
	CALLABLE_REG(copy_clip_rectangle_list);
	CALLABLE_REG(fill);
	CALLABLE_REG(fill_preserve);
	CALLABLE_REG(fill_extents);
	CALLABLE_REG(in_fill);
	CALLABLE_REG(mask);
	CALLABLE_REG(mask_surface);
	CALLABLE_REG(paint);
	CALLABLE_REG(paint_with_alpha);
	CALLABLE_REG(stroke);
	CALLABLE_REG(stroke_preserve);
	CALLABLE_REG(stroke_extents);
	CALLABLE_REG(in_stroke);
	CALLABLE_REG(copy_page);
	CALLABLE_REG(show_page);
	CALLABLE_REG(copy_path);
	CALLABLE_REG(copy_path_flat);
	CALLABLE_REG(append_path);
	CALLABLE_REG(get_current_point);
	CALLABLE_REG(new_path);
	CALLABLE_REG(new_sub_path);
	CALLABLE_REG(close_path);
	CALLABLE_REG(arc);
	CALLABLE_REG(arc_negative);
	CALLABLE_REG(curve_to);
	CALLABLE_REG(line_to);
	CALLABLE_REG(move_to);
	CALLABLE_REG(rectangle);
	CALLABLE_REG(glyph_path);
	CALLABLE_REG(text_path);
	CALLABLE_REG(rel_curve_to);
	CALLABLE_REG(rel_line_to);
	CALLABLE_REG(rel_move_to);
	CALLABLE_REG(translate);
	CALLABLE_REG(scale);
	CALLABLE_REG(rotate);
	CALLABLE_REG(transform);
	CALLABLE_REG(set_matrix);
	CALLABLE_REG(get_matrix);
	CALLABLE_REG(identity_matrix);
	CALLABLE_REG(user_to_device);
	CALLABLE_REG(user_to_device_distance);
	CALLABLE_REG(device_to_user);
	CALLABLE_REG(device_to_user_distance);
	CALLABLE_REG(select_font_face);
	CALLABLE_REG(set_font_size);
	CALLABLE_REG(set_font_matrix);
	CALLABLE_REG(get_font_matrix);
	CALLABLE_REG(set_font_options);
	CALLABLE_REG(get_font_options);
	CALLABLE_REG(set_font_face);
	CALLABLE_REG(get_font_face);
	CALLABLE_REG(set_scaled_font);
	CALLABLE_REG(get_scaled_font);
	CALLABLE_REG(show_text);
	CALLABLE_REG(show_glyphs);
	CALLABLE_REG(font_extents);
	CALLABLE_REG(text_extents);
	CALLABLE_REG(glyph_extents);

	cairo_reference(_context);
	context = _context;
}

LuaCairoContext::~LuaCairoContext()
{
	cairo_destroy(context);
}


// Surface (cairo_surface_t)

CALLABLE_IMPL(LuaCairoSurface, create_similar)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_content_t ct = (cairo_content_t)luaL_checkoption(L, 1, NULL, content_types_list);
	int width = luaL_checkint(L, 2);
	int height = luaL_checkint(L, 3);
	cairo_surface_t *nsurf = cairo_surface_create_similar(surf->surface, ct, width, height);
	new LuaCairoSurface(L, nsurf);
	cairo_surface_destroy(nsurf);
	return 1;
}

CALLABLE_NOTIMPL(LuaCairoSurface, reference)

CALLABLE_IMPL(LuaCairoSurface, status)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_status_t st = cairo_surface_status(surf->surface);
	lua_pushstring(L, status_names_list[st]);
	return 1;
}

CALLABLE_IMPL(LuaCairoSurface, create_context)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));

	cairo_t *context = cairo_create(surf->surface);

	if (!context || cairo_status(context) != CAIRO_STATUS_SUCCESS) {
		lua_pushliteral(L, "Failed creating cairo context");
		lua_error(L);
		return 0;
	}

	LuaCairoContext *ctxojb = new LuaCairoContext(L, context);
	cairo_destroy(context);
	return 1;
}

CALLABLE_IMPL(LuaCairoSurface, finish)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_surface_finish(surf->surface);
	return 0;
}

CALLABLE_IMPL(LuaCairoSurface, flush)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_surface_flush(surf->surface);
	return 0;
}

CALLABLE_IMPL(LuaCairoSurface, get_font_options)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoFontOptions *fo = new LuaCairoFontOptions(L);
	cairo_surface_get_font_options(surf->surface, fo->GetFontOptions());
	return 1;
}

CALLABLE_IMPL(LuaCairoSurface, get_content)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_content_t ct = cairo_surface_get_content(surf->surface);
	lua_pushstring(L, content_types_list[ct]);
	return 1;
}

CALLABLE_IMPL(LuaCairoSurface, mark_dirty)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_surface_mark_dirty(surf->surface);
	return 0;
}

CALLABLE_IMPL(LuaCairoSurface, mark_dirty_rectangle)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	int x = luaL_checkint(L, 1);
	int y = luaL_checkint(L, 2);
	int width = luaL_checkint(L, 3);
	int height = luaL_checkint(L, 4);
	cairo_surface_mark_dirty_rectangle(surf->surface, x, y, width, height);
	return 0;
}

CALLABLE_IMPL(LuaCairoSurface, set_device_offset)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	double xofs = luaL_checknumber(L, 1);
	double yofs = luaL_checknumber(L, 2);
	cairo_surface_set_device_offset(surf->surface, xofs, yofs);
	return 0;
}

CALLABLE_IMPL(LuaCairoSurface, get_device_offset)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	double xofs, yofs;
	cairo_surface_get_device_offset(surf->surface, &xofs, &yofs);
	lua_pushnumber(L, xofs);
	lua_pushnumber(L, yofs);
	return 2;
}

CALLABLE_IMPL(LuaCairoSurface, set_fallback_resolution)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	double xppi = luaL_checknumber(L, 1);
	double yppi = luaL_checknumber(L, 2);
	cairo_surface_set_device_offset(surf->surface, xppi, yppi);
	return 0;
}

CALLABLE_NOTIMPL(LuaCairoSurface, get_type)

CALLABLE_IMPL(LuaCairoSurface, image_get_format)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	if (cairo_surface_get_type(surf->GetSurface()) != CAIRO_SURFACE_TYPE_IMAGE) {
		lua_pushliteral(L, "Surface is not an image surface");
		lua_error(L);
		return 0;
	}

	switch (cairo_image_surface_get_format(surf->GetSurface())) {
		case CAIRO_FORMAT_ARGB32:
			lua_pushliteral(L, "argb32");
			return 1;
		case CAIRO_FORMAT_RGB24:
			lua_pushliteral(L, "rgb24");
			return 1;
		case CAIRO_FORMAT_A8:
			lua_pushliteral(L, "a8");
			return 1;
		case CAIRO_FORMAT_A1:
			lua_pushliteral(L, "a1");
			return 1;
		default:
			lua_pushnil(L);
			return 1;
	}
}

CALLABLE_IMPL(LuaCairoSurface, image_get_width)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	if (cairo_surface_get_type(surf->GetSurface()) != CAIRO_SURFACE_TYPE_IMAGE) {
		lua_pushliteral(L, "Surface is not an image surface");
		lua_error(L);
		return 0;
	}

	lua_pushinteger(L, cairo_image_surface_get_width(surf->GetSurface()));
	return 1;
}

CALLABLE_IMPL(LuaCairoSurface, image_get_height)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	if (cairo_surface_get_type(surf->GetSurface()) != CAIRO_SURFACE_TYPE_IMAGE) {
		lua_pushliteral(L, "Surface is not an image surface");
		lua_error(L);
		return 0;
	}

	lua_pushinteger(L, cairo_image_surface_get_height(surf->GetSurface()));
	return 1;
}

CALLABLE_NOTIMPL(LuaCairoSurface, image_set_pixel)

CALLABLE_IMPL(LuaCairoSurface, image_get_pixel)
{
	LuaCairoSurface *surf = GetObjPointer(L, lua_upvalueindex(1));
	int x = luaL_checkint(L, 1);
	int y = luaL_checkint(L, 2);
	if (cairo_surface_get_type(surf->surface) != CAIRO_SURFACE_TYPE_IMAGE) {
		lua_pushliteral(L, "Surface is not an image surface");
		lua_error(L);
		return 0;
	}

	// Assume the surface is already flush()ed
	int width = cairo_image_surface_get_width(surf->surface);
	int height = cairo_image_surface_get_height(surf->surface);
	int stride = cairo_image_surface_get_stride(surf->surface);
	cairo_format_t format = cairo_image_surface_get_format(surf->surface);
	unsigned char *data = cairo_image_surface_get_data(surf->surface);

	switch (format) {
		case CAIRO_FORMAT_ARGB32: {
			uint32_t pixel = *(uint32_t*)(data + y*stride + x * 4);
			lua_pushinteger(L, (pixel & 0xff000000)>>24); // alpha
			lua_pushinteger(L, (pixel & 0x00ff0000)>>16); // red
			lua_pushinteger(L, (pixel & 0x0000ff00)>>8); // green
			lua_pushinteger(L, pixel & 0x000000ff); // blue
			return 4; }

		case CAIRO_FORMAT_RGB24: {
			uint32_t pixel = *(uint32_t*)(data + y*stride + x * 4);
			lua_pushinteger(L, (pixel & 0x00ff0000)>>16); // red
			lua_pushinteger(L, (pixel & 0x0000ff00)>>8); // green
			lua_pushinteger(L, pixel & 0x000000ff); // blue
			return 3; }

		case CAIRO_FORMAT_A8:
			lua_pushinteger(L, data[y*stride+x]);
			return 1;

		default:
			lua_pushliteral(L, "Unhandled pixel format in image surface get pixel");
			lua_error(L);
			return 0;
	}
}

CALLABLE_IMPL(LuaCairoSurface, image_surface_create)
{
	// Swap arguments a bit and make a default value for the format
	int width = luaL_checkint(L, 1);
	int height = luaL_checkint(L, 2);
	cairo_format_t format = (cairo_format_t)luaL_checkoption(L, 3, "argb32", image_formats_list);

	// Create surface from parameters
	cairo_surface_t *surf = cairo_image_surface_create(format, width, height);

	// Check that the surface was successfully created
	if (!surf || cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS)
		return 0;

	// Create wrapping Lua object
	LuaCairoSurface *surfobj = new LuaCairoSurface(L, surf);
	// Lua object takes its own reference to surface, so release ours
	cairo_surface_destroy(surf);
	// Return surface object
	return 1;
}

const char *LuaCairoSurface::GetTypeName()
{
	return "surface";
}

LuaCairoSurface::LuaCairoSurface(lua_State *L, cairo_surface_t *_surface) :
	LuaCairoBase(L)
{
	cairo_surface_reference(_surface);
	surface = _surface;

	cairo_surface_type_t st = cairo_surface_get_type(surface);

	CALLABLE_REG(create_similar);
	CALLABLE_REG(reference);
	CALLABLE_REG(status);
	CALLABLE_REG(create_context);
	CALLABLE_REG(finish);
	CALLABLE_REG(flush);
	CALLABLE_REG(get_font_options);
	CALLABLE_REG(get_content);
	CALLABLE_REG(mark_dirty);
	CALLABLE_REG(mark_dirty_rectangle);
	CALLABLE_REG(set_device_offset);
	CALLABLE_REG(get_device_offset);
	CALLABLE_REG(set_fallback_resolution);
	CALLABLE_REG(get_type);

	if (st == CAIRO_SURFACE_TYPE_IMAGE) {
		CALLABLE_REG2(image_get_format, get_format);
		CALLABLE_REG2(image_get_width, get_width);
		CALLABLE_REG2(image_get_height, get_height);
		CALLABLE_REG2(image_set_pixel, set_pixel);
		CALLABLE_REG2(image_get_pixel, get_pixel);
	}
}

LuaCairoSurface::~LuaCairoSurface()
{
	cairo_surface_destroy(surface);
}


// Font face (cairo_font_face_t)

CALLABLE_NOTIMPL(LuaCairoFontFace, create_scaled_font)
CALLABLE_NOTIMPL(LuaCairoFontFace, reference)

CALLABLE_IMPL(LuaCairoFontFace, status)
{
	LuaCairoFontFace *face = GetObjPointer(L, lua_upvalueindex(1));
	cairo_status_t st = cairo_font_face_status(face->font_face);
	lua_pushstring(L, status_names_list[st]);
	return 1;
}

CALLABLE_NOTIMPL(LuaCairoFontFace, get_type)

const char *LuaCairoFontFace::GetTypeName()
{
	return "font_face";
}

LuaCairoFontFace::LuaCairoFontFace(lua_State *L, cairo_font_face_t *_font_face) :
	LuaCairoBase(L)
{
	CALLABLE_REG(create_scaled_font);
	CALLABLE_REG(reference);
	CALLABLE_REG(status);
	CALLABLE_REG(get_type);

	cairo_font_face_reference(_font_face);
	font_face = _font_face;
}

LuaCairoFontFace::~LuaCairoFontFace()
{
	cairo_font_face_destroy(font_face);
}


// Scaled font (cairo_scaled_font_t)

CALLABLE_NOTIMPL(LuaCairoScaledFont, reference)

CALLABLE_IMPL(LuaCairoScaledFont, status)
{
	LuaCairoScaledFont*sf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_status_t st = cairo_scaled_font_status(sf->scaled_font);
	lua_pushstring(L, status_names_list[st]);
	return 1;
}

CALLABLE_IMPL(LuaCairoScaledFont, extents)
{
	LuaCairoScaledFont *scf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_font_extents_t extents;
	cairo_scaled_font_extents(scf->scaled_font, &extents);
	font_extents_to_lua(L, extents);
	return 1;
}

CALLABLE_IMPL(LuaCairoScaledFont, text_extents)
{
	LuaCairoScaledFont *scf = GetObjPointer(L, lua_upvalueindex(1));
	const char *utf8 = luaL_checkstring(L, 1);
	cairo_text_extents_t extents;
	cairo_scaled_font_text_extents(scf->scaled_font, utf8, &extents);
	text_extents_to_lua(L, extents);
	return 1;
}

CALLABLE_NOTIMPL(LuaCairoScaledFont, glyph_extents)

CALLABLE_IMPL(LuaCairoScaledFont, get_font_face)
{
	LuaCairoScaledFont *scf = GetObjPointer(L, lua_upvalueindex(1));
	cairo_font_face_t *face = cairo_scaled_font_get_font_face(scf->scaled_font);
	new LuaCairoFontFace(L, face);
	return 1;
}

CALLABLE_IMPL(LuaCairoScaledFont, get_font_options)
{
	LuaCairoScaledFont *scf = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoFontOptions *opt = new LuaCairoFontOptions(L);
	cairo_scaled_font_get_font_options(scf->scaled_font, opt->GetFontOptions());
	return 1;
}

CALLABLE_IMPL(LuaCairoScaledFont, get_font_matrix)
{
	LuaCairoScaledFont *scf = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = new LuaCairoMatrix(L);
	cairo_scaled_font_get_font_matrix(scf->scaled_font, mat->GetMatrix());
	return 1;
}

CALLABLE_IMPL(LuaCairoScaledFont, get_ctm)
{
	LuaCairoScaledFont *scf = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = new LuaCairoMatrix(L);
	cairo_scaled_font_get_ctm(scf->scaled_font, mat->GetMatrix());
	return 1;
}

CALLABLE_NOTIMPL(LuaCairoScaledFont, get_type)

const char *LuaCairoScaledFont::GetTypeName()
{
	return "scaled_font";
}

LuaCairoScaledFont::LuaCairoScaledFont(lua_State *L, cairo_scaled_font_t *_scaled_font) :
	LuaCairoBase(L)
{
	CALLABLE_REG(reference);
	CALLABLE_REG(status);
	CALLABLE_REG(extents);
	CALLABLE_REG(text_extents);
	CALLABLE_REG(glyph_extents);
	CALLABLE_REG(get_font_face);
	CALLABLE_REG(get_font_options);
	CALLABLE_REG(get_font_matrix);
	CALLABLE_REG(get_ctm);
	CALLABLE_REG(get_type);

	cairo_scaled_font_reference(_scaled_font);
	scaled_font = _scaled_font;
}

LuaCairoScaledFont::~LuaCairoScaledFont()
{
	cairo_scaled_font_destroy(scaled_font);
}


// Font options (cairo_font_options_t)

CALLABLE_IMPL(LuaCairoFontOptions, copy)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_font_options_t *nfo = cairo_font_options_copy(fo->font_options);
	new LuaCairoFontOptions(L, nfo);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, status)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_status_t st = cairo_font_options_status(fo->font_options);
	lua_pushstring(L, status_names_list[st]);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, merge)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoFontOptions *other = GetObjPointer(L, 1);
	cairo_font_options_merge(fo->font_options, other->font_options);
	return 0;
}

CALLABLE_IMPL(LuaCairoFontOptions, hash)
{
	char hash[sizeof(unsigned long)*2+1]; // hex + null
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	unsigned long lhash = cairo_font_options_hash(fo->font_options);
	sprintf(hash, "%0*x", sizeof(unsigned long)*2, lhash);
	lua_pushstring(L, hash);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, equal)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoFontOptions *other = GetObjPointer(L, 1);
	cairo_bool_t eq = cairo_font_options_equal(fo->font_options, other->font_options);
	lua_pushboolean(L, eq);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, set_antialias)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_antialias_t aa = (cairo_antialias_t)luaL_checkoption(L, 1, NULL, antialias_types_list);
	cairo_font_options_set_antialias(fo->font_options, aa);
	return 0;
}

CALLABLE_IMPL(LuaCairoFontOptions, get_antialias)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_antialias_t aa = cairo_font_options_get_antialias(fo->font_options);
	lua_pushstring(L, antialias_types_list[aa]);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, set_subpixel_order)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_subpixel_order_t spo = (cairo_subpixel_order_t)luaL_checkoption(L, 1, NULL, subpixel_order_list);
	cairo_font_options_set_subpixel_order(fo->font_options, spo);
	return 0;
}

CALLABLE_IMPL(LuaCairoFontOptions, get_subpixel_order)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_subpixel_order_t spo = cairo_font_options_get_subpixel_order(fo->font_options);
	lua_pushstring(L, subpixel_order_list[spo]);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, set_hint_style)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_hint_style_t hs = (cairo_hint_style_t)luaL_checkoption(L, 1, NULL, hint_style_list);
	cairo_font_options_set_hint_style(fo->font_options, hs);
	return 0;
}

CALLABLE_IMPL(LuaCairoFontOptions, get_hint_style)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_hint_style_t hs = cairo_font_options_get_hint_style(fo->font_options);
	lua_pushstring(L, hint_style_list[hs]);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, set_hint_metrics)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_hint_metrics_t hm = (cairo_hint_metrics_t)luaL_checkoption(L, 1, NULL, hint_metrics_list);
	cairo_font_options_set_hint_metrics(fo->font_options, hm);
	return 0;
}

CALLABLE_IMPL(LuaCairoFontOptions, get_hint_metrics)
{
	LuaCairoFontOptions *fo = GetObjPointer(L, lua_upvalueindex(1));
	cairo_hint_metrics_t hm = cairo_font_options_get_hint_metrics(fo->font_options);
	lua_pushstring(L, hint_metrics_list[hm]);
	return 1;
}

CALLABLE_IMPL(LuaCairoFontOptions, create)
{
	new LuaCairoFontOptions(L);
	return 1;
}

void LuaCairoFontOptions::RegFontOptionsCallables()
{
	CALLABLE_REG(copy);
	CALLABLE_REG(status);
	CALLABLE_REG(merge);
	CALLABLE_REG(hash);
	CALLABLE_REG(equal);
	CALLABLE_REG(set_antialias);
	CALLABLE_REG(get_antialias);
	CALLABLE_REG(set_subpixel_order);
	CALLABLE_REG(get_subpixel_order);
	CALLABLE_REG(set_hint_style);
	CALLABLE_REG(get_hint_style);
	CALLABLE_REG(set_hint_metrics);
	CALLABLE_REG(get_hint_metrics);
}

const char *LuaCairoFontOptions::GetTypeName()
{
	return "font_options";
}

LuaCairoFontOptions::LuaCairoFontOptions(lua_State *L) :
	LuaCairoBase(L)
{
	RegFontOptionsCallables();

	owned = true;
	font_options = cairo_font_options_create();
}

LuaCairoFontOptions::LuaCairoFontOptions(lua_State *L, cairo_font_options_t *_font_options) :
	LuaCairoBase(L)
{
	RegFontOptionsCallables();

	owned = false;
	font_options = _font_options;
}

LuaCairoFontOptions::~LuaCairoFontOptions()
{
	if (owned)
		cairo_font_options_destroy(font_options);
}


// Matrix (cairo_matrix_t)

CALLABLE_IMPL(LuaCairoMatrix, init)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double xx = luaL_checknumber(L, 1);
	double yx = luaL_checknumber(L, 2);
	double xy = luaL_checknumber(L, 3);
	double yy = luaL_checknumber(L, 4);
	double x0 = luaL_checknumber(L, 5);
	double y0 = luaL_checknumber(L, 6);
	cairo_matrix_init(&mat->matrix, xx, yx, xy, yy, x0, y0);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, init_identity)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	cairo_matrix_init_identity(&mat->matrix);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, init_translate)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double tx = luaL_checknumber(L, 1);
	double ty = luaL_checknumber(L, 2);
	cairo_matrix_init_translate(&mat->matrix, tx, ty);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, init_scale)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double sx = luaL_checknumber(L, 1);
	double sy = luaL_checknumber(L, 2);
	cairo_matrix_init_scale(&mat->matrix, sx, sy);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, init_rotate)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double rads = luaL_checknumber(L, 1);
	cairo_matrix_init_rotate(&mat->matrix, rads);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, translate)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double tx = luaL_checknumber(L, 1);
	double ty = luaL_checknumber(L, 2);
	cairo_matrix_translate(&mat->matrix, tx, ty);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, scale)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double sx = luaL_checknumber(L, 1);
	double sy = luaL_checknumber(L, 2);
	cairo_matrix_scale(&mat->matrix, sx, sy);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, rotate)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double rads = luaL_checknumber(L, 1);
	cairo_matrix_rotate(&mat->matrix, rads);
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, invert)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	cairo_status_t res = cairo_matrix_invert(&mat->matrix);
	if (res == CAIRO_STATUS_SUCCESS)
		lua_pushboolean(L, 1);
	else
		lua_pushboolean(L, 0);
	return 1;
}

CALLABLE_IMPL(LuaCairoMatrix, multiply)
{
	LuaCairoMatrix *mat1 = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat2 = (LuaCairoMatrix*)GetObjPointer(L, 1);
	cairo_matrix_t res;
	cairo_matrix_multiply(&res, &mat1->matrix, &mat2->matrix);
	new LuaCairoMatrix(L, &res);
	return 1;
}

CALLABLE_IMPL(LuaCairoMatrix, transform_distance)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double dx = luaL_checknumber(L, 1);
	double dy = luaL_checknumber(L, 2);
	cairo_matrix_transform_distance(&mat->matrix, &dx, &dy);
	lua_pushnumber(L, dx);
	lua_pushnumber(L, dy);
	return 2;
}

CALLABLE_IMPL(LuaCairoMatrix, transform_point)
{
	LuaCairoMatrix *mat = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	double x = luaL_checknumber(L, 1);
	double y = luaL_checknumber(L, 2);
	cairo_matrix_transform_distance(&mat->matrix, &x, &y);
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	return 2;
}

CALLABLE_IMPL(LuaCairoMatrix, op_add)
{
	if (lua_isuserdata(L, 1) && lua_isuserdata(L, 2)) {
		// matrix + matrix (pointwise)
		LuaCairoMatrix *mat1 = (LuaCairoMatrix*)GetObjPointer(L, 1);
		LuaCairoMatrix *mat2 = (LuaCairoMatrix*)GetObjPointer(L, 2);
		LuaCairoMatrix *matR = new LuaCairoMatrix(L);
		cairo_matrix_t &a = mat1->matrix, &b = mat2->matrix, &res = matR->matrix;
		res.xx = a.xx + b.xx; res.yx = a.yx + b.yx;
		res.xy = a.xy + b.xy; res.yy = a.yy + b.yy;
		res.x0 = a.x0 + b.x0; res.y0 = a.y0 + b.y0;
		return 1;

	} else if (lua_isuserdata(L, 1) && lua_isnumber(L, 2)) {
		// matrix + number
		LuaCairoMatrix *matI = (LuaCairoMatrix*)GetObjPointer(L, 1);
		double num = luaL_checknumber(L, 2);
		LuaCairoMatrix *matR = new LuaCairoMatrix(L);
		cairo_matrix_t &I = matI->matrix, &R = matR->matrix;
		R.xx = num + I.xx; R.yx = num + I.yx;
		R.xy = num + I.xy; R.yy = num + I.yy;
		R.x0 = num + I.x0; R.y0 = num + I.y0;
		return 1;

	} else {
		lua_pushliteral(L, "Unsupported addition operation on matrix");
		lua_error(L);
		return 0;
	}
}

CALLABLE_IMPL(LuaCairoMatrix, op_sub)
{
	if (lua_isuserdata(L, 1) && lua_isuserdata(L, 2)) {
		// matrix - matrix (pointwise)
		LuaCairoMatrix *mat1 = (LuaCairoMatrix*)GetObjPointer(L, 1);
		LuaCairoMatrix *mat2 = (LuaCairoMatrix*)GetObjPointer(L, 2);
		LuaCairoMatrix *matR = new LuaCairoMatrix(L);
		cairo_matrix_t &a = mat1->matrix, &b = mat2->matrix, &res = matR->matrix;
		res.xx = a.xx - b.xx; res.yx = a.yx - b.yx;
		res.xy = a.xy - b.xy; res.yy = a.yy - b.yy;
		res.x0 = a.x0 - b.x0; res.y0 = a.y0 - b.y0;
		return 1;

	} else if (lua_isuserdata(L, 1) && lua_isnumber(L, 2)) {
		// matrix - number
		LuaCairoMatrix *matI = (LuaCairoMatrix*)GetObjPointer(L, 1);
		double num = luaL_checknumber(L, 2);
		LuaCairoMatrix *matR = new LuaCairoMatrix(L);
		cairo_matrix_t &I = matI->matrix, &R = matR->matrix;
		R.xx = I.xx - num; R.yx = I.yx - num;
		R.xy = I.xy - num; R.yy = I.yy - num;
		R.x0 = I.x0 - num; R.y0 = I.y0 - num;
		return 1;

	} else {
		lua_pushliteral(L, "Unsupported subtraction operation on matrix");
		lua_error(L);
		return 0;
	}
}

CALLABLE_IMPL(LuaCairoMatrix, op_mul)
{
	if (lua_isuserdata(L, 1) && lua_isuserdata(L, 2)) {
		// matrix * matrix (matrix multiplication)
		LuaCairoMatrix *mat1 = (LuaCairoMatrix*)GetObjPointer(L, 1);
		LuaCairoMatrix *mat2 = (LuaCairoMatrix*)GetObjPointer(L, 2);
		LuaCairoMatrix *matR = new LuaCairoMatrix(L);
		cairo_matrix_multiply(&matR->matrix, &mat1->matrix, &mat2->matrix);
		return 1;

	} else if ((lua_isuserdata(L, 1) && lua_isnumber(L, 2)) || (lua_isnumber(L, 1) && lua_isuserdata(L, 2))) {
		// matrix * number or number * matrix
		LuaCairoMatrix *matI;
		double num;
		if (lua_isuserdata(L, 1)) {
			matI = (LuaCairoMatrix*)GetObjPointer(L, 1);
			num = luaL_checknumber(L, 2);
		} else {
			num = luaL_checknumber(L, 1);
			matI = (LuaCairoMatrix*)GetObjPointer(L, 2);
		}
		LuaCairoMatrix *matR = new LuaCairoMatrix(L);
		cairo_matrix_t &I = matI->matrix, &R = matR->matrix;
		R.xx = num * I.xx; R.yx = num * I.yx;
		R.xy = num * I.xy; R.yy = num * I.yy;
		R.x0 = num * I.x0; R.y0 = num * I.y0;
		return 1;

	} else {
		lua_pushliteral(L, "Unsupported multiplication operation on matrix");
		lua_error(L);
		return 0;
	}
}

CALLABLE_IMPL(LuaCairoMatrix, op_div)
{
	if (lua_isuserdata(L, 1) && lua_isnumber(L, 2)) {
		// matrix / number = matrix * (1/number)
		LuaCairoMatrix *matI = (LuaCairoMatrix*)GetObjPointer(L, 1);
		double num = luaL_checknumber(L, 2);
		LuaCairoMatrix *matR = new LuaCairoMatrix(L);
		cairo_matrix_t &I = matI->matrix, &R = matR->matrix;
		R.xx = I.xx / num; R.yx = I.yx / num;
		R.xy = I.xy / num; R.yy = I.yy / num;
		R.x0 = I.x0 / num; R.y0 = I.y0 / num;
		return 1;

	} else if (lua_isnumber(L, 1) && lua_isuserdata(L, 2)) {
		// number / matrix = number * inv(matrix)
		LuaCairoMatrix *matI = (LuaCairoMatrix*)GetObjPointer(L, 1);
		double num = luaL_checknumber(L, 2);
		LuaCairoMatrix *matR = new LuaCairoMatrix(L, &matI->matrix);
		cairo_matrix_t &I = matI->matrix, &R = matR->matrix;
		if (!cairo_matrix_invert(&R)) {
			// inversion failed, result is nil
			lua_pushnil(L);
			return 1;
		}
		R.xx = R.xx * num; R.yx = R.yx * num;
		R.xy = R.xy * num; R.yy = R.yy * num;
		R.x0 = R.x0 * num; R.y0 = R.y0 * num;
		return 1;

	} else {
		lua_pushliteral(L, "Unsupported division operation on matrix");
		lua_error(L);
		return 0;
	}
}

CALLABLE_IMPL(LuaCairoMatrix, op_unm)
{
	LuaCairoMatrix *matI = (LuaCairoMatrix*)GetObjPointer(L, 1);
	LuaCairoMatrix *matR = new LuaCairoMatrix(L);
	cairo_matrix_t &I = matI->matrix, &R = matR->matrix;
	R.xx = -I.xx; R.yx = -I.yx;
	R.xy = -I.xy; R.yy = -I.yy;
	R.x0 = -I.x0; R.y0 = -I.y0;
	return 0;
}

CALLABLE_IMPL(LuaCairoMatrix, op_eq)
{
	LuaCairoMatrix *mat1 = (LuaCairoMatrix*)GetObjPointer(L, 1);
	LuaCairoMatrix *mat2 = (LuaCairoMatrix*)GetObjPointer(L, 2);
	cairo_matrix_t &a = mat1->matrix, &b = mat2->matrix;
	bool res =
		a.xx==b.xx && a.yx==b.yx &&
		a.xy==b.xy && a.yy==b.yy &&
		a.x0==b.x0 && a.y0==b.y0;
	lua_pushboolean(L, (int)res);
	return 1;
}

CALLABLE_IMPL(LuaCairoMatrix, copy)
{
	LuaCairoMatrix *org = (LuaCairoMatrix*)GetObjPointer(L, lua_upvalueindex(1));
	new LuaCairoMatrix(L, &org->matrix);
	return 1;
}

CALLABLE_IMPL(LuaCairoMatrix, create)
{
	new LuaCairoMatrix(L);
	return 1;
}

void LuaCairoMatrix::RegMatrixCallables(lua_State *L)
{
	CALLABLE_REG(init);
	CALLABLE_REG(init_identity);
	CALLABLE_REG(init_translate);
	CALLABLE_REG(init_scale);
	CALLABLE_REG(init_rotate);
	CALLABLE_REG(translate);
	CALLABLE_REG(scale);
	CALLABLE_REG(rotate);
	CALLABLE_REG(invert);
	CALLABLE_REG(multiply);
	CALLABLE_REG(transform_distance);
	CALLABLE_REG(transform_point);
	CALLABLE_REG(copy);
}

int LuaCairoMatrix::internal_lua_index(lua_State *L)
{
	if (lua_type(L, 2) == LUA_TSTRING) {
		const char *field = lua_tostring(L, 2);
		if (strcmp(field, "xx") == 0) {
			lua_pushnumber(L, matrix.xx);
			return 1;
		} else if (strcmp(field, "yx") == 0) {
			lua_pushnumber(L, matrix.yx);
			return 1;
		} else if (strcmp(field, "xy") == 0) {
			lua_pushnumber(L, matrix.xy);
			return 1;
		} else if (strcmp(field, "yy") == 0) {
			lua_pushnumber(L, matrix.yy);
			return 1;
		} else if (strcmp(field, "x0") == 0) {
			lua_pushnumber(L, matrix.x0);
			return 1;
		} else if (strcmp(field, "y0") == 0) {
			lua_pushnumber(L, matrix.y0);
			return 1;
		}
	}

	return LuaCairoBase::internal_lua_index(L);
}

int LuaCairoMatrix::internal_lua_newindex(lua_State *L)
{
	if (lua_type(L, 2) == LUA_TSTRING) {
		const char *field = lua_tostring(L, 2);
		if (strcmp(field, "xx") == 0) {
			matrix.xx = luaL_checknumber(L, 3);
			return 0;
		} else if (strcmp(field, "yx") == 0) {
			matrix.yx = luaL_checknumber(L, 3);
			return 0;
		} else if (strcmp(field, "xy") == 0) {
			matrix.xy = luaL_checknumber(L, 3);
			return 0;
		} else if (strcmp(field, "yy") == 0) {
			matrix.yy = luaL_checknumber(L, 3);
			return 0;
		} else if (strcmp(field, "x0") == 0) {
			matrix.x0 = luaL_checknumber(L, 3);
			return 0;
		} else if (strcmp(field, "y0") == 0) {
			matrix.y0 = luaL_checknumber(L, 3);
			return 0;
		}
	}

	return LuaCairoBase::internal_lua_newindex(L);
}

const char *LuaCairoMatrix::GetTypeName()
{
	return "matrix";
}

void LuaCairoMatrix::CreateMetaTable(lua_State *L)
{
	LuaCairoBase::CreateMetaTable(L);

	lua_pushcclosure(L, lua_op_add, 0);
	lua_setfield(L, -2, "__add");
	lua_pushcclosure(L, lua_op_sub, 0);
	lua_setfield(L, -2, "__sub");
	lua_pushcclosure(L, lua_op_mul, 0);
	lua_setfield(L, -2, "__mul");
	lua_pushcclosure(L, lua_op_div, 0);
	lua_setfield(L, -2, "__div");
	lua_pushcclosure(L, lua_op_unm, 0);
	lua_setfield(L, -2, "__unm");
	lua_pushcclosure(L, lua_op_eq, 0);
	lua_setfield(L, -2, "__eq");
}

LuaCairoMatrix::LuaCairoMatrix(lua_State *L) :
	LuaCairoBase(L)
{
	RegMatrixCallables(L);

	cairo_matrix_init_identity(&matrix);
}

LuaCairoMatrix::LuaCairoMatrix(lua_State *L, const cairo_matrix_t *_matrix) :
	LuaCairoBase(L)
{
	RegMatrixCallables(L);

	memcpy(&matrix, _matrix, sizeof(matrix));
}

LuaCairoMatrix::~LuaCairoMatrix()
{
	// Automatic memory management here - nothing to free
}

cairo_matrix_t *LuaCairoMatrix::GetMatrix()
{
	return &matrix;
}


// Path (cairo_path_t)

static void path_element_to_lua(cairo_path_data_t *path, lua_State *L)
{
	lua_newtable(L);
	switch (path[0].header.type) {
		case CAIRO_PATH_MOVE_TO:
			lua_pushliteral(L, "move_to");
			lua_setfield(L, -2, "type");
			lua_pushnumber(L, path[1].point.x);
			lua_setfield(L, -2, "x");
			lua_pushnumber(L, path[1].point.y);
			lua_setfield(L, -2, "y");
			break;

		case CAIRO_PATH_LINE_TO:
			lua_pushliteral(L, "line_to");
			lua_setfield(L, -2, "type");
			lua_pushnumber(L, path[1].point.x);
			lua_setfield(L, -2, "x");
			lua_pushnumber(L, path[1].point.y);
			lua_setfield(L, -2, "y");
			break;

		case CAIRO_PATH_CURVE_TO:
			lua_pushliteral(L, "curve_to");
			lua_setfield(L, -2, "type");
			lua_pushnumber(L, path[1].point.x);
			lua_setfield(L, -2, "x0");
			lua_pushnumber(L, path[1].point.y);
			lua_setfield(L, -2, "y0");
			lua_pushnumber(L, path[2].point.x);
			lua_setfield(L, -2, "x1");
			lua_pushnumber(L, path[2].point.y);
			lua_setfield(L, -2, "y1");
			lua_pushnumber(L, path[3].point.x);
			lua_setfield(L, -2, "x2");
			lua_pushnumber(L, path[3].point.y);
			lua_setfield(L, -2, "y2");
			break;

		case CAIRO_PATH_CLOSE_PATH:
			lua_pushliteral(L, "close");
			lua_setfield(L, -2, "type");
			break;

		default:
			lua_pushliteral(L, "unknown");
			lua_setfield(L, -2, "type");
			break;
	}
}

static void read_lua_path_element(lua_State *L, cairo_path_data_t *path)
{
	lua_getfield(L, -1, "type");
	if (!lua_isstring(L, -1)) {
		luaL_error(L, "Invalid or missing 'type' field in path element table");
		return;
	}
	const char *type = lua_tostring(L, -1);

	if (strcmp(type, "move_to") == 0) {
		path[0].header.length = 2;
		path[0].header.type = CAIRO_PATH_MOVE_TO;
		lua_getfield(L, -2, "x");
		lua_getfield(L, -3, "y");
		if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2)) {
			luaL_error(L, "Invalid 'x' or 'y' field in path element table with type 'move_to'");
			return;
		}
		path[1].point.x = lua_tonumber(L, -2);
		path[1].point.y = lua_tonumber(L, -1);
		lua_pop(L, 3);
		return;

	} else if (strcmp(type, "line_to") == 0) {
		path[0].header.length = 2;
		path[0].header.type = CAIRO_PATH_LINE_TO;
		lua_getfield(L, -2, "x");
		lua_getfield(L, -3, "y");
		if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2)) {
			luaL_error(L, "Invalid 'x' or 'y' field in path element table with type 'line_to'");
			return;
		}
		path[1].point.x = lua_tonumber(L, -2);
		path[1].point.y = lua_tonumber(L, -1);
		lua_pop(L, 3);
		return;

	} else if (strcmp(type, "curve_to") == 0) {
		path[0].header.length = 4;
		path[0].header.type = CAIRO_PATH_CURVE_TO;
		lua_getfield(L, -2, "x0");
		lua_getfield(L, -3, "y0");
		if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2)) {
			luaL_error(L, "Invalid 'x0' or 'y0' field in path element table with type 'line_to'");
			return;
		}
		path[1].point.x = lua_tonumber(L, -2);
		path[1].point.y = lua_tonumber(L, -1);
		lua_getfield(L, -4, "x1");
		lua_getfield(L, -5, "y1");
		if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2)) {
			luaL_error(L, "Invalid 'x1' or 'y1' field in path element table with type 'line_to'");
			return;
		}
		path[2].point.x = lua_tonumber(L, -2);
		path[2].point.y = lua_tonumber(L, -1);
		lua_getfield(L, -2, "x2");
		lua_getfield(L, -3, "y2");
		if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2)) {
			luaL_error(L, "Invalid 'x2' or 'y2' field in path element table with type 'line_to'");
			return;
		}
		path[3].point.x = lua_tonumber(L, -2);
		path[3].point.y = lua_tonumber(L, -1);
		lua_pop(L, 7);
		return;

	} else if (strcmp(type, "close") == 0) {
		path[0].header.length = 1;
		path[0].header.type = CAIRO_PATH_CLOSE_PATH;
		lua_pop(L, 1);
		return;

	} else {
		luaL_error(L, "Invalid 'type' field in path element table, '%s'", type);
	}
}

CALLABLE_NOTIMPL(LuaCairoPath, clear)
CALLABLE_NOTIMPL(LuaCairoPath, move_to)
CALLABLE_NOTIMPL(LuaCairoPath, line_to)
CALLABLE_NOTIMPL(LuaCairoPath, curve_to)
CALLABLE_NOTIMPL(LuaCairoPath, close)

CALLABLE_IMPL(LuaCairoPath, map)
{
	LuaCairoPath *path = GetObjPointer(L, lua_upvalueindex(1));
	if (!lua_isfunction(L, 1)) {
		luaL_error(L, "First argument to path.map_coords must be a function, is %s", luaL_typename(L, 1));
		return 0;
	}

	// Function should be p->p
	cairo_path_t *p = path->path;
	if (!p->num_data || !p->data) return 0;

	// Prepare a new path for building
	path->path = (cairo_path_t*)malloc(sizeof(cairo_path_t));
	cairo_path_t *np = path->path;
	np->num_data = 0;
	np->status = CAIRO_STATUS_SUCCESS;
	np->data = 0;

	cairo_path_data_t *pd = p->data;
	int outi = 0;
	for (int i = 0; i < p->num_data; ) {
		lua_pushvalue(L, 1);
		path_element_to_lua(pd, L);
		lua_call(L, 1, 1);

		path->EnsureSpaceFor(4); // dumb but simple, ensures there's always enough space for even the longest segments
		read_lua_path_element(L, np->data+outi);
		np->num_data += np->data[outi].header.length;
		outi += np->data[outi].header.length;
		
		i += pd->header.length;
		pd += pd->header.length;
	}

	if (path->cairo_owns_memory) {
		cairo_path_destroy(p);
		path->cairo_owns_memory = false;
	} else {
		free(p->data);
		free(p);
	}

	// Now just r should be left on top of stack
	return 1;}

CALLABLE_IMPL(LuaCairoPath, map_coords)
{
	LuaCairoPath *path = GetObjPointer(L, lua_upvalueindex(1));
	if (!lua_isfunction(L, 1)) {
		luaL_error(L, "First argument to path.map_coords must be a function, is %s", luaL_typename(L, 1));
		return 0;
	}

	// Function should be (x,y)->(x,y)
	cairo_path_t *p = path->path;
	if (!p->num_data || !p->data) return 0;

	int length_to_go = 0;
	cairo_path_data_t *pd = p->data;
	for (int i = 0; i < p->num_data; i++, pd++) {
		if (length_to_go > 0) {
			lua_pushvalue(L, 1);
			lua_pushnumber(L, pd->point.x);
			lua_pushnumber(L, pd->point.y);
			lua_call(L, 2, 2);
			if (!lua_isnumber(L, -1) || !lua_isnumber(L, -2)) {
				luaL_error(L, "The function given to path.map_coords must return two numbers");
				return 0;
			}
			pd->point.x = lua_tonumber(L, -2);
			pd->point.y = lua_tonumber(L, -1);
			lua_pop(L, 2);
			length_to_go--;

		} else {
			length_to_go = pd->header.length-1;
		}
	}

	return 0;
}

CALLABLE_IMPL(LuaCairoPath, fold)
{
	LuaCairoPath *path = GetObjPointer(L, lua_upvalueindex(1));
	if (!lua_isfunction(L, 1)) {
		luaL_error(L, "First argument to path.map_coords must be a function, is %s", luaL_typename(L, 1));
		return 0;
	}
	luaL_checkany(L, 2);

	// Function should be (r,p)->r
	cairo_path_t *p = path->path;
	if (!p->num_data || !p->data) return 0;

	cairo_path_data_t *pd = p->data;
	lua_pushvalue(L, 2); // initial 'r' for function
	for (int i = 0; i < p->num_data; ) {
		lua_pushvalue(L, 1);
		lua_pushvalue(L, -2); // dig up 'r'
		lua_remove(L, -3); // remove dug up 'r'
		path_element_to_lua(pd, L);
		lua_call(L, 2, 1);
		// leave result 'r' on stack for next iteration or final return
		i += pd->header.length;
		pd += pd->header.length;
	}

	// Now just r should be left on top of stack
	return 1;
}

CALLABLE_IMPL(LuaCairoPath, fold_coords)
{
	LuaCairoPath *path = GetObjPointer(L, lua_upvalueindex(1));
	if (!lua_isfunction(L, 1)) {
		luaL_error(L, "First argument to path.map_coords must be a function, is %s", luaL_typename(L, 1));
		return 0;
	}
	luaL_checkany(L, 2);

	// Function should be (r,x,y)->r
	cairo_path_t *p = path->path;
	if (!p->num_data || !p->data) return 0;

	int length_to_go = 0;
	cairo_path_data_t *pd = p->data;
	lua_pushvalue(L, 2); // initial 'r' for function
	for (int i = 0; i < p->num_data; i++, pd++) {
		if (length_to_go > 0) {
			lua_pushvalue(L, 1);
			lua_pushvalue(L, -2); // dig up 'r'
			lua_remove(L, -3); // remove dug up 'r'
			lua_pushnumber(L, pd->point.x);
			lua_pushnumber(L, pd->point.y);
			lua_call(L, 3, 1);
			// leave result 'r' on stack for next iteration or final return
			length_to_go--;

		} else {
			length_to_go = pd->header.length - 1;
		}
	}

	// Now just r should be left on top of stack
	return 1;
}

void LuaCairoPath::EnsurePathOwned()
{
	if (cairo_owns_memory) {
		cairo_path_t *np = (cairo_path_t*)malloc(sizeof(cairo_path_t));
		np->status = path->status;
		np->num_data = path->num_data;
		np->data = (cairo_path_data_t*)malloc(path->num_data*sizeof(cairo_path_data_t));
		memcpy(np->data, path->data, np->num_data*sizeof(cairo_path_data_t));
		cairo_path_destroy(path);
		path = np;
		cairo_owns_memory = false;
	}
}

void LuaCairoPath::EnsureSpaceFor(size_t n)
{
	EnsurePathOwned();

	if (path_elements_allocated - path->num_data < n) {
		path_elements_allocated = path->num_data*2 + n;
		path->data = (cairo_path_data_t*)realloc(path->data, path_elements_allocated*sizeof(cairo_path_data_t));
	}
}

void LuaCairoPath::RegPathCallables(lua_State *L)
{
	CALLABLE_REG(clear);
	CALLABLE_REG(move_to);
	CALLABLE_REG(line_to);
	CALLABLE_REG(curve_to);
	CALLABLE_REG(close);
	CALLABLE_REG(map);
	CALLABLE_REG(map_coords);
	CALLABLE_REG(fold);
	CALLABLE_REG(fold_coords);
}

int LuaCairoPath::internal_lua_index(lua_State *L)
{
	return LuaCairoBase::internal_lua_index(L);
}

const char *LuaCairoPath::GetTypeName()
{
	return "path";
}

LuaCairoPath::LuaCairoPath(lua_State *L) :
	LuaCairoBase(L)
{
	RegPathCallables(L);

	cairo_owns_memory = false;
	path = (cairo_path_t*)malloc(sizeof(cairo_path_t));
	path->status = CAIRO_STATUS_SUCCESS;
	path->num_data = 0;
	path->data = 0;
	EnsureSpaceFor(8);
}

LuaCairoPath::LuaCairoPath(lua_State *L, cairo_path_t *_path) :
	LuaCairoBase(L)
{
	RegPathCallables(L);

	cairo_owns_memory = true;
	path = _path;
}

LuaCairoPath::~LuaCairoPath()
{
	if (cairo_owns_memory)
		cairo_path_destroy(path);
	else {
		free(path->data);
		free(path);
	}
}


// Pattern (cairo_pattern_t)

CALLABLE_IMPL(LuaCairoPattern, add_color_stop_rgb)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	double offset = luaL_checknumber(L, 1);
	double red = luaL_checknumber(L, 2);
	double green = luaL_checknumber(L, 3);
	double blue = luaL_checknumber(L, 4);
	cairo_pattern_add_color_stop_rgb(pat->pattern, offset, red, green, blue);
	return 0;
}

CALLABLE_IMPL(LuaCairoPattern, add_color_stop_rgba)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	double offset = luaL_checknumber(L, 1);
	double red = luaL_checknumber(L, 2);
	double green = luaL_checknumber(L, 3);
	double blue = luaL_checknumber(L, 4);
	double alpha = luaL_checknumber(L, 5);
	cairo_pattern_add_color_stop_rgba(pat->pattern, offset, red, green, blue, alpha);
	return 0;
}

CALLABLE_IMPL(LuaCairoPattern, get_color_stop_count)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	int count;
	cairo_status_t res = cairo_pattern_get_color_stop_count(pat->pattern, &count);
	if (res != CAIRO_STATUS_SUCCESS)
		return 0;
	lua_pushinteger(L, count);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, get_color_stop_rgba)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	int index = luaL_checkint(L, 1);
	double offset, red, green, blue, alpha;
	cairo_status_t res = cairo_pattern_get_color_stop_rgba(pat->pattern, index, &offset, &red, &green, &blue, &alpha);
	if (res != CAIRO_STATUS_SUCCESS)
		return 0;
	lua_pushnumber(L, offset);
	lua_pushnumber(L, red);
	lua_pushnumber(L, green);
	lua_pushnumber(L, blue);
	lua_pushnumber(L, alpha);
	return 5;
}

CALLABLE_IMPL(LuaCairoPattern, get_rgba)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	double red, green, blue, alpha;
	cairo_status_t res = cairo_pattern_get_rgba(pat->pattern, &red, &green, &blue, &alpha);
	if (res != CAIRO_STATUS_SUCCESS)
		return 0;
	lua_pushnumber(L, red);
	lua_pushnumber(L, green);
	lua_pushnumber(L, blue);
	lua_pushnumber(L, alpha);
	return 4;
}

CALLABLE_IMPL(LuaCairoPattern, get_surface)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	cairo_surface_t *surf;
	cairo_status_t res = cairo_pattern_get_surface(pat->pattern, &surf);
	if (res != CAIRO_STATUS_SUCCESS)
		return 0;
	new LuaCairoSurface(L, surf);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, get_linear_points)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	double x0, y0, x1, y1;
	cairo_status_t res = cairo_pattern_get_linear_points(pat->pattern, &x0, &y0, &x1, &y1);
	if (res != CAIRO_STATUS_SUCCESS)
		return 0;
	lua_pushnumber(L, x0);
	lua_pushnumber(L, y0);
	lua_pushnumber(L, x1);
	lua_pushnumber(L, y1);
	return 4;
}

CALLABLE_IMPL(LuaCairoPattern, get_radial_circles)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	double x0, y0, r0, x1, y1, r1;
	cairo_status_t res = cairo_pattern_get_radial_circles(pat->pattern, &x0, &y0, &r0, &x1, &y1, &r1);
	if (res != CAIRO_STATUS_SUCCESS)
		return 0;
	lua_pushnumber(L, x0);
	lua_pushnumber(L, y0);
	lua_pushnumber(L, r0);
	lua_pushnumber(L, x1);
	lua_pushnumber(L, y1);
	lua_pushnumber(L, r1);
	return 6;
}

CALLABLE_NOTIMPL(LuaCairoPattern, reference)

CALLABLE_IMPL(LuaCairoPattern, status)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	cairo_status_t st = cairo_pattern_status(pat->pattern);
	lua_pushstring(L, status_names_list[st]);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, set_extend)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	cairo_extend_t ext = (cairo_extend_t)luaL_checkoption(L, 1, NULL, pattern_extend_list);
	cairo_pattern_set_extend(pat->pattern, ext);
	return 0;
}

CALLABLE_IMPL(LuaCairoPattern, get_extend)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	cairo_extend_t ext = cairo_pattern_get_extend(pat->pattern);
	lua_pushstring(L, pattern_extend_list[ext]);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, set_filter)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	cairo_filter_t filt = (cairo_filter_t)luaL_checkoption(L, 1, NULL, pattern_filter_list);
	cairo_pattern_set_filter(pat->pattern, filt);
	return 0;
}

CALLABLE_IMPL(LuaCairoPattern, get_filter)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	cairo_filter_t filt = cairo_pattern_get_filter(pat->pattern);
	lua_pushstring(L, pattern_filter_list[filt]);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, set_matrix)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = LuaCairoMatrix::GetObjPointer(L, 1);
	cairo_pattern_set_matrix(pat->pattern, mat->GetMatrix());
	return 0;
}

CALLABLE_IMPL(LuaCairoPattern, get_matrix)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	LuaCairoMatrix *mat = new LuaCairoMatrix(L);
	cairo_pattern_get_matrix(pat->pattern, mat->GetMatrix());
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, get_type)
{
	LuaCairoPattern *pat = GetObjPointer(L, lua_upvalueindex(1));
	cairo_pattern_type_t type = cairo_pattern_get_type(pat->pattern);
	lua_pushstring(L, pattern_type_list[type]);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, create_rgb)
{
	double red = luaL_checknumber(L, 1);
	double green = luaL_checknumber(L, 2);
	double blue = luaL_checknumber(L, 3);
	cairo_pattern_t *pat = cairo_pattern_create_rgb(red, green, blue);
	new LuaCairoPattern(L, pat);
	cairo_pattern_destroy(pat);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, create_rgba)
{
	double red = luaL_checknumber(L, 1);
	double green = luaL_checknumber(L, 2);
	double blue = luaL_checknumber(L, 3);
	double alpha = luaL_checknumber(L, 4);
	cairo_pattern_t *pat = cairo_pattern_create_rgba(red, green, blue, alpha);
	new LuaCairoPattern(L, pat);
	cairo_pattern_destroy(pat);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, create_for_surface)
{
	LuaCairoSurface *surf = LuaCairoSurface::GetObjPointer(L, 1);
	cairo_pattern_t *pat = cairo_pattern_create_for_surface(surf->GetSurface());
	new LuaCairoPattern(L, pat);
	cairo_pattern_destroy(pat);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, create_linear)
{
	double x0 = luaL_checknumber(L, 1);
	double y0 = luaL_checknumber(L, 2);
	double x1 = luaL_checknumber(L, 3);
	double y1 = luaL_checknumber(L, 4);
	cairo_pattern_t *pat = cairo_pattern_create_linear(x0, y0, x1, y1);
	new LuaCairoPattern(L, pat);
	cairo_pattern_destroy(pat);
	return 1;
}

CALLABLE_IMPL(LuaCairoPattern, create_radial)
{
	double cx0 = luaL_checknumber(L, 1);
	double cy0 = luaL_checknumber(L, 2);
	double radius0 = luaL_checknumber(L, 3);
	double cx1 = luaL_checknumber(L, 4);
	double cy1 = luaL_checknumber(L, 5);
	double radius1 = luaL_checknumber(L, 6);
	cairo_pattern_t *pat = cairo_pattern_create_radial(cx0, cy0, radius0, cx1, cy1, radius1);
	new LuaCairoPattern(L, pat);
	cairo_pattern_destroy(pat);
	return 1;
}

const char *LuaCairoPattern::GetTypeName()
{
	return "pattern";
}

LuaCairoPattern::LuaCairoPattern(lua_State *L, cairo_pattern_t *_pattern) :
	LuaCairoBase(L)
{
	CALLABLE_REG(add_color_stop_rgb);
	CALLABLE_REG(add_color_stop_rgba);
	CALLABLE_REG(get_color_stop_count);
	CALLABLE_REG(get_color_stop_rgba);
	CALLABLE_REG(get_rgba);
	CALLABLE_REG(get_surface);
	CALLABLE_REG(get_linear_points);
	CALLABLE_REG(get_radial_circles);
	CALLABLE_REG(reference);
	CALLABLE_REG(status);
	CALLABLE_REG(set_extend);
	CALLABLE_REG(get_extend);
	CALLABLE_REG(set_filter);
	CALLABLE_REG(get_filter);
	CALLABLE_REG(set_matrix);
	CALLABLE_REG(get_matrix);
	CALLABLE_REG(get_type);

	cairo_pattern_reference(_pattern);
	pattern = _pattern;
}

LuaCairoPattern::~LuaCairoPattern()
{
	cairo_pattern_destroy(pattern);
}


// Lua registration

static const luaL_Reg cairolib[] = {
	{"image_surface_create", LuaCairoSurface::lua_image_surface_create},
	{"font_options_create", LuaCairoFontOptions::lua_create},
	{"matrix_create", LuaCairoMatrix::lua_create},
	{"pattern_create_rgb", LuaCairoPattern::lua_create_rgb},
	{"pattern_create_rgba", LuaCairoPattern::lua_create_rgba},
	{"pattern_create_for_surface", LuaCairoPattern::lua_create_for_surface},
	{"pattern_create_linear", LuaCairoPattern::lua_create_linear},
	{"pattern_create_radial", LuaCairoPattern::lua_create_radial},
	{NULL,NULL}
};

int luaopen_cairo(lua_State *L)
{
	luaL_register(L, "cairo", cairolib);
	return 0;
}