/***************************************************************************

    rendlay.c

    Core rendering layout parser and manager.

    Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

****************************************************************************

    Notes:

        Unlike the old system, the artwork is not rotated with the game
        orientation. This is to support odd configurations like two
        monitors in different orientations. You can specify an orientation
        for a backdrop/screen/overlay/bezel element, but it only applies
        to the artwork itself, and does not affect coordinates in any way.


    Overview of objects:

        layout_file -- A layout_file comprises a list of elements and a
            list of views. The elements are reusable items that the views
            reference.

        layout_view -- A layout_view describes a single view within a
            layout_file. The view is described using arbitrary coordinates
            that are scaled to fit within the render target. Pixels within
            a view are assumed to be square.

        view_item -- Each view has four lists of view_items, one for each
            "layer." Each view item is specified using floating point
            coordinates in arbitrary units, and is assumed to have square
            pixels. Each view item can control its orientation independently.
            Each item can also have an optional name, and can be set at
            runtime into different "states", which control how the embedded
            elements are displayed.

        layout_element -- A layout_element is a description of a piece of
            visible artwork. Most view_items (except for those in the screen
            layer) have exactly one layout_element which describes the
            contents of the item. Elements are separate from items because
            they can be re-used multiple times within a layout. Even though
            an element can contain a number of components, they are treated
            as if they were a single bitmap.

        element_component -- Each layout_element contains one or more
            components. Each component can describe either an image or
            a rectangle/disk primitive. Each component also has a "state"
            associated with it, which controls whether or not the component
            is visible (if the owning item has the same state, it is
            visible).

***************************************************************************/

#include "render.h"
#include "rendfont.h"
#include "rendlay.h"
#include "rendutil.h"
#include "output.h"
#include "xmlfile.h"
#include "png.h"



/***************************************************************************
    STANDARD LAYOUTS
***************************************************************************/

/* single screen layouts */
#include "horizont.lh"
#include "vertical.lh"

/* dual screen layouts */
#include "dualhsxs.lh"
#include "dualhovu.lh"
#include "dualhuov.lh"

/* triple screen layouts */
#include "triphsxs.lh"

/* generic color overlay layouts */
#include "ho20ffff.lh"
#include "ho2eff2e.lh"
#include "ho4f893d.lh"
#include "ho88ffff.lh"
#include "hoa0a0ff.lh"
#include "hoffe457.lh"
#include "voffff20.lh"
#include "hoffff20.lh"



/***************************************************************************
    CONSTANTS
***************************************************************************/

#define LAYOUT_VERSION			2

