⛏️ index : haiku.git

/*
 * Copyright 2001-2018, Haiku Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Marc Flerackers (mflerackers@androme.be)
 *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
 *		Marcus Overhagen (marcus@overhagen.de)
 *		Stephan Aßmus <superstippi@gmx.de>
 */

/**	PicturePlayer is used to play picture data. */

#include <PicturePlayer.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <AffineTransform.h>
#include <DataIO.h>
#include <Gradient.h>
#include <PictureProtocol.h>
#include <Shape.h>
#include <ShapePrivate.h>

#include <AutoDeleter.h>
#include <StackOrHeapArray.h>


using BPrivate::PicturePlayer;


struct adapter_context {
	void* user_data;
	void** function_table;
};


static void
nop()
{
}


static void
move_pen_by(void* _context, const BPoint& delta)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BPoint))context->function_table[1])(context->user_data,
		delta);
}


static void
stroke_line(void* _context, const BPoint& start, const BPoint& end)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BPoint, BPoint))context->function_table[2])(
		context->user_data, start, end);
}


static void
draw_rect(void* _context, const BRect& rect, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BRect))context->function_table[fill ? 4 : 3])(
		context->user_data, rect);
}


static void
draw_round_rect(void* _context, const BRect& rect, const BPoint& radii,
	bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BRect, BPoint))context->function_table[fill ? 6 : 5])(
		context->user_data, rect, radii);
}


static void
draw_bezier(void* _context, const BPoint _points[4], bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);

	BPoint points[4] = { _points[0], _points[1], _points[2], _points[3] };
	((void (*)(void*, BPoint*))context->function_table[fill ? 8 : 7])(
		context->user_data, points);
}


static void
draw_arc(void* _context, const BPoint& center, const BPoint& radii,
	float startTheta, float arcTheta, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BPoint, BPoint, float, float))
		context->function_table[fill ? 10 : 9])(context->user_data, center,
			radii, startTheta, arcTheta);
}


static void
draw_ellipse(void* _context, const BRect& rect, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	BPoint radii((rect.Width() + 1) / 2.0f, (rect.Height() + 1) / 2.0f);
	BPoint center = rect.LeftTop() + radii;
	((void (*)(void*, BPoint, BPoint))
		context->function_table[fill ? 12 : 11])(context->user_data, center,
			radii);
}


static void
draw_polygon(void* _context, size_t numPoints, const BPoint _points[],
	bool isClosed, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);

	BStackOrHeapArray<BPoint, 200> points(numPoints);
	if (!points.IsValid())
		return;

	memcpy((void*)points, _points, numPoints * sizeof(BPoint));

	((void (*)(void*, int32, BPoint*, bool))
		context->function_table[fill ? 14 : 13])(context->user_data, numPoints,
			points, isClosed);
}


static void
draw_shape(void* _context, const BShape& shape, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BShape))context->function_table[fill ? 16 : 15])(
		context->user_data, shape);
}


static void
draw_string(void* _context, const char* _string, size_t length,
	float deltaSpace, float deltaNonSpace)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	char* string = strndup(_string, length);

	((void (*)(void*, char*, float, float))
		context->function_table[17])(context->user_data, string, deltaSpace,
			deltaNonSpace);

	free(string);
}


static void
draw_pixels(void* _context, const BRect& src, const BRect& dest, uint32 width,
	uint32 height, size_t bytesPerRow, color_space pixelFormat, uint32 options,
	const void* _data, size_t length)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	void* data = malloc(length);
	if (data == NULL)
		return;

	memcpy(data, _data, length);

	((void (*)(void*, BRect, BRect, int32, int32, int32, int32, int32, void*))
		context->function_table[18])(context->user_data, src, dest, width,
			height, bytesPerRow, pixelFormat, options, data);

	free(data);
}


static void
draw_picture(void* _context, const BPoint& where, int32 token)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BPoint, int32))context->function_table[19])(
		context->user_data, where, token);
}


static void
set_clipping_rects(void* _context, size_t numRects, const clipping_rect _rects[])
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);

	// This is rather ugly but works for such a trivial class.
	BStackOrHeapArray<BRect, 100> rects(numRects);
	if (!rects.IsValid())
		return;

	for (size_t i = 0; i < numRects; i++) {
		clipping_rect srcRect = _rects[i];
		rects[i] = BRect(srcRect.left, srcRect.top, srcRect.right, srcRect.bottom);
	}

	((void (*)(void*, BRect*, uint32))context->function_table[20])(
		context->user_data, rects, numRects);
}


static void
clip_to_picture(void* _context, int32 token, const BPoint& origin,
	bool clipToInverse)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, int32, BPoint, bool))context->function_table[21])(
			context->user_data, token, origin, clipToInverse);
}


static void
push_state(void* _context)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*))context->function_table[22])(context->user_data);
}


static void
pop_state(void* _context)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*))context->function_table[23])(context->user_data);
}


static void
enter_state_change(void* _context)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*))context->function_table[24])(context->user_data);
}


static void
exit_state_change(void* _context)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*))context->function_table[25])(context->user_data);
}


static void
enter_font_state(void* _context)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*))context->function_table[26])(context->user_data);
}


static void
exit_font_state(void* _context)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*))context->function_table[27])(context->user_data);
}


static void
set_origin(void* _context, const BPoint& origin)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BPoint))context->function_table[28])(context->user_data,
		origin);
}


static void
set_pen_location(void* _context, const BPoint& penLocation)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BPoint))context->function_table[29])(context->user_data,
		penLocation);
}


static void
set_drawing_mode(void* _context, drawing_mode mode)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, drawing_mode))context->function_table[30])(
		context->user_data, mode);
}


static void
set_line_mode(void* _context, cap_mode capMode, join_mode joinMode,
	float miterLimit)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, cap_mode, join_mode, float))context->function_table[31])(
		context->user_data, capMode, joinMode, miterLimit);
}


static void
set_pen_size(void* _context, float size)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, float))context->function_table[32])(context->user_data,
		size);
}


static void
set_fore_color(void* _context, const rgb_color& color)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, rgb_color))context->function_table[33])(
		context->user_data, color);
}


static void
set_back_color(void* _context, const rgb_color& color)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, rgb_color))context->function_table[34])(
		context->user_data, color);
}


static void
set_stipple_pattern(void* _context, const pattern& stipplePattern)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, pattern))context->function_table[35])(context->user_data,
		stipplePattern);
}


static void
set_scale(void* _context, float scale)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, float))context->function_table[36])(context->user_data,
		scale);
}


static void
set_font_family(void* _context, const char* _family, size_t length)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	char* family = strndup(_family, length);

	((void (*)(void*, char*))context->function_table[37])(context->user_data,
		family);

	free(family);
}


static void
set_font_style(void* _context, const char* _style, size_t length)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	char* style = strndup(_style, length);

	((void (*)(void*, char*))context->function_table[38])(context->user_data,
		style);

	free(style);
}


static void
set_font_spacing(void* _context, uint8 spacing)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, int32))context->function_table[39])(context->user_data,
		spacing);
}


static void
set_font_size(void* _context, float size)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, float))context->function_table[40])(context->user_data,
		size);
}


static void
set_font_rotation(void* _context, float rotation)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, float))context->function_table[41])(context->user_data,
		rotation);
}


static void
set_font_encoding(void* _context, uint8 encoding)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, int32))context->function_table[42])(context->user_data,
		encoding);
}


static void
set_font_flags(void* _context, uint32 flags)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, int32))context->function_table[43])(context->user_data,
		flags);
}


static void
set_font_shear(void* _context, float shear)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, float))context->function_table[44])(context->user_data,
		shear);
}


static void
set_font_face(void* _context, uint16 face)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, int32))context->function_table[46])(context->user_data,
		face);
}


static void
set_blending_mode(void* _context, source_alpha alphaSrcMode,
	alpha_function alphaFncMode)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, source_alpha, alpha_function))
		context->function_table[47])(context->user_data, alphaSrcMode,
			alphaFncMode);
}


static void
set_transform(void* _context, const BAffineTransform& transform)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, const BAffineTransform&))
		context->function_table[48])(context->user_data, transform);
}


static void
translate_by(void* _context, double x, double y)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, double, double))
		context->function_table[49])(context->user_data, x, y);
}


static void
scale_by(void* _context, double x, double y)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, double, double))
		context->function_table[50])(context->user_data, x, y);
}


static void
rotate_by(void* _context, double angleRadians)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, double))
		context->function_table[51])(context->user_data, angleRadians);
}


static void
blend_layer(void* _context, Layer* layer)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, Layer*))
		context->function_table[52])(context->user_data, layer);
}


static void
clip_to_rect(void* _context, const BRect& rect, bool inverse)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, const BRect&, bool))
		context->function_table[53])(context->user_data, rect, inverse);
}


static void
clip_to_shape(void* _context, int32 opCount, const uint32 opList[],
	int32 ptCount, const BPoint ptList[], bool inverse)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, int32, const uint32*, int32, const BPoint*, bool))
		context->function_table[54])(context->user_data, opCount, opList,
			ptCount, ptList, inverse);
}


static void
draw_string_locations(void* _context, const char* _string, size_t length,
	const BPoint* locations, size_t locationCount)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	char* string = strndup(_string, length);

	((void (*)(void*, char*, const BPoint*, size_t))
		context->function_table[55])(context->user_data, string, locations,
			locationCount);

	free(string);
}


static void
draw_rect_gradient(void* _context, const BRect& rect, BGradient& gradient, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BRect, BGradient&))context->function_table[fill ? 56 : 57])(
		context->user_data, rect, gradient);
}


static void
draw_round_rect_gradient(void* _context, const BRect& rect, const BPoint& radii, BGradient& gradient,
	bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BRect, BPoint, BGradient&))context->function_table[fill ? 58 : 59])(
		context->user_data, rect, radii, gradient);
}


static void
draw_bezier_gradient(void* _context, const BPoint _points[4], BGradient& gradient, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);

	BPoint points[4] = { _points[0], _points[1], _points[2], _points[3] };
	((void (*)(void*, BPoint*, BGradient&))context->function_table[fill ? 60 : 61])(
		context->user_data, points, gradient);
}


static void
draw_arc_gradient(void* _context, const BPoint& center, const BPoint& radii,
	float startTheta, float arcTheta, BGradient& gradient, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BPoint, BPoint, float, float, BGradient&))
		context->function_table[fill ? 62 : 63])(context->user_data, center,
			radii, startTheta, arcTheta, gradient);
}


static void
draw_ellipse_gradient(void* _context, const BRect& rect, BGradient& gradient, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	BPoint radii((rect.Width() + 1) / 2.0f, (rect.Height() + 1) / 2.0f);
	BPoint center = rect.LeftTop() + radii;
	((void (*)(void*, BPoint, BPoint, BGradient&))
		context->function_table[fill ? 64 : 65])(context->user_data, center,
			radii, gradient);
}