enum
{
	COMPONENT_TYPE_IMAGE = 0,
	COMPONENT_TYPE_RECT,
	COMPONENT_TYPE_DISK,
	COMPONENT_TYPE_TEXT,
	COMPONENT_TYPE_LED7SEG,
	COMPONENT_TYPE_MAX
};



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* an element_component represents an image, rectangle, or disk in an element */
struct _element_component
{
	element_component *	next;				/* link to next component */
	int					type;				/* type of component */
	int					state;				/* state where this component is visible (-1 means all states) */
	render_bounds		bounds;				/* bounds of the element */
	render_color		color;				/* color of the element */
	const char *		string;				/* string for text components */
	mame_bitmap *		bitmap;				/* source bitmap for images */
	const char *		dirname;			/* directory name of image file (for lazy loading) */
	const char *		imagefile;			/* name of the image file (for lazy loading) */
	const char *		alphafile;			/* name of the alpha file (for lazy loading) */
	int					hasalpha;			/* is there any alpha component present? */
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

/* layout elements */
static void layout_element_scale(mame_bitmap *dest, const mame_bitmap *source, const rectangle *sbounds, void *param);
static void layout_element_draw_rect(mame_bitmap *dest, const rectangle *bounds, const render_color *color);
static void layout_element_draw_disk(mame_bitmap *dest, const rectangle *bounds, const render_color *color);
static void layout_element_draw_text(mame_bitmap *dest, const rectangle *bounds, const render_color *color, const char *string);
static void layout_element_draw_led7seg(mame_bitmap *dest, const rectangle *bounds, const render_color *color, int state);

/* layout file parsing */
static layout_element *load_layout_element(xml_data_node *elemnode, const char *dirname);
static element_component *load_element_component(xml_data_node *compnode, const char *dirname);
static layout_view *load_layout_view(xml_data_node *viewnode, layout_element *elemlist);
static view_item *load_view_item(xml_data_node *itemnode, layout_element *elemlist);
static mame_bitmap *load_component_bitmap(const char *dirname, const char *file, const char *alphafile, int *hasalpha);
static int load_bounds(xml_data_node *boundsnode, render_bounds *bounds);
static int load_color(xml_data_node *colornode, render_color *color);
static int load_orientation(xml_data_node *orientnode, int *orientation);
static void layout_view_free(layout_view *view);
static void layout_element_free(layout_element *element);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    gcd - compute the greatest common divisor (GCD)
    of two integers using the Euclidean algorithm
-------------------------------------------------*/

INLINE int gcd(int a, int b)
{
	while (b != 0)
	{
		int t = b;
		b = a % b;
		a = t;
	}
	return a;
}


/*-------------------------------------------------
    reduce_fraction - reduce a fraction by
    dividing out common factors
-------------------------------------------------*/

INLINE void reduce_fraction(int *num, int *den)
{
	int div;

	/* search the greatest common divisor */
	div = gcd(*num, *den);

	/* reduce the fraction if a common divisor has been found */
	if (div > 1)
	{
		*num /= div;
		*den /= div;
	}
}


/*-------------------------------------------------
    copy_string - make a copy of a string
-------------------------------------------------*/

INLINE const char *copy_string(const char *string)
{
	char *newstring = malloc_or_die(strlen(string) + 1);
	strcpy(newstring, string);
	return newstring;
}


/*-------------------------------------------------
    view_has_art - true if a render_view contains
    any non-screen elements
-------------------------------------------------*/

INLINE int view_has_art(layout_view *view)
{
	return (view->itemlist[ITEM_LAYER_BACKDROP] != 0 ||
			view->itemlist[ITEM_LAYER_OVERLAY] != 0 ||
			view->itemlist[ITEM_LAYER_BEZEL] != 0);
}



/***************************************************************************
    LAYOUT VIEWS
***************************************************************************/

/*-------------------------------------------------
    layout_view_recompute - recompute the bounds
    and aspect ratio of a view and all of its
    contained items
-------------------------------------------------*/

void layout_view_recompute(layout_view *view, int layerconfig)
{
	render_bounds target_bounds;
	float xscale, yscale;
	float xoffs, yoffs;
	int scrfirst = TRUE;
	int first = TRUE;
	int layer;

	/* reset the bounds */
	view->bounds.x0 = view->bounds.y0 = view->bounds.x1 = view->bounds.y1 = 0.0f;
	view->scrbounds.x0 = view->scrbounds.y0 = view->scrbounds.x1 = view->scrbounds.y1 = 0.0f;
	view->screens = 0;

	/* loop over all layers */
	for (layer = 0; layer < ITEM_LAYER_MAX; layer++)
	{
		static const int layer_mask[ITEM_LAYER_MAX] = { LAYER_CONFIG_ENABLE_BACKDROP, 0, LAYER_CONFIG_ENABLE_OVERLAY, LAYER_CONFIG_ENABLE_BEZEL };

		/* determine if this layer should be visible */
		view->layenabled[layer] = (layer_mask[layer] == 0 || (layerconfig & layer_mask[layer]));

		/* only do it if requested */
		if (view->layenabled[layer])
		{
			view_item *item;

			for (item = view->itemlist[layer]; item != NULL; item = item->next)
			{
				/* accumulate bounds */
				if (first)
					view->bounds = item->rawbounds;
				else
					union_render_bounds(&view->bounds, &item->rawbounds);
				first = FALSE;

				/* accumulate screen bounds */
				if (item->element == NULL)
				{
					if (scrfirst)
						view->scrbounds = item->rawbounds;
					else
						union_render_bounds(&view->scrbounds, &item->rawbounds);
					scrfirst = FALSE;
				}
			}
		}
	}

	/* if we have an explicit bounds, override it */
	if (view->expbounds.x1 > view->expbounds.x0)
		view->bounds = view->expbounds;

	/* compute the aspect ratio of the view */
	view->aspect = (view->bounds.x1 - view->bounds.x0) / (view->bounds.y1 - view->bounds.y0);
	view->scraspect = (view->scrbounds.x1 - view->scrbounds.x0) / (view->scrbounds.y1 - view->scrbounds.y0);

	/* if we're handling things normally, the target bounds are (0,0)-(1,1) */
	if (!(layerconfig & LAYER_CONFIG_ZOOM_TO_SCREEN))
	{
		target_bounds.x0 = target_bounds.y0 = 0.0f;
		target_bounds.x1 = target_bounds.y1 = 1.0f;
	}

	/* if we're cropping, we want the screen area to fill (0,0)-(1,1) */
	else
	{
		float targwidth = (view->bounds.x1 - view->bounds.x0) / (view->scrbounds.x1 - view->scrbounds.x0);
		float targheight = (view->bounds.y1 - view->bounds.y0) / (view->scrbounds.y1 - view->scrbounds.y0);
		target_bounds.x0 = (view->bounds.x0 - view->scrbounds.x0) / (view->bounds.x1 - view->bounds.x0) * targwidth;
		target_bounds.y0 = (view->bounds.y0 - view->scrbounds.y0) / (view->bounds.y1 - view->bounds.y0) * targheight;
		target_bounds.x1 = target_bounds.x0 + targwidth;
		target_bounds.y1 = target_bounds.y0 + targheight;
	}

	/* determine the scale/offset for normalization */
	xoffs = view->bounds.x0;
	yoffs = view->bounds.y0;
	xscale = (target_bounds.x1 - target_bounds.x0) / (view->bounds.x1 - view->bounds.x0);
	yscale = (target_bounds.y1 - target_bounds.y0) / (view->bounds.y1 - view->bounds.y0);

	/* normalize all the item bounds */
	for (layer = 0; layer < ITEM_LAYER_MAX; layer++)
	{
		view_item *item;

		/* adjust the bounds for each item */
		for (item = view->itemlist[layer]; item; item = item->next)
		{
			item->bounds.x0 = target_bounds.x0 + (item->rawbounds.x0 - xoffs) * xscale;
			item->bounds.x1 = target_bounds.x0 + (item->rawbounds.x1 - xoffs) * xscale;
			item->bounds.y0 = target_bounds.y0 + (item->rawbounds.y0 - yoffs) * yscale;
			item->bounds.y1 = target_bounds.y0 + (item->rawbounds.y1 - yoffs) * yscale;

			/* accumulate the screens in use while we're scanning */
			if (item->element == NULL)
				view->screens |= 1 << item->index;
		}
	}
}



/***************************************************************************
    LAYOUT ELEMENTS
***************************************************************************/

/*-------------------------------------------------
    layout_element_scale - scale an element by
    rendering all the components at the
    appropriate resolution
-------------------------------------------------*/

static void layout_element_scale(mame_bitmap *dest, const mame_bitmap *source, const rectangle *sbounds, void *param)
{
	element_texture *elemtex = param;
	element_component *component;

	/* iterate over components that are part of the current state */
	for (component = elemtex->element->complist; component != NULL; component = component->next)
		if (component->state == -1 || component->state == elemtex->state)
		{
			rectangle bounds;

			/* get the local scaled bounds */
			bounds.min_x = render_round_nearest(component->bounds.x0 * dest->width);
			bounds.min_y = render_round_nearest(component->bounds.y0 * dest->height);
			bounds.max_x = render_round_nearest(component->bounds.x1 * dest->width);
			bounds.max_y = render_round_nearest(component->bounds.y1 * dest->height);

			/* based on the component type, add to the texture */
			switch (component->type)
			{
				case COMPONENT_TYPE_IMAGE:
					if (component->bitmap == NULL)
						component->bitmap = load_component_bitmap(component->dirname, component->imagefile, component->alphafile, &component->hasalpha);
					render_resample_argb_bitmap_hq(
							BITMAP_ADDR32(dest, bounds.min_y, bounds.min_x),
							dest->rowpixels,
							bounds.max_x - bounds.min_x,
							bounds.max_y - bounds.min_y,
							component->bitmap, NULL, &component->color);
					break;

				case COMPONENT_TYPE_RECT:
					layout_element_draw_rect(dest, &bounds, &component->color);
					break;

				case COMPONENT_TYPE_DISK:
					layout_element_draw_disk(dest, &bounds, &component->color);
					break;

				case COMPONENT_TYPE_TEXT:
					layout_element_draw_text(dest, &bounds, &component->color, component->string);
					break;

				case COMPONENT_TYPE_LED7SEG:
					layout_element_draw_led7seg(dest, &bounds, &component->color, elemtex->state);
					break;
			}
		}
}


/*-------------------------------------------------
    layout_element_draw_rect - draw a rectangle
    in the specified color
-------------------------------------------------*/

static void layout_element_draw_rect(mame_bitmap *dest, const rectangle *bounds, const render_color *color)
{
	UINT32 r, g, b, inva;
	UINT32 x, y;

	/* compute premultiplied colors */
	r = color->r * color->a * 255.0;
	g = color->g * color->a * 255.0;
	b = color->b * color->a * 255.0;
	inva = (1.0f - color->a) * 255.0;

	/* iterate over X and Y */
	for (y = bounds->min_y; y < bounds->max_y; y++)
		for (x = bounds->min_x; x < bounds->max_x; x++)
		{
			UINT32 finalr = r;
			UINT32 finalg = g;
			UINT32 finalb = b;

			/* if we're translucent, add in the destination pixel contribution */
			if (inva > 0)
			{
				UINT32 dpix = *BITMAP_ADDR32(dest, y, x);
				finalr += (RGB_RED(dpix) * inva) >> 8;
				finalg += (RGB_GREEN(dpix) * inva) >> 8;
				finalb += (RGB_BLUE(dpix) * inva) >> 8;
			}

			/* store the target pixel, dividing the RGBA values by the overall scale factor */
			*BITMAP_ADDR32(dest, y, x) = MAKE_ARGB(0xff, finalr, finalg, finalb);
		}
}


/*-------------------------------------------------
    layout_element_draw_disk - draw an ellipse
    in the specified color
-------------------------------------------------*/

static void layout_element_draw_disk(mame_bitmap *dest, const rectangle *bounds, const render_color *color)
{
	float xcenter, ycenter;
	float xradius, yradius, ooyradius2;
	UINT32 r, g, b, inva;
	UINT32 x, y;

	/* compute premultiplied colors */
	r = color->r * color->a * 255.0;
	g = color->g * color->a * 255.0;
	b = color->b * color->a * 255.0;
	inva = (1.0f - color->a) * 255.0;

	/* find the center */
	xcenter = (float)(bounds->min_x + bounds->max_x) * 0.5f;
	ycenter = (float)(bounds->min_y + bounds->max_y) * 0.5f;
	xradius = (float)(bounds->max_x - bounds->min_x) * 0.5f;
	yradius = (float)(bounds->max_y - bounds->min_y) * 0.5f;
	ooyradius2 = 1.0f / (yradius * yradius);

	/* iterate over y */
	for (y = bounds->min_y; y < bounds->max_y; y++)
	{
		float ycoord = ycenter - ((float)y + 0.5f);
		float xval = xradius * sqrt(1.0f - (ycoord * ycoord) * ooyradius2);
		INT32 left, right;

		/* compute left/right coordinates */
		left = (INT32)(xcenter - xval + 0.5f);
		right = (INT32)(xcenter + xval + 0.5f);

		/* draw this scanline */
		for (x = left; x < right; x++)
		{
			UINT32 finalr = r;
			UINT32 finalg = g;
			UINT32 finalb = b;

			/* if we're translucent, add in the destination pixel contribution */
			if (inva > 0)
			{
				UINT32 dpix = *BITMAP_ADDR32(dest, y, x);
				finalr += (RGB_RED(dpix) * inva) >> 8;
				finalg += (RGB_GREEN(dpix) * inva) >> 8;
				finalb += (RGB_BLUE(dpix) * inva) >> 8;
			}

			/* store the target pixel, dividing the RGBA values by the overall scale factor */
			*BITMAP_ADDR32(dest, y, x) = MAKE_ARGB(0xff, finalr, finalg, finalb);
		}
	}
}


/*-------------------------------------------------
    layout_element_draw_text - draw text in the
    specified color
-------------------------------------------------*/

static void layout_element_draw_text(mame_bitmap *dest, const rectangle *bounds, const render_color *color, const char *string)
{
	render_font *font = render_font_alloc(NULL);
	mame_bitmap *tempbitmap;
	UINT32 r, g, b, a;
	float aspect = 1.0f;
	INT32 curx, width;
	const char *s;

	/* compute premultiplied colors */
	r = color->r * 255.0;
	g = color->g * 255.0;
	b = color->b * 255.0;
	a = color->a * 255.0;

	/* get the width of the string */
	while (1)
	{
		width = render_font_get_string_width(font, bounds->max_y - bounds->min_y, aspect, string);
		if (width < bounds->max_x - bounds->min_x)
			break;
		aspect *= 0.9f;
	}
	curx = bounds->min_x + (bounds->max_x - bounds->min_x - width) / 2;

	/* allocate a temporary bitmap */
	tempbitmap = bitmap_alloc(dest->width, dest->height, BITMAP_FORMAT_ARGB32);

	/* loop over characters */
	for (s = string; *s != 0; s++)
	{
		rectangle chbounds;
		int x, y;

		/* get the font bitmap */
		render_font_get_scaled_bitmap_and_bounds(font, tempbitmap, bounds->max_y - bounds->min_y, aspect, *s, &chbounds);

		/* copy the data into the target */
		for (y = 0; y < chbounds.max_y - chbounds.min_y; y++)
		{
			int effy = bounds->min_y + y;
			if (effy >= bounds->min_y && effy <= bounds->max_y)
			{
				UINT32 *s = BITMAP_ADDR32(tempbitmap, y, 0);
				UINT32 *d = BITMAP_ADDR32(dest, effy, 0);
				for (x = 0; x < chbounds.max_x - chbounds.min_x; x++)
				{
					int effx = curx + x + chbounds.min_x;
					if (effx >= bounds->min_x && effx <= bounds->max_x)
					{
						UINT32 spix = RGB_ALPHA(s[x]);
						if (spix != 0)
						{
							UINT32 dpix = d[effx];
							UINT32 ta, tr, tg, tb;

							ta = (a * (spix + 1)) >> 8;
							tr = (r * ta + RGB_RED(dpix) * (0x100 - ta)) >> 8;
							tg = (g * ta + RGB_GREEN(dpix) * (0x100 - ta)) >> 8;
							tb = (b * ta + RGB_BLUE(dpix) * (0x100 - ta)) >> 8;
							d[effx] = MAKE_ARGB(0xff, tr, tg, tb);
						}
					}
				}
			}
		}

		/* advance in the X direction */
		curx += render_font_get_char_width(font, bounds->max_y - bounds->min_y, aspect, *s);
	}

	/* free the temporary bitmap and font */
	bitmap_free(tempbitmap);
	render_font_free(font);
}


/*-------------------------------------------------
    draw_segment_horizontal - draw a horizontal
    LED segment
-------------------------------------------------*/

static void draw_segment_horizontal(mame_bitmap *dest, int minx, int maxx, int midy, int width, rgb_t color)
{
	int x, y;

	/* loop over the width of the segment */
	for (y = 0; y < width / 2; y++)
	{
		UINT32 *d0 = BITMAP_ADDR32(dest, midy - y, 0);
		UINT32 *d1 = BITMAP_ADDR32(dest, midy + y, 0);
		int ty = (y < width / 8) ? width / 8 : y;

		/* loop over the length of the segment */
		for (x = minx + ty; x < maxx - ty; x++)
			d0[x] = d1[x] = color;
	}
}


/*-------------------------------------------------
    draw_segment_horizontal - draw a vertical
    LED segment
-------------------------------------------------*/

static void draw_segment_vertical(mame_bitmap *dest, int miny, int maxy, int midx, int width, rgb_t color)
{
	int x, y;

	/* loop over the width of the segment */
	for (x = 0; x < width / 2; x++)
	{
		UINT32 *d0 = BITMAP_ADDR32(dest, 0, midx - x);
		UINT32 *d1 = BITMAP_ADDR32(dest, 0, midx + x);
		int tx = (x < width / 8) ? width / 8 : x;

		/* loop over the length of the segment */
		for (y = miny + tx; y < maxy - tx; y++)
			d0[y * dest->rowpixels] = d1[y * dest->rowpixels] = color;
	}
}


/*-------------------------------------------------
    draw_segment_decimal - draw a decimal point
-------------------------------------------------*/

static void draw_segment_decimal(mame_bitmap *dest, int midx, int midy, int width, rgb_t color)
{
	float ooradius2;
	UINT32 x, y;

	/* compute parameters */
	width /= 2;
	ooradius2 = 1.0f / (float)(width * width);

	/* iterate over y */
	for (y = 0; y <= width; y++)
	{
		UINT32 *d0 = BITMAP_ADDR32(dest, midy - y, 0);
		UINT32 *d1 = BITMAP_ADDR32(dest, midy + y, 0);
		float xval = width * sqrt(1.0f - (float)(y * y) * ooradius2);
		INT32 left, right;

		/* compute left/right coordinates */
		left = midx - (INT32)(xval + 0.5f);
		right = midx + (INT32)(xval + 0.5f);

		/* draw this scanline */
		for (x = left; x < right; x++)
			d0[x] = d1[x] = color;
	}
}


/*-------------------------------------------------
    layout_element_draw_led7seg - draw a
    7-segment LCD
-------------------------------------------------*/

static void layout_element_draw_led7seg(mame_bitmap *dest, const rectangle *bounds, const render_color *color, int pattern)
{
	const rgb_t onpen = MAKE_ARGB(0xff,0xff,0xff,0xff);
	const rgb_t offpen = MAKE_ARGB(0xff,0x20,0x20,0x20);
	int bmwidth, bmheight, segwidth, skewwidth;
	mame_bitmap *tempbitmap;
	int x, y;

	/* sizes for computation */
	bmwidth = 250;
	bmheight = 400;
	segwidth = 40;
	skewwidth = 40;

	/* allocate a temporary bitmap for drawing */
	tempbitmap = bitmap_alloc(bmwidth + skewwidth, bmheight, BITMAP_FORMAT_ARGB32);
	fillbitmap(tempbitmap, MAKE_ARGB(0xff,0x00,0x00,0x00), NULL);

	/* top bar */
	draw_segment_horizontal(tempbitmap, 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, 0 + segwidth/2, segwidth, (pattern & (1 << 0)) ? onpen : offpen);

	/* top-right bar */
	draw_segment_vertical(tempbitmap, 0 + 2*segwidth/3, bmheight/2 - segwidth/3, bmwidth - segwidth/2, segwidth, (pattern & (1 << 1)) ? onpen : offpen);

	/* bottom-right bar */
	draw_segment_vertical(tempbitmap, bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, bmwidth - segwidth/2, segwidth, (pattern & (1 << 2)) ? onpen : offpen);

	/* bottom bar */
	draw_segment_horizontal(tempbitmap, 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, bmheight - segwidth/2, segwidth, (pattern & (1 << 3)) ? onpen : offpen);

	/* bottom-left bar */
	draw_segment_vertical(tempbitmap, bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, 0 + segwidth/2, segwidth, (pattern & (1 << 4)) ? onpen : offpen);

	/* top-left bar */
	draw_segment_vertical(tempbitmap, 0 + 2*segwidth/3, bmheight/2 - segwidth/3, 0 + segwidth/2, segwidth, (pattern & (1 << 5)) ? onpen : offpen);

	/* middle bar */
	draw_segment_horizontal(tempbitmap, 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, bmheight/2, segwidth, (pattern & (1 << 6)) ? onpen : offpen);

	/* apply skew */
	for (y = 0; y < tempbitmap->height; y++)
	{
		UINT32 *destrow = BITMAP_ADDR32(tempbitmap, y, 0);
		int offs = skewwidth * (tempbitmap->height - y) / tempbitmap->height;
		for (x = bmwidth - 1; x >= 0; x--)
			destrow[x + offs] = destrow[x];
		for (x = 0; x < offs; x++)
			destrow[x] = 0;
	}

	/* decimal point */
	draw_segment_decimal(tempbitmap, bmwidth + segwidth/2, bmheight - segwidth/2, segwidth, (pattern & (1 << 7)) ? onpen : offpen);

	/* resample to the target size */
	render_resample_argb_bitmap_hq(dest->base, dest->rowpixels, dest->width, dest->height, tempbitmap, NULL, color);

	bitmap_free(tempbitmap);
}



/***************************************************************************
    LAYOUT FILE PARSING
***************************************************************************/

/*-------------------------------------------------
    get_variable_value - compute the value of
    a variable in an XML attribute
-------------------------------------------------*/

static int get_variable_value(const char *string, char **outputptr)
{
	int num, den, scrnum;
	char temp[100];

	/* screen 0 parameters */
	for (scrnum = 0; scrnum < MAX_SCREENS; scrnum++)
	{
		/* native X aspect factor */
		sprintf(temp, "~scr%dnativexaspect~", scrnum);
		if (!strncmp(string, temp, strlen(temp)))
		{
			num = Machine->drv->screen[scrnum].defstate.visarea.max_x + 1 - Machine->drv->screen[scrnum].defstate.visarea.min_x;
			den = Machine->drv->screen[scrnum].defstate.visarea.max_y + 1 - Machine->drv->screen[scrnum].defstate.visarea.min_y;
			reduce_fraction(&num, &den);
			*outputptr += sprintf(*outputptr, "%d", num);
			return strlen(temp);
		}

		/* native Y aspect factor */
		sprintf(temp, "~scr%dnativeyaspect~", scrnum);
		if (!strncmp(string, temp, strlen(temp)))
		{
			num = Machine->drv->screen[scrnum].defstate.visarea.max_x + 1 - Machine->drv->screen[scrnum].defstate.visarea.min_x;
			den = Machine->drv->screen[scrnum].defstate.visarea.max_y + 1 - Machine->drv->screen[scrnum].defstate.visarea.min_y;
			reduce_fraction(&num, &den);
			*outputptr += sprintf(*outputptr, "%d", den);
			return strlen(temp);
		}

		/* native width */
		sprintf(temp, "~scr%dwidth~", scrnum);
		if (!strncmp(string, temp, strlen(temp)))
		{
			*outputptr += sprintf(*outputptr, "%d", Machine->drv->screen[scrnum].defstate.visarea.max_x + 1 - Machine->drv->screen[0].defstate.visarea.min_x);
			return strlen(temp);
		}

		/* native height */
		sprintf(temp, "~scr%dheight~", scrnum);
		if (!strncmp(string, temp, strlen(temp)))
		{
			*outputptr += sprintf(*outputptr, "%d", Machine->drv->screen[scrnum].defstate.visarea.max_y + 1 - Machine->drv->screen[0].defstate.visarea.min_y);
			return strlen(temp);
		}
	}

	/* default: copy the first character and continue */
	**outputptr = *string;
	*outputptr += 1;
	return 1;
}


/*-------------------------------------------------
    xml_get_attribute_string_with_subst - analog
    to xml_get_attribute_string but with variable
    substitution
-------------------------------------------------*/

static const char *xml_get_attribute_string_with_subst(xml_data_node *node, const char *attribute, const char *defvalue)
{
	const char *str = xml_get_attribute_string(node, attribute, NULL);
	static char buffer[1000];
	const char *s;
	char *d;

	/* if nothing, just return the default */
	if (str == NULL)
		return defvalue;

	/* if no tildes, don't worry */
	if (strchr(str, '~') == NULL)
		return str;

	/* make a copy of the string, doing substitutions along the way */
	for (s = str, d = buffer; *s != 0; )
	{
		/* if not a variable, just copy */
		if (*s != '~')
			*d++ = *s++;

		/* extract the variable */
		else
			s += get_variable_value(s, &d);
	}
	*d = 0;
	return buffer;
}


/*-------------------------------------------------
    xml_get_attribute_int_with_subst - analog
    to xml_get_attribute_int but with variable
    substitution
-------------------------------------------------*/

static int xml_get_attribute_int_with_subst(xml_data_node *node, const char *attribute, int defvalue)
{
	const char *string = xml_get_attribute_string_with_subst(node, attribute, NULL);
	int value;

	if (!string || sscanf(string, "%d", &value) != 1)
		return defvalue;
	return value;
}


/*-------------------------------------------------
    xml_get_attribute_float_with_subst - analog
    to xml_get_attribute_float but with variable
    substitution
-------------------------------------------------*/

static float xml_get_attribute_float_with_subst(xml_data_node *node, const char *attribute, float defvalue)
{
	const char *string = xml_get_attribute_string_with_subst(node, attribute, NULL);
	float value;

	if (!string || sscanf(string, "%f", &value) != 1)
		return defvalue;
	return value;
}


/*-------------------------------------------------
    layout_file_load - parse a layout XML file
    into a layout_file
-------------------------------------------------*/

layout_file *layout_file_load(const char *dirname, const char *filename)
{
	xml_data_node *rootnode, *mamelayoutnode, *elemnode, *viewnode;
	layout_element **elemnext;
	layout_view **viewnext;
	layout_file *file;
	int version;

	/* if the first character of the "file" is an open brace, assume it is an XML string */
	if (filename[0] == '<')
		rootnode = xml_string_read(filename, NULL);

	/* otherwise, assume it is a file */
	else
	{
		file_error filerr;
		mame_file *layoutfile;
		char *fname;

		if (dirname != NULL)
			fname = assemble_4_strings(dirname, PATH_SEPARATOR, filename, ".lay");
		else
			fname = assemble_2_strings(filename, ".lay");
		filerr = mame_fopen(SEARCHPATH_ARTWORK, fname, OPEN_FLAG_READ, &layoutfile);
		free(fname);

		if (filerr != FILERR_NONE)
			return NULL;
		rootnode = xml_file_read(mame_core_file(layoutfile), NULL);
		mame_fclose(layoutfile);
	}

	/* if unable to parse the file, just bail */
	if (rootnode == NULL)
		return NULL;

	/* allocate the layout group object first */
	file = malloc_or_die(sizeof(*file));
	memset(file, 0, sizeof(*file));

	/* find the layout node */
	mamelayoutnode = xml_get_sibling(rootnode->child, "mamelayout");
	if (mamelayoutnode == NULL)
		fatalerror("Invalid XML file: missing mamelayout node");

	/* validate the config data version */
	version = xml_get_attribute_int(mamelayoutnode, "version", 0);
	if (version != LAYOUT_VERSION)
		fatalerror("Invalid XML file: unsupported version");

	/* parse all the elements */
	file->elemlist = NULL;
	elemnext = &file->elemlist;
	for (elemnode = xml_get_sibling(mamelayoutnode->child, "element"); elemnode; elemnode = xml_get_sibling(elemnode->next, "element"))
	{
		layout_element *element = load_layout_element(elemnode, dirname);
		if (element == NULL)
			goto error;

		/* add to the end of the list */
		*elemnext = element;
		elemnext = &element->next;
	}

	/* parse all the views */
	file->viewlist = NULL;
	viewnext = &file->viewlist;
	for (viewnode = xml_get_sibling(mamelayoutnode->child, "view"); viewnode; viewnode = xml_get_sibling(viewnode->next, "view"))
	{
		layout_view *view = load_layout_view(viewnode, file->elemlist);
		if (view == NULL)
			goto error;

		/* add to the end of the list */
		*viewnext = view;
		viewnext = &view->next;
	}
	xml_file_free(rootnode);
	return file;

error:
	layout_file_free(file);
	xml_file_free(rootnode);
	return NULL;
}


/*-------------------------------------------------
    load_layout_element - parse an element XML
    node from the layout file
-------------------------------------------------*/

static layout_element *load_layout_element(xml_data_node *elemnode, const char *dirname)
{
	render_bounds bounds = { 0 };
	element_component **nextcomp;
	element_component *component;
	xml_data_node *compnode;
	layout_element *element;
	float xscale, yscale;
	float xoffs, yoffs;
	const char *name;
	int state;
	int first;

	/* allocate a new element */
	element = malloc_or_die(sizeof(*element));
	memset(element, 0, sizeof(*element));

	/* extract the name */
	name = xml_get_attribute_string_with_subst(elemnode, "name", NULL);
	if (name == NULL)
	{
		logerror("All layout elements must have a name!\n");
		goto error;
	}
	element->name = copy_string(name);
	element->defstate = xml_get_attribute_int_with_subst(elemnode, "defstate", -1);

	/* parse components in order */
	first = TRUE;
	nextcomp = &element->complist;
	for (compnode = elemnode->child; compnode; compnode = compnode->next)
	{
		/* allocate a new component */
		element_component *component = load_element_component(compnode, dirname);
		if (component == NULL)
			goto error;

		/* link it into the list */
		*nextcomp = component;
		nextcomp = &component->next;

		/* accumulate bounds */
		if (first)
			bounds = component->bounds;
		else
			union_render_bounds(&bounds, &component->bounds);
		first = FALSE;

		/* determine the maximum state */
		if (component->state > element->maxstate)
			element->maxstate = component->state;
		if (component->type == COMPONENT_TYPE_LED7SEG)
			element->maxstate = 255;
	}

	/* determine the scale/offset for normalization */
	xoffs = bounds.x0;
	yoffs = bounds.y0;
	xscale = 1.0f / (bounds.x1 - bounds.x0);
	yscale = 1.0f / (bounds.y1 - bounds.y0);

	/* normalize all the component bounds */
	for (component = element->complist; component != NULL; component = component->next)
	{
		component->bounds.x0 = (component->bounds.x0 - xoffs) * xscale;
		component->bounds.x1 = (component->bounds.x1 - xoffs) * xscale;
		component->bounds.y0 = (component->bounds.y0 - yoffs) * yscale;
		component->bounds.y1 = (component->bounds.y1 - yoffs) * yscale;
	}

	/* allocate an array of textures for the states */
	element->elemtex = malloc_or_die((element->maxstate + 1) * sizeof(element->elemtex[0]));
	for (state = 0; state <= element->maxstate; state++)
	{
		element_component *component;

		element->elemtex[state].element = element;
		element->elemtex[state].state = state;

		/* look for at least one visible component in this state */
		for (component = element->complist; component != NULL; component = component->next)
			if (component->state == -1 || component->state == state || component->type == COMPONENT_TYPE_LED7SEG)
				break;

		/* allocate a texture only if we have some visible components in this state */
		if (component != NULL)
			element->elemtex[state].texture = render_texture_alloc(NULL, NULL, 0, TEXFORMAT_ARGB32, layout_element_scale, &element->elemtex[state]);
		else
			element->elemtex[state].texture = NULL;
	}
	return element;

error:
	layout_element_free(element);
	return NULL;
}


/*-------------------------------------------------
    load_element_component - parse a component
    XML node (image/rect/disk)
-------------------------------------------------*/

static element_component *load_element_component(xml_data_node *compnode, const char *dirname)
{
	element_component *component;

	/* allocate memory for the component */
	component = malloc_or_die(sizeof(*component));
	memset(component, 0, sizeof(*component));

	/* fetch common data */
	component->state = xml_get_attribute_int_with_subst(compnode, "state", -1);
	if (load_bounds(xml_get_sibling(compnode->child, "bounds"), &component->bounds))
		goto error;
	if (load_color(xml_get_sibling(compnode->child, "color"), &component->color))
		goto error;

	/* image nodes */
	if (strcmp(compnode->name, "image") == 0)
	{
		const char *file = xml_get_attribute_string_with_subst(compnode, "file", NULL);
		const char *afile = xml_get_attribute_string_with_subst(compnode, "alphafile", NULL);

		/* load and allocate the bitmap */
		component->type = COMPONENT_TYPE_IMAGE;
		component->dirname = (dirname == NULL) ? NULL : copy_string(dirname);
		component->imagefile = (file == NULL) ? NULL : copy_string(file);
		component->alphafile = (afile == NULL) ? NULL : copy_string(afile);
	}

	/* text nodes */
	else if (strcmp(compnode->name, "text") == 0)
	{
		const char *text = xml_get_attribute_string_with_subst(compnode, "string", "");
		char *string;

		/* allocate a copy of the string */
		component->type = COMPONENT_TYPE_TEXT;
		string = malloc_or_die(strlen(text) + 1);
		strcpy(string, text);
		component->string = string;
	}

	/* led7seg nodes */
	else if (strcmp(compnode->name, "led7seg") == 0)
		component->type = COMPONENT_TYPE_LED7SEG;

	/* rect nodes */
	else if (strcmp(compnode->name, "rect") == 0)
		component->type = COMPONENT_TYPE_RECT;

	/* disk nodes */
	else if (strcmp(compnode->name, "disk") == 0)
		component->type = COMPONENT_TYPE_DISK;

	/* error otherwise */
	else
		fatalerror("Unknown element component: %s", compnode->name);

	return component;

error:
	free(component);
	return NULL;
}


/*-------------------------------------------------
    load_layout_view - parse a view XML node
-------------------------------------------------*/

static layout_view *load_layout_view(xml_data_node *viewnode, layout_element *elemlist)
{
	xml_data_node *boundsnode;
	view_item **itemnext;
	layout_view *view;
	int layer;

	/* first allocate memory */
	view = malloc_or_die(sizeof(*view));
	memset(view, 0, sizeof(*view));

	/* allocate a copy of the name */
	view->name = copy_string(xml_get_attribute_string_with_subst(viewnode, "name", ""));

	/* if we have a bounds item, load it */
	boundsnode = xml_get_sibling(viewnode->child, "bounds");
	if (boundsnode != NULL && load_bounds(xml_get_sibling(boundsnode, "bounds"), &view->expbounds))
		goto error;

	/* loop over all the layer types we support */
	for (layer = 0; layer < ITEM_LAYER_MAX; layer++)
	{
		static const char *layer_node_name[ITEM_LAYER_MAX] = { "backdrop", "screen", "overlay", "bezel" };
		xml_data_node *itemnode;

		/* initialize the list */
		view->itemlist[layer] = NULL;
		itemnext = &view->itemlist[layer];

		/* parse all of the elements of that type */
		for (itemnode = xml_get_sibling(viewnode->child, layer_node_name[layer]); itemnode; itemnode = xml_get_sibling(itemnode->next, layer_node_name[layer]))
		{
			view_item *item = load_view_item(itemnode, elemlist);
			if (!item)
				goto error;

			/* add to the end of the list */
			*itemnext = item;
			itemnext = &item->next;
		}
	}

	/* recompute the data for the view */
	layout_view_recompute(view, ~0);
	return view;

error:
	layout_view_free(view);
	return NULL;
}


/*-------------------------------------------------
    load_view_item - parse an item XML node
-------------------------------------------------*/

static view_item *load_view_item(xml_data_node *itemnode, layout_element *elemlist)
{
	view_item *item;
	const char *name;

	/* allocate a new item */
	item = malloc_or_die(sizeof(*item));
	memset(item, 0, sizeof(*item));

	/* allocate a copy of the name */
	item->name = copy_string(xml_get_attribute_string_with_subst(itemnode, "name", ""));

	/* find the associated element */
	name = xml_get_attribute_string_with_subst(itemnode, "element", NULL);
	if (name != NULL)
	{
		layout_element *element;

		/* search the list of elements for a match */
		for (element = elemlist; element; element = element->next)
			if (strcmp(name, element->name) == 0)
				break;

		/* error if not found */
		if (element == NULL)
			fatalerror("Unable to find layout element %s", name);
		item->element = element;
	}

	/* fetch common data */
	item->index = xml_get_attribute_int_with_subst(itemnode, "index", -1);
	if (item->name[0] != 0 && item->element != 0)
		output_set_value(item->name, item->element->defstate);
	if (load_bounds(xml_get_sibling(itemnode->child, "bounds"), &item->rawbounds))
		goto error;
	if (load_color(xml_get_sibling(itemnode->child, "color"), &item->color))
		goto error;
	if (load_orientation(xml_get_sibling(itemnode->child, "orientation"), &item->orientation))
		goto error;

	/* sanity checks */
	if (strcmp(itemnode->name, "screen") == 0)
	{
		if (item->index >= MAX_SCREENS)
			fatalerror("Layout references invalid screen index %d", item->index);
	}
	else
	{
		if (item->element == NULL)
			fatalerror("Layout item of type %s require an element tag", itemnode->name);
	}

	return item;

error:
	if (item->name != NULL)
		free((void *)item->name);
	free(item);
	return NULL;
}


/*-------------------------------------------------
    load_component_bitmap - load a PNG file
    with artwork for a component
-------------------------------------------------*/

static mame_bitmap *load_component_bitmap(const char *dirname, const char *file, const char *alphafile, int *hasalpha)
{
	mame_bitmap *bitmap;

	/* load the basic bitmap */
	bitmap = render_load_png(dirname, file, NULL, hasalpha);
	if (bitmap != NULL && alphafile != NULL)

		/* load the alpha bitmap if specified */
		if (render_load_png(dirname, alphafile, bitmap, hasalpha) == NULL)
		{
			bitmap_free(bitmap);
			bitmap = NULL;
		}

	/* if we can't load the bitmap, allocate a dummy one and report an error */
	if (bitmap == NULL)
	{
		int step, line;

		/* draw some stripes in the bitmap */
		bitmap = bitmap_alloc(100, 100, BITMAP_FORMAT_ARGB32);
		fillbitmap(bitmap, 0, NULL);
		for (step = 0; step < 100; step += 25)
			for (line = 0; line < 100; line++)
				*BITMAP_ADDR32(bitmap, (step + line) % 100, line % 100) = MAKE_ARGB(0xff,0xff,0xff,0xff);

		/* log an error */
		if (alphafile == NULL)
			logerror("Unable to load component bitmap '%s'", file);
		else
			logerror("Unable to load component bitmap '%s'/'%s'", file, alphafile);
	}

	return bitmap;
}


/*-------------------------------------------------
    load_bounds - parse a bounds XML node
-------------------------------------------------*/

static int load_bounds(xml_data_node *boundsnode, render_bounds *bounds)
{
	/* skip if nothing */
	if (boundsnode == NULL)
	{
		bounds->x0 = bounds->y0 = 0.0f;
		bounds->x1 = bounds->y1 = 1.0f;
		return 0;
	}

	/* parse out the data */
	if (xml_get_attribute(boundsnode, "left") != NULL)
	{
		/* left/right/top/bottom format */
		bounds->x0 = xml_get_attribute_float_with_subst(boundsnode, "left", 0.0);
		bounds->x1 = xml_get_attribute_float_with_subst(boundsnode, "right", 1.0);
		bounds->y0 = xml_get_attribute_float_with_subst(boundsnode, "top", 0.0);
		bounds->y1 = xml_get_attribute_float_with_subst(boundsnode, "bottom", 1.0);
	}
	else if (xml_get_attribute(boundsnode, "x") != NULL)
	{
		/* x/y/width/height format */
		bounds->x0 = xml_get_attribute_float_with_subst(boundsnode, "x", 0.0);
		bounds->x1 = bounds->x0 + xml_get_attribute_float_with_subst(boundsnode, "width", 1.0);
		bounds->y0 = xml_get_attribute_float_with_subst(boundsnode, "y", 0.0);
		bounds->y1 = bounds->y0 + xml_get_attribute_float_with_subst(boundsnode, "height", 1.0);
	}
	else
	{
		fatalerror("Illegal bounds value in XML");
		return 1;
	}

	/* check for errors */
	if (bounds->x0 > bounds->x1 || bounds->y0 > bounds->y1)
	{
		fatalerror("Illegal bounds value in XML: (%f-%f)-(%f-%f)", bounds->x0, bounds->x1, bounds->y0, bounds->y1);
		return 1;
	}
	return 0;
}


/*-------------------------------------------------
    load_color - parse a color XML node
-------------------------------------------------*/

static int load_color(xml_data_node *colornode, render_color *color)
{
	/* skip if nothing */
	if (colornode == NULL)
	{
		color->r = color->g = color->b = color->a = 1.0f;
		return 0;
	}

	/* parse out the data */
	color->r = xml_get_attribute_float_with_subst(colornode, "red", 1.0);
	color->g = xml_get_attribute_float_with_subst(colornode, "green", 1.0);
	color->b = xml_get_attribute_float_with_subst(colornode, "blue", 1.0);
	color->a = xml_get_attribute_float_with_subst(colornode, "alpha", 1.0);

	/* check for errors */
	if (color->r < 0.0 || color->r > 1.0 || color->g < 0.0 || color->g > 1.0 ||
		color->b < 0.0 || color->b > 1.0 || color->a < 0.0 || color->a > 1.0)
	{
		fatalerror("Illegal ARGB color value in XML: %f,%f,%f,%f", color->r, color->g, color->b, color->a);
		return 1;
	}
	return 0;
}


/*-------------------------------------------------
    load_orientation - parse an orientation XML
    node
-------------------------------------------------*/

static int load_orientation(xml_data_node *orientnode, int *orientation)
{
	int rotate;

	/* skip if nothing */
	if (orientnode == NULL)
	{
		*orientation = ROT0;
		return 0;
	}

	/* parse out the data */
	rotate = xml_get_attribute_int_with_subst(orientnode, "rotate", 0);
	switch (rotate)
	{
		case 0:		*orientation = ROT0;	break;
		case 90:	*orientation = ROT90;	break;
		case 180:	*orientation = ROT180;	break;
		case 270:	*orientation = ROT270;	break;
		default:
			fatalerror("Invalid rotation in XML orientation node: %d", rotate);
			return 1;
	}
	if (strcmp("yes", xml_get_attribute_string_with_subst(orientnode, "swapxy", "no")) == 0)
		*orientation ^= ORIENTATION_SWAP_XY;
	if (strcmp("yes", xml_get_attribute_string_with_subst(orientnode, "flipx", "no")) == 0)
		*orientation ^= ORIENTATION_FLIP_X;
	if (strcmp("yes", xml_get_attribute_string_with_subst(orientnode, "flipy", "no")) == 0)
		*orientation ^= ORIENTATION_FLIP_Y;
	return 0;
}


/*-------------------------------------------------
    layout_file_free - free memory for a
    layout_file and all of its subelements
-------------------------------------------------*/

void layout_file_free(layout_file *file)
{
	/* free each element in the list */
	while (file->elemlist != NULL)
	{
		layout_element *temp = file->elemlist;
		file->elemlist = temp->next;
		layout_element_free(temp);
	}

	/* free each layout */
	while (file->viewlist != NULL)
	{
		layout_view *temp = file->viewlist;
		file->viewlist = temp->next;
		layout_view_free(temp);
	}

	/* free the file itself */
	free(file);
}


/*-------------------------------------------------
    layout_view_free - free memory for a
    layout_view and all of its subelements
-------------------------------------------------*/

static void layout_view_free(layout_view *view)
{
	int layer;

	/* for each layer, free each item in that layer */
	for (layer = 0; layer < ITEM_LAYER_MAX; layer++)
		while (view->itemlist[layer] != NULL)
		{
			view_item *temp = view->itemlist[layer];
			view->itemlist[layer] = temp->next;
			if (temp->name != NULL)
				free((void *)temp->name);
			free(temp);
		}

	/* free the view itself */
	if (view->name != NULL)
		free((void *)view->name);
	free(view);
}


/*-------------------------------------------------
    layout_element_free - free memory for a
    layout_element and its components
-------------------------------------------------*/

static void layout_element_free(layout_element *element)
{
	/* free all allocated components */
	while (element->complist != NULL)
	{
		element_component *temp = element->complist;
		element->complist = temp->next;
		if (temp->string != NULL)
			free((void *)temp->string);
		if (temp->dirname != NULL)
			free((void *)temp->dirname);
		if (temp->imagefile != NULL)
			free((void *)temp->imagefile);
		if (temp->alphafile != NULL)
			free((void *)temp->alphafile);
		if (temp->bitmap != NULL)
			bitmap_free(temp->bitmap);
		free(temp);
	}

	/* free all textures */
	if (element->elemtex != NULL)
	{
		int state;

		/* loop over all states and free their textures */
		for (state = 0; state <= element->maxstate; state++)
			if (element->elemtex[state].texture != NULL)
				render_texture_free(element->elemtex[state].texture);
		free(element->elemtex);
	}

	/* free the element itself */
	if (element->name != NULL)
		free((void *)element->name);
	free(element);
}