static void
draw_polygon_gradient(void* _context, size_t numPoints, const BPoint _points[],
	bool isClosed, BGradient& gradient, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);

	BStackOrHeapArray<BPoint, 200> points(numPoints);
	if (!points.IsValid())
		return;

	memcpy((void*)points, _points, numPoints * sizeof(BPoint));

	((void (*)(void*, int32, BPoint*, bool, BGradient&))
		context->function_table[fill ? 66 : 67])(context->user_data, numPoints,
			points, isClosed, gradient);
}


static void
draw_shape_gradient(void* _context, const BShape& shape, BGradient& gradient, bool fill)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, BShape, BGradient&))context->function_table[fill ? 68 : 69])(
		context->user_data, shape, gradient);
}


static void
set_fill_rule(void* _context, int32 fillRule)
{
	adapter_context* context = reinterpret_cast<adapter_context*>(_context);
	((void (*)(void*, int32))context->function_table[70])(
		context->user_data, fillRule);
}



#if DEBUG > 1
static const char *
PictureOpToString(int op)
{
	#define RETURN_STRING(x) case x: return #x

	switch(op) {
		RETURN_STRING(B_PIC_MOVE_PEN_BY);
		RETURN_STRING(B_PIC_STROKE_LINE);
		RETURN_STRING(B_PIC_STROKE_RECT);
		RETURN_STRING(B_PIC_FILL_RECT);
		RETURN_STRING(B_PIC_STROKE_ROUND_RECT);
		RETURN_STRING(B_PIC_FILL_ROUND_RECT);
		RETURN_STRING(B_PIC_STROKE_BEZIER);
		RETURN_STRING(B_PIC_FILL_BEZIER);
		RETURN_STRING(B_PIC_STROKE_POLYGON);
		RETURN_STRING(B_PIC_FILL_POLYGON);
		RETURN_STRING(B_PIC_STROKE_SHAPE);
		RETURN_STRING(B_PIC_FILL_SHAPE);
		RETURN_STRING(B_PIC_DRAW_STRING);
		RETURN_STRING(B_PIC_DRAW_STRING_LOCATIONS);
		RETURN_STRING(B_PIC_DRAW_PIXELS);
		RETURN_STRING(B_PIC_DRAW_PICTURE);
		RETURN_STRING(B_PIC_STROKE_ARC);
		RETURN_STRING(B_PIC_FILL_ARC);
		RETURN_STRING(B_PIC_STROKE_ELLIPSE);
		RETURN_STRING(B_PIC_FILL_ELLIPSE);
		RETURN_STRING(B_PIC_STROKE_RECT_GRADIENT);
		RETURN_STRING(B_PIC_FILL_RECT_GRADIENT);
		RETURN_STRING(B_PIC_STROKE_ROUND_RECT_GRADIENT);
		RETURN_STRING(B_PIC_FILL_ROUND_RECT_GRADIENT);
		RETURN_STRING(B_PIC_STROKE_BEZIER_GRADIENT);
		RETURN_STRING(B_PIC_FILL_BEZIER_GRADIENT);
		RETURN_STRING(B_PIC_STROKE_POLYGON_GRADIENT);
		RETURN_STRING(B_PIC_FILL_POLYGON_GRADIENT);
		RETURN_STRING(B_PIC_STROKE_SHAPE_GRADIENT);
		RETURN_STRING(B_PIC_FILL_SHAPE_GRADIENT);
		RETURN_STRING(B_PIC_STROKE_ARC_GRADIENT);
		RETURN_STRING(B_PIC_FILL_ARC_GRADIENT);
		RETURN_STRING(B_PIC_STROKE_ELLIPSE_GRADIENT);
		RETURN_STRING(B_PIC_FILL_ELLIPSE_GRADIENT);

		RETURN_STRING(B_PIC_ENTER_STATE_CHANGE);
		RETURN_STRING(B_PIC_SET_CLIPPING_RECTS);
		RETURN_STRING(B_PIC_CLIP_TO_PICTURE);
		RETURN_STRING(B_PIC_PUSH_STATE);
		RETURN_STRING(B_PIC_POP_STATE);
		RETURN_STRING(B_PIC_CLEAR_CLIPPING_RECTS);
		RETURN_STRING(B_PIC_CLIP_TO_RECT);
		RETURN_STRING(B_PIC_CLIP_TO_SHAPE);

		RETURN_STRING(B_PIC_SET_ORIGIN);
		RETURN_STRING(B_PIC_SET_PEN_LOCATION);
		RETURN_STRING(B_PIC_SET_DRAWING_MODE);
		RETURN_STRING(B_PIC_SET_LINE_MODE);
		RETURN_STRING(B_PIC_SET_PEN_SIZE);
		RETURN_STRING(B_PIC_SET_SCALE);
		RETURN_STRING(B_PIC_SET_TRANSFORM);
		RETURN_STRING(B_PIC_SET_FORE_COLOR);
		RETURN_STRING(B_PIC_SET_BACK_COLOR);
		RETURN_STRING(B_PIC_SET_STIPLE_PATTERN);
		RETURN_STRING(B_PIC_ENTER_FONT_STATE);
		RETURN_STRING(B_PIC_SET_BLENDING_MODE);
		RETURN_STRING(B_PIC_SET_FILL_RULE);
		RETURN_STRING(B_PIC_SET_FONT_FAMILY);
		RETURN_STRING(B_PIC_SET_FONT_STYLE);
		RETURN_STRING(B_PIC_SET_FONT_SPACING);
		RETURN_STRING(B_PIC_SET_FONT_ENCODING);
		RETURN_STRING(B_PIC_SET_FONT_FLAGS);
		RETURN_STRING(B_PIC_SET_FONT_SIZE);
		RETURN_STRING(B_PIC_SET_FONT_ROTATE);
		RETURN_STRING(B_PIC_SET_FONT_SHEAR);
		RETURN_STRING(B_PIC_SET_FONT_BPP);
		RETURN_STRING(B_PIC_SET_FONT_FACE);

		RETURN_STRING(B_PIC_AFFINE_TRANSLATE);
		RETURN_STRING(B_PIC_AFFINE_SCALE);
		RETURN_STRING(B_PIC_AFFINE_ROTATE);

		RETURN_STRING(B_PIC_BLEND_LAYER);

		default: return "Unknown op";
	}
	#undef RETURN_STRING
}
#endif


PicturePlayer::PicturePlayer(const void *data, size_t size, BList *pictures)
	:	fData(data),
		fSize(size),
		fPictures(pictures)
{
}


PicturePlayer::~PicturePlayer()
{
}


status_t
PicturePlayer::Play(void** callBackTable, int32 tableEntries, void* userData)
{
	const BPrivate::picture_player_callbacks kAdapterCallbacks = {
		move_pen_by,
		stroke_line,
		draw_rect,
		draw_round_rect,
		draw_bezier,
		draw_arc,
		draw_ellipse,
		draw_polygon,
		draw_shape,
		draw_string,
		draw_pixels,
		draw_picture,
		set_clipping_rects,
		clip_to_picture,
		push_state,
		pop_state,
		enter_state_change,
		exit_state_change,
		enter_font_state,
		exit_font_state,
		set_origin,
		set_pen_location,
		set_drawing_mode,
		set_line_mode,
		set_pen_size,
		set_fore_color,
		set_back_color,
		set_stipple_pattern,
		set_scale,
		set_font_family,
		set_font_style,
		set_font_spacing,
		set_font_size,
		set_font_rotation,
		set_font_encoding,
		set_font_flags,
		set_font_shear,
		set_font_face,
		set_blending_mode,
		set_transform,
		translate_by,
		scale_by,
		rotate_by,
		blend_layer,
		clip_to_rect,
		clip_to_shape,
		draw_string_locations,
		draw_rect_gradient,
		draw_round_rect_gradient,
		draw_bezier_gradient,
		draw_arc_gradient,
		draw_ellipse_gradient,
		draw_polygon_gradient,
		draw_shape_gradient,
		set_fill_rule
	};

	// We don't check if the functions in the table are NULL, but we
	// check the tableEntries to see if the table is big enough.
	// If an application supplies the wrong size or an invalid pointer,
	// it's its own fault.

	// If the caller supplied a function table smaller than needed,
	// we use our dummy table, and copy the supported ops from the supplied one.
	void *dummyTable[kOpsTableSize];

	adapter_context adapterContext;
	adapterContext.user_data = userData;
	adapterContext.function_table = callBackTable;

	if ((size_t)tableEntries < kOpsTableSize) {
		memcpy(dummyTable, callBackTable, tableEntries * sizeof(void*));
		for (size_t i = (size_t)tableEntries; i < kOpsTableSize; i++)
			dummyTable[i] = (void*)nop;

		adapterContext.function_table = dummyTable;
	}

	return _Play(kAdapterCallbacks, &adapterContext, fData, fSize, 0);
}


status_t
PicturePlayer::Play(const picture_player_callbacks& callbacks,
	size_t callbacksSize, void* userData)
{
	return _Play(callbacks, userData, fData, fSize, 0);
}


class DataReader {
public:
		DataReader(const void* buffer, size_t length)
			:
			fBuffer((const uint8*)buffer),
			fRemaining(length)
		{
		}

		size_t
		Remaining() const
		{
			return fRemaining;
		}

		template<typename T>
		bool
		Get(const T*& typed, size_t count = 1)
		{
			if (fRemaining < sizeof(T) * count)
				return false;

			typed = reinterpret_cast<const T *>(fBuffer);
			fRemaining -= sizeof(T) * count;
			fBuffer += sizeof(T) * count;
			return true;
		}

		bool GetGradient(BGradient*& gradient)
		{
			BMemoryIO stream(fBuffer, fRemaining);
			printf("fRemaining: %ld\n", fRemaining);
			if (BGradient::Unflatten(gradient, &stream) != B_OK) {
				printf("BGradient::Unflatten(_gradient, &stream) != B_OK\n");
				return false;
			}

			fRemaining -= stream.Position();
			fBuffer += stream.Position();
			return true;
		}

		template<typename T>
		bool
		GetRemaining(const T*& buffer, size_t& size)
		{
			if (fRemaining == 0)
				return false;

			buffer = reinterpret_cast<const T*>(fBuffer);
			size = fRemaining;
			fRemaining = 0;
			return true;
		}

private:
		const uint8*	fBuffer;
		size_t			fRemaining;
};


struct picture_data_entry_header {
	uint16 op;
	uint32 size;
} _PACKED;


status_t
PicturePlayer::_Play(const picture_player_callbacks& callbacks, void* userData,
	const void* buffer, size_t length, uint16 parentOp)
{
#if DEBUG
	printf("Start rendering %sBPicture...\n", parentOp != 0 ? "sub " : "");
	bigtime_t startTime = system_time();
	int32 numOps = 0;
#endif

	DataReader pictureReader(buffer, length);

	while (pictureReader.Remaining() > 0) {
		const picture_data_entry_header* header;
		const uint8* opData = NULL;
		if (!pictureReader.Get(header)
			|| !pictureReader.Get(opData, header->size)) {
			return B_BAD_DATA;
		}

		DataReader reader(opData, header->size);

		// Disallow ops that don't fit the parent.
		switch (parentOp) {
			case 0:
				// No parent op, no restrictions.
				break;

			case B_PIC_ENTER_STATE_CHANGE:
				if (header->op <= B_PIC_ENTER_STATE_CHANGE
					|| header->op > B_PIC_SET_TRANSFORM) {
					return B_BAD_DATA;
				}
				break;

			case B_PIC_ENTER_FONT_STATE:
				if (header->op < B_PIC_SET_FONT_FAMILY
					|| header->op > B_PIC_SET_FONT_FACE) {
					return B_BAD_DATA;
					}
				break;

			default:
				return B_BAD_DATA;
		}

#if DEBUG > 1
		bigtime_t startOpTime = system_time();
		printf("Op %s ", PictureOpToString(header->op));
#endif
		switch (header->op) {
			case B_PIC_MOVE_PEN_BY:
			{
				const BPoint* where;
				if (callbacks.move_pen_by == NULL || !reader.Get(where))
					break;

				callbacks.move_pen_by(userData, *where);
				break;
			}

			case B_PIC_STROKE_LINE:
			{
				const BPoint* start;
				const BPoint* end;
				if (callbacks.stroke_line == NULL || !reader.Get(start)
					|| !reader.Get(end)) {
					break;
				}

				callbacks.stroke_line(userData, *start, *end);
				break;
			}

			case B_PIC_STROKE_RECT:
			case B_PIC_FILL_RECT:
			{
				const BRect* rect;
				if (callbacks.draw_rect == NULL || !reader.Get(rect))
					break;

				callbacks.draw_rect(userData, *rect,
					header->op == B_PIC_FILL_RECT);
				break;
			}

			case B_PIC_STROKE_ROUND_RECT:
			case B_PIC_FILL_ROUND_RECT:
			{
				const BRect* rect;
				const BPoint* radii;
				if (callbacks.draw_round_rect == NULL || !reader.Get(rect)
					|| !reader.Get(radii)) {
					break;
				}

				callbacks.draw_round_rect(userData, *rect, *radii,
					header->op == B_PIC_FILL_ROUND_RECT);
				break;
			}

			case B_PIC_STROKE_BEZIER:
			case B_PIC_FILL_BEZIER:
			{
				const size_t kNumControlPoints = 4;
				const BPoint* controlPoints;
				if (callbacks.draw_bezier == NULL
					|| !reader.Get(controlPoints, kNumControlPoints)) {
					break;
				}

				callbacks.draw_bezier(userData, controlPoints, header->op == B_PIC_FILL_BEZIER);
				break;
			}

			case B_PIC_STROKE_ARC:
			case B_PIC_FILL_ARC:
			{
				const BPoint* center;
				const BPoint* radii;
				const float* startTheta;
				const float* arcTheta;
				if (callbacks.draw_arc == NULL || !reader.Get(center)
					|| !reader.Get(radii) || !reader.Get(startTheta)
					|| !reader.Get(arcTheta)) {
					break;
				}

				callbacks.draw_arc(userData, *center, *radii, *startTheta,
					*arcTheta, header->op == B_PIC_FILL_ARC);
				break;
			}

			case B_PIC_STROKE_ELLIPSE:
			case B_PIC_FILL_ELLIPSE:
			{
				const BRect* rect;
				if (callbacks.draw_ellipse == NULL || !reader.Get(rect))
					break;

				callbacks.draw_ellipse(userData, *rect,
					header->op == B_PIC_FILL_ELLIPSE);
				break;
			}

			case B_PIC_STROKE_POLYGON:
			case B_PIC_FILL_POLYGON:
			{
				const uint32* numPoints;
				const BPoint* points;
				if (callbacks.draw_polygon == NULL || !reader.Get(numPoints)
					|| !reader.Get(points, *numPoints)) {
					break;
				}

				bool isClosed = true;
				const bool* closedPointer;
				if (header->op != B_PIC_FILL_POLYGON) {
					if (!reader.Get(closedPointer))
						break;

					isClosed = *closedPointer;
				}

				callbacks.draw_polygon(userData, *numPoints, points, isClosed,
					header->op == B_PIC_FILL_POLYGON);
				break;
			}

			case B_PIC_STROKE_SHAPE:
			case B_PIC_FILL_SHAPE:
			{
				const uint32* opCount;
				const uint32* pointCount;
				const uint32* opList;
				const BPoint* pointList;
				if (callbacks.draw_shape == NULL || !reader.Get(opCount)
					|| !reader.Get(pointCount) || !reader.Get(opList, *opCount)
					|| !reader.Get(pointList, *pointCount)) {
					break;
				}

				// TODO: remove BShape data copying
				BShape shape;
				BShape::Private(shape).SetData(*opCount, *pointCount, opList, pointList);

				callbacks.draw_shape(userData, shape,
					header->op == B_PIC_FILL_SHAPE);
				break;
			}

			case B_PIC_STROKE_RECT_GRADIENT:
			case B_PIC_FILL_RECT_GRADIENT:
			{
				const BRect* rect;
				BGradient* gradient;
				if (callbacks.draw_rect_gradient == NULL || !reader.Get(rect) || !reader.GetGradient(gradient))
					break;
				ObjectDeleter<BGradient> gradientDeleter(gradient);

				callbacks.draw_rect_gradient(userData, *rect, *gradient,
					header->op == B_PIC_FILL_RECT_GRADIENT);
				break;
			}

			case B_PIC_STROKE_ROUND_RECT_GRADIENT:
			case B_PIC_FILL_ROUND_RECT_GRADIENT:
			{
				const BRect* rect;
				const BPoint* radii;
				BGradient* gradient;
				if (callbacks.draw_round_rect_gradient == NULL || !reader.Get(rect)
					|| !reader.Get(radii) || !reader.GetGradient(gradient)) {
					break;
				}
				ObjectDeleter<BGradient> gradientDeleter(gradient);

				callbacks.draw_round_rect_gradient(userData, *rect, *radii, *gradient,
					header->op == B_PIC_FILL_ROUND_RECT_GRADIENT);
				break;
			}

			case B_PIC_STROKE_BEZIER_GRADIENT:
			case B_PIC_FILL_BEZIER_GRADIENT:
			{
				const size_t kNumControlPoints = 4;
				const BPoint* controlPoints;
				BGradient* gradient;
				if (callbacks.draw_bezier_gradient == NULL
					|| !reader.Get(controlPoints, kNumControlPoints) || !reader.GetGradient(gradient)) {
					break;
				}
				ObjectDeleter<BGradient> gradientDeleter(gradient);

				callbacks.draw_bezier_gradient(userData, controlPoints, *gradient,
					header->op == B_PIC_FILL_BEZIER_GRADIENT);
				break;
			}

			case B_PIC_STROKE_POLYGON_GRADIENT:
			case B_PIC_FILL_POLYGON_GRADIENT:
			{
				const uint32* numPoints;
				const BPoint* points;
				BGradient* gradient;
				if (callbacks.draw_polygon_gradient == NULL || !reader.Get(numPoints)
					|| !reader.Get(points, *numPoints)) {
					break;
				}

				bool isClosed = true;
				const bool* closedPointer;
				if (header->op != B_PIC_FILL_POLYGON_GRADIENT) {
					if (!reader.Get(closedPointer))
						break;

					isClosed = *closedPointer;
				}

				if (!reader.GetGradient(gradient))
					break;
				ObjectDeleter<BGradient> gradientDeleter(gradient);

				callbacks.draw_polygon_gradient(userData, *numPoints, points, isClosed, *gradient,
					header->op == B_PIC_FILL_POLYGON_GRADIENT);
				break;
			}

			case B_PIC_STROKE_SHAPE_GRADIENT:
			case B_PIC_FILL_SHAPE_GRADIENT:
			{
				const uint32* opCount;
				const uint32* pointCount;
				const uint32* opList;
				const BPoint* pointList;
				BGradient* gradient;
				if (callbacks.draw_shape_gradient == NULL || !reader.Get(opCount)
					|| !reader.Get(pointCount) || !reader.Get(opList, *opCount)
					|| !reader.Get(pointList, *pointCount) || !reader.GetGradient(gradient)) {
					break;
				}
				ObjectDeleter<BGradient> gradientDeleter(gradient);

				// TODO: remove BShape data copying
				BShape shape;
				BShape::Private(shape).SetData(*opCount, *pointCount, opList, pointList);

				callbacks.draw_shape_gradient(userData, shape, *gradient,
					header->op == B_PIC_FILL_SHAPE_GRADIENT);
				break;
			}

			case B_PIC_STROKE_ARC_GRADIENT:
			case B_PIC_FILL_ARC_GRADIENT:
			{
				const BPoint* center;
				const BPoint* radii;
				const float* startTheta;
				const float* arcTheta;
				BGradient* gradient;
				if (callbacks.draw_arc_gradient == NULL || !reader.Get(center)
					|| !reader.Get(radii) || !reader.Get(startTheta)
					|| !reader.Get(arcTheta) || !reader.GetGradient(gradient)) {
					break;
				}
				ObjectDeleter<BGradient> gradientDeleter(gradient);

				callbacks.draw_arc_gradient(userData, *center, *radii, *startTheta,
					*arcTheta, *gradient, header->op == B_PIC_FILL_ARC_GRADIENT);
				break;
			}

			case B_PIC_STROKE_ELLIPSE_GRADIENT:
			case B_PIC_FILL_ELLIPSE_GRADIENT:
			{
				const BRect* rect;
				BGradient* gradient;
				if (callbacks.draw_ellipse_gradient == NULL || !reader.Get(rect) || !reader.GetGradient(gradient))
					break;
				ObjectDeleter<BGradient> gradientDeleter(gradient);

				callbacks.draw_ellipse_gradient(userData, *rect, *gradient,
					header->op == B_PIC_FILL_ELLIPSE_GRADIENT);
				break;
			}

			case B_PIC_DRAW_STRING:
			{
				const int32* length;
				const char* string;
				const float* escapementSpace;
				const float* escapementNonSpace;
				if (callbacks.draw_string == NULL
					|| !reader.Get(length)
					|| !reader.Get(string, *length)
					|| !reader.Get(escapementSpace)
					|| !reader.Get(escapementNonSpace)) {
					break;
				}

				callbacks.draw_string(userData, string, *length,
					*escapementSpace, *escapementNonSpace);
				break;
			}

			case B_PIC_DRAW_STRING_LOCATIONS:
			{
				const uint32* pointCount;
				const BPoint* pointList;
				const int32* length;
				const char* string;
				if (callbacks.draw_string_locations == NULL
					|| !reader.Get(pointCount)
					|| !reader.Get(pointList, *pointCount)
					|| !reader.Get(length)
					|| !reader.Get(string, *length)) {
					break;
				}

				callbacks.draw_string_locations(userData, string, *length,
					pointList, *pointCount);
				break;
			}

			case B_PIC_DRAW_PIXELS:
			{
				const BRect* sourceRect;
				const BRect* destinationRect;
				const uint32* width;
				const uint32* height;
				const uint32* bytesPerRow;
				const uint32* colorSpace;
				const uint32* flags;
				const void* data;
				size_t length;
				if (callbacks.draw_pixels == NULL || !reader.Get(sourceRect)
					|| !reader.Get(destinationRect) || !reader.Get(width)
					|| !reader.Get(height) || !reader.Get(bytesPerRow)
					|| !reader.Get(colorSpace) || !reader.Get(flags)
					|| !reader.GetRemaining(data, length)) {
					break;
				}

				callbacks.draw_pixels(userData, *sourceRect, *destinationRect,
					*width, *height, *bytesPerRow, (color_space)*colorSpace,
					*flags, data, length);
				break;
			}

			case B_PIC_DRAW_PICTURE:
			{
				const BPoint* where;
				const int32* token;
				if (callbacks.draw_picture == NULL || !reader.Get(where)
					|| !reader.Get(token)) {
					break;
				}

				callbacks.draw_picture(userData, *where, *token);
				break;
			}

			case B_PIC_SET_CLIPPING_RECTS:
			{
				if (callbacks.set_clipping_rects == NULL)
					break;

				const clipping_rect* frame;
				if (!reader.Get(frame))
					break;

				uint32 numRects = reader.Remaining() / sizeof(clipping_rect);

				const clipping_rect* rects;
				if (!reader.Get(rects, numRects))
					break;

				callbacks.set_clipping_rects(userData, numRects, rects);
				break;
			}

			case B_PIC_CLEAR_CLIPPING_RECTS:
			{
				if (callbacks.set_clipping_rects == NULL)
					break;

				callbacks.set_clipping_rects(userData, 0, NULL);
				break;
			}

			case B_PIC_CLIP_TO_PICTURE:
			{
				const int32* token;
				const BPoint* where;
				const bool* inverse;
				if (callbacks.clip_to_picture == NULL || !reader.Get(token)
					|| !reader.Get(where) || !reader.Get(inverse))
					break;

				callbacks.clip_to_picture(userData, *token, *where, *inverse);
				break;
			}

			case B_PIC_PUSH_STATE:
			{
				if (callbacks.push_state == NULL)
					break;

				callbacks.push_state(userData);
				break;
			}

			case B_PIC_POP_STATE:
			{
				if (callbacks.pop_state == NULL)
					break;

				callbacks.pop_state(userData);
				break;
			}

			case B_PIC_ENTER_STATE_CHANGE:
			case B_PIC_ENTER_FONT_STATE:
			{
				const void* data;
				size_t length;
				if (!reader.GetRemaining(data, length))
					break;

				if (header->op == B_PIC_ENTER_STATE_CHANGE) {
					if (callbacks.enter_state_change != NULL)
						callbacks.enter_state_change(userData);
				} else if (callbacks.enter_font_state != NULL)
					callbacks.enter_font_state(userData);

				status_t result = _Play(callbacks, userData, data, length,
					header->op);
				if (result != B_OK)
					return result;

				if (header->op == B_PIC_ENTER_STATE_CHANGE) {
					if (callbacks.exit_state_change != NULL)
						callbacks.exit_state_change(userData);
				} else if (callbacks.exit_font_state != NULL)
					callbacks.exit_font_state(userData);

				break;
			}

			case B_PIC_SET_ORIGIN:
			{
				const BPoint* origin;
				if (callbacks.set_origin == NULL || !reader.Get(origin))
					break;

				callbacks.set_origin(userData, *origin);
				break;
			}

			case B_PIC_SET_PEN_LOCATION:
			{
				const BPoint* location;
				if (callbacks.set_pen_location == NULL || !reader.Get(location))
					break;

				callbacks.set_pen_location(userData, *location);
				break;
			}

			case B_PIC_SET_DRAWING_MODE:
			{
				const uint16* mode;
				if (callbacks.set_drawing_mode == NULL || !reader.Get(mode))
					break;

				callbacks.set_drawing_mode(userData, (drawing_mode)*mode);
				break;
			}

			case B_PIC_SET_LINE_MODE:
			{
				const uint16* capMode;
				const uint16* joinMode;
				const float* miterLimit;
				if (callbacks.set_line_mode == NULL || !reader.Get(capMode)
					|| !reader.Get(joinMode) || !reader.Get(miterLimit)) {
					break;
				}

				callbacks.set_line_mode(userData, (cap_mode)*capMode,
					(join_mode)*joinMode, *miterLimit);
				break;
			}

			case B_PIC_SET_PEN_SIZE:
			{
				const float* penSize;
				if (callbacks.set_pen_size == NULL || !reader.Get(penSize))
					break;

				callbacks.set_pen_size(userData, *penSize);
				break;
			}

			case B_PIC_SET_FORE_COLOR:
			{
				const rgb_color* color;
				if (callbacks.set_fore_color == NULL || !reader.Get(color))
					break;

				callbacks.set_fore_color(userData, *color);
				break;
			}

			case B_PIC_SET_BACK_COLOR:
			{
				const rgb_color* color;
				if (callbacks.set_back_color == NULL || !reader.Get(color))
					break;

				callbacks.set_back_color(userData, *color);
				break;
			}

			case B_PIC_SET_STIPLE_PATTERN:
			{
				const pattern* stipplePattern;
				if (callbacks.set_stipple_pattern == NULL
					|| !reader.Get(stipplePattern)) {
					break;
				}

				callbacks.set_stipple_pattern(userData, *stipplePattern);
				break;
			}

			case B_PIC_SET_SCALE:
			{
				const float* scale;
				if (callbacks.set_scale == NULL || !reader.Get(scale))
					break;

				callbacks.set_scale(userData, *scale);
				break;
			}

			case B_PIC_SET_FONT_FAMILY:
			{
				const int32* length;
				const char* family;
				if (callbacks.set_font_family == NULL
					|| !reader.Get(length)
					|| !reader.Get(family, *length)) {
					break;
				}

				callbacks.set_font_family(userData, family, *length);
				break;
			}

			case B_PIC_SET_FONT_STYLE:
			{
				const int32* length;
				const char* style;
				if (callbacks.set_font_style == NULL
					|| !reader.Get(length)
					|| !reader.Get(style, *length)) {
					break;
				}

				callbacks.set_font_style(userData, style, *length);
				break;
			}

			case B_PIC_SET_FONT_SPACING:
			{
				const uint32* spacing;
				if (callbacks.set_font_spacing == NULL || !reader.Get(spacing))
					break;

				callbacks.set_font_spacing(userData, *spacing);
				break;
			}

			case B_PIC_SET_FONT_SIZE:
			{
				const float* size;
				if (callbacks.set_font_size == NULL || !reader.Get(size))
					break;

				callbacks.set_font_size(userData, *size);
				break;
			}

			case B_PIC_SET_FONT_ROTATE:
			{
				const float* rotation;
				if (callbacks.set_font_rotation == NULL
					|| !reader.Get(rotation)) {
					break;
				}

				callbacks.set_font_rotation(userData, *rotation);
				break;
			}

			case B_PIC_SET_FONT_ENCODING:
			{
				const uint32* encoding;
				if (callbacks.set_font_encoding == NULL
					|| !reader.Get(encoding)) {
					break;
				}

				callbacks.set_font_encoding(userData, *encoding);
				break;
			}

			case B_PIC_SET_FONT_FLAGS:
			{
				const uint32* flags;
				if (callbacks.set_font_flags == NULL || !reader.Get(flags))
					break;

				callbacks.set_font_flags(userData, *flags);
				break;
			}

			case B_PIC_SET_FONT_SHEAR:
			{
				const float* shear;
				if (callbacks.set_font_shear == NULL || !reader.Get(shear))
					break;

				callbacks.set_font_shear(userData, *shear);
				break;
			}

			case B_PIC_SET_FONT_FACE:
			{
				const uint32* face;
				if (callbacks.set_font_face == NULL || !reader.Get(face))
					break;

				callbacks.set_font_face(userData, *face);
				break;
			}

			case B_PIC_SET_BLENDING_MODE:
			{
				const uint16* alphaSourceMode;
				const uint16* alphaFunctionMode;
				if (callbacks.set_blending_mode == NULL
					|| !reader.Get(alphaSourceMode)
					|| !reader.Get(alphaFunctionMode)) {
					break;
				}

				callbacks.set_blending_mode(userData,
					(source_alpha)*alphaSourceMode,
					(alpha_function)*alphaFunctionMode);
				break;
			}

			case B_PIC_SET_FILL_RULE:
			{
				const uint32* fillRule;
				if (callbacks.set_fill_rule == NULL
					|| !reader.Get(fillRule)) {
					break;
				}

				callbacks.set_fill_rule(userData, *fillRule);
				break;
			}

			case B_PIC_SET_TRANSFORM:
			{
				const BAffineTransform* transform;
				if (callbacks.set_transform == NULL || !reader.Get(transform))
					break;

				callbacks.set_transform(userData, *transform);
				break;
			}

			case B_PIC_AFFINE_TRANSLATE:
			{
				const double* x;
				const double* y;
				if (callbacks.translate_by == NULL || !reader.Get(x)
					|| !reader.Get(y)) {
					break;
				}

				callbacks.translate_by(userData, *x, *y);
				break;
			}

			case B_PIC_AFFINE_SCALE:
			{
				const double* x;
				const double* y;
				if (callbacks.scale_by == NULL || !reader.Get(x)
					|| !reader.Get(y)) {
					break;
				}

				callbacks.scale_by(userData, *x, *y);
				break;
			}

			case B_PIC_AFFINE_ROTATE:
			{
				const double* angleRadians;
				if (callbacks.rotate_by == NULL || !reader.Get(angleRadians))
					break;

				callbacks.rotate_by(userData, *angleRadians);
				break;
			}

			case B_PIC_BLEND_LAYER:
			{
				Layer* const* layer;
				if (callbacks.blend_layer == NULL || !reader.Get<Layer*>(layer))
					break;

				callbacks.blend_layer(userData, *layer);
				break;
			}

			case B_PIC_CLIP_TO_RECT:
			{
				const bool* inverse;
				const BRect* rect;

				if (callbacks.clip_to_rect == NULL || !reader.Get(inverse)
					|| !reader.Get(rect)) {
					break;
				}

				callbacks.clip_to_rect(userData, *rect, *inverse);
				break;
			}

			case B_PIC_CLIP_TO_SHAPE:
			{
				const bool* inverse;
				const uint32* opCount;
				const uint32* pointCount;
				const uint32* opList;
				const BPoint* pointList;
				if (callbacks.clip_to_shape == NULL || !reader.Get(inverse)
					|| !reader.Get(opCount) || !reader.Get(pointCount)
					|| !reader.Get(opList, *opCount)
					|| !reader.Get(pointList, *pointCount)) {
					break;
				}

				callbacks.clip_to_shape(userData, *opCount, opList,
					*pointCount, pointList, *inverse);
				break;
			}

			default:
				break;
		}

#if DEBUG
		numOps++;
#if DEBUG > 1
		printf("executed in %" B_PRId64 " usecs\n", system_time()
			- startOpTime);
#endif
#endif
	}

#if DEBUG
	printf("Done! %" B_PRId32 " ops, rendering completed in %" B_PRId64
		" usecs.\n", numOps, system_time() - startTime);
#endif
	return B_OK;
}