root/src/kits/interface/PicturePlayer.cpp
/*
 * 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 <Font.h>
#include <Gradient.h>
#include <PictureProtocol.h>
#include <Shape.h>
#include <ShapePrivate.h>

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


using BPrivate::PicturePlayer;
using BPrivate::PicturePlayerCallbacks;
using BPrivate::picture_player_callbacks_compat;


class CallbackAdapterPlayer : public PicturePlayerCallbacks {
public:
        CallbackAdapterPlayer(void* userData, void** functionTable);

        virtual void MovePenBy(const BPoint& where);
        virtual void StrokeLine(const BPoint& start, const BPoint& end);
        virtual void DrawRect(const BRect& rect, bool fill);
        virtual void DrawRoundRect(const BRect& rect, const BPoint& radii, bool fill);
        virtual void DrawBezier(const BPoint controlPoints[4], bool fill);
        virtual void DrawArc(const BPoint& center, const BPoint& radii, float startTheta,
                float arcTheta, bool fill);
        virtual void DrawEllipse(const BRect& rect, bool fill);
        virtual void DrawPolygon(size_t numPoints, const BPoint points[], bool isClosed, bool fill);
        virtual void DrawShape(const BShape& shape, bool fill);
        virtual void DrawString(const char* string, size_t length, const escapement_delta& delta);
        virtual void DrawPixels(const BRect& source, const BRect& destination, uint32 width,
                uint32 height, size_t bytesPerRow, color_space pixelFormat, uint32 flags, const void* data,
                size_t length);
        virtual void DrawPicture(const BPoint& where, int32 token);
        virtual void SetClippingRects(size_t numRects, const clipping_rect rects[]);
        virtual void ClipToPicture(int32 token, const BPoint& where, bool clipToInverse);
        virtual void PushState();
        virtual void PopState();
        virtual void EnterStateChange();
        virtual void ExitStateChange();
        virtual void EnterFontState();
        virtual void ExitFontState();
        virtual void SetOrigin(const BPoint& origin);
        virtual void SetPenLocation(const BPoint& location);
        virtual void SetDrawingMode(drawing_mode mode);
        virtual void SetLineMode(cap_mode capMode, join_mode joinMode, float miterLimit);
        virtual void SetPenSize(float size);
        virtual void SetForeColor(const rgb_color& color);
        virtual void SetBackColor(const rgb_color& color);
        virtual void SetStipplePattern(const pattern& patter);
        virtual void SetScale(float scale);
        virtual void SetFontFamily(const char* familyName, size_t length);
        virtual void SetFontStyle(const char* styleName, size_t length);
        virtual void SetFontSpacing(uint8 spacing);
        virtual void SetFontSize(float size);
        virtual void SetFontRotation(float rotation);
        virtual void SetFontEncoding(uint8 encoding);
        virtual void SetFontFlags(uint32 flags);
        virtual void SetFontShear(float shear);
        virtual void SetFontFace(uint16 face);
        virtual void SetBlendingMode(source_alpha alphaSourceMode, alpha_function alphaFunctionMode);
        virtual void SetTransform(const BAffineTransform& transform);
        virtual void TranslateBy(double x, double y);
        virtual void ScaleBy(double x, double y);
        virtual void RotateBy(double angleRadians);
        virtual void BlendLayer(Layer* layer);
        virtual void ClipToRect(const BRect& rect, bool inverse);
        virtual void ClipToShape(int32 opCount, const uint32 opList[], int32 ptCount,
                const BPoint ptList[], bool inverse);
        virtual void DrawStringLocations(const char* string, size_t length, const BPoint locations[],
                size_t locationCount);
        virtual void DrawRectGradient(const BRect& rect, BGradient& gradient, bool fill);
        virtual void DrawRoundRectGradient(const BRect& rect, const BPoint& radii, BGradient& gradient,
                bool fill);
        virtual void DrawBezierGradient(const BPoint controlPoints[4], BGradient& gradient, bool fill);
        virtual void DrawArcGradient(const BPoint& center, const BPoint& radii, float startTheta,
                float arcTheta, BGradient& gradient, bool fill);
        virtual void DrawEllipseGradient(const BRect& rect, BGradient& gradient, bool fill);
        virtual void DrawPolygonGradient(size_t numPoints, const BPoint points[], bool isClosed,
                BGradient& gradient, bool fill);
        virtual void DrawShapeGradient(const BShape& shape, BGradient& gradient, bool fill);
        virtual void SetFillRule(int32 fillRule);
        virtual void StrokeLineGradient(const BPoint& start, const BPoint& end, BGradient& gradient);

private:
        void* fUserData;
        picture_player_callbacks_compat* fCallbacks;
};


static void
nop()
{
}


CallbackAdapterPlayer::CallbackAdapterPlayer(void* userData, void** functionTable)
        :
        fUserData(userData),
        fCallbacks((picture_player_callbacks_compat*)functionTable)
{
}


void
CallbackAdapterPlayer::MovePenBy(const BPoint& delta)
{
        fCallbacks->move_pen_by(fUserData, delta);
}


void
CallbackAdapterPlayer::StrokeLine(const BPoint& start, const BPoint& end)
{
        fCallbacks->stroke_line(fUserData, start, end);
}


void
CallbackAdapterPlayer::DrawRect(const BRect& rect, bool fill)
{
        if (fill)
                fCallbacks->fill_rect(fUserData, rect);
        else
                fCallbacks->stroke_rect(fUserData, rect);
}


void
CallbackAdapterPlayer::DrawRoundRect(const BRect& rect, const BPoint& radii,
        bool fill)
{
        if (fill)
                fCallbacks->fill_round_rect(fUserData, rect, radii);
        else
                fCallbacks->stroke_round_rect(fUserData, rect, radii);
}


void
CallbackAdapterPlayer::DrawBezier(const BPoint _points[4], bool fill)
{
        BPoint points[4] = { _points[0], _points[1], _points[2], _points[3] };

        if (fill)
                fCallbacks->fill_bezier(fUserData, points);
        else
                fCallbacks->stroke_bezier(fUserData, points);
}


void
CallbackAdapterPlayer::DrawArc(const BPoint& center, const BPoint& radii,
        float startTheta, float arcTheta, bool fill)
{
        if (fill)
                fCallbacks->fill_arc(fUserData, center, radii, startTheta, arcTheta);
        else
                fCallbacks->stroke_arc(fUserData, center, radii, startTheta, arcTheta);
}


void
CallbackAdapterPlayer::DrawEllipse(const BRect& rect, bool fill)
{
        BPoint radii((rect.Width() + 1) / 2.0f, (rect.Height() + 1) / 2.0f);
        BPoint center = rect.LeftTop() + radii;

        if (fill)
                fCallbacks->fill_ellipse(fUserData, center, radii);
        else
                fCallbacks->stroke_ellipse(fUserData, center, radii);
}


void
CallbackAdapterPlayer::DrawPolygon(size_t numPoints, const BPoint _points[],
        bool isClosed, bool fill)
{

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

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

        if (fill)
                fCallbacks->fill_polygon(fUserData, numPoints, points, isClosed);
        else
                fCallbacks->stroke_polygon(fUserData, numPoints, points, isClosed);
}


void
CallbackAdapterPlayer::DrawShape(const BShape& shape, bool fill)
{
        if (fill)
                fCallbacks->fill_shape(fUserData, &shape);
        else
                fCallbacks->stroke_shape(fUserData, &shape);
}


void
CallbackAdapterPlayer::DrawString(const char* _string, size_t length,
        const escapement_delta& delta)
{
        char* string = strndup(_string, length);

        fCallbacks->draw_string(fUserData, string, delta.nonspace, delta.space);

        free(string);
}


void
CallbackAdapterPlayer::DrawPixels(const BRect& src, const BRect& dest, uint32 width,
        uint32 height, size_t bytesPerRow, color_space pixelFormat, uint32 options,
        const void* _data, size_t length)
{
        void* data = malloc(length);
        if (data == NULL)
                return;

        memcpy(data, _data, length);

        fCallbacks->draw_pixels(fUserData, src, dest, width,
                        height, bytesPerRow, pixelFormat, options, data);

        free(data);
}


void
CallbackAdapterPlayer::DrawPicture(const BPoint& where, int32 token)
{
        fCallbacks->draw_picture(fUserData, where, token);
}


void
CallbackAdapterPlayer::SetClippingRects(size_t numRects, const clipping_rect _rects[])
{
        // 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);
        }

        fCallbacks->set_clipping_rects(fUserData, rects, numRects);
}


void
CallbackAdapterPlayer::ClipToPicture(int32 token, const BPoint& origin,
        bool clipToInverse)
{
        fCallbacks->clip_to_picture(fUserData, token, origin, clipToInverse);
}


void
CallbackAdapterPlayer::PushState()
{
        fCallbacks->push_state(fUserData);
}


void
CallbackAdapterPlayer::PopState()
{
        fCallbacks->pop_state(fUserData);
}


void
CallbackAdapterPlayer::EnterStateChange()
{
        fCallbacks->enter_state_change(fUserData);
}


void
CallbackAdapterPlayer::ExitStateChange()
{
        fCallbacks->exit_state_change(fUserData);
}


void
CallbackAdapterPlayer::EnterFontState()
{
        fCallbacks->enter_font_state(fUserData);
}


void
CallbackAdapterPlayer::ExitFontState()
{
        fCallbacks->exit_font_state(fUserData);
}


void
CallbackAdapterPlayer::SetOrigin(const BPoint& origin)
{
        fCallbacks->set_origin(fUserData, origin);
}


void
CallbackAdapterPlayer::SetPenLocation(const BPoint& penLocation)
{
        fCallbacks->set_pen_location(fUserData, penLocation);
}


void
CallbackAdapterPlayer::SetDrawingMode(drawing_mode mode)
{
        fCallbacks->set_drawing_mode(fUserData, mode);
}


void
CallbackAdapterPlayer::SetLineMode(cap_mode capMode, join_mode joinMode,
        float miterLimit)
{
        fCallbacks->set_line_mode(fUserData, capMode, joinMode, miterLimit);
}


void
CallbackAdapterPlayer::SetPenSize(float size)
{
        fCallbacks->set_pen_size(fUserData, size);
}


void
CallbackAdapterPlayer::SetForeColor(const rgb_color& color)
{
        fCallbacks->set_fore_color(fUserData, color);
}


void
CallbackAdapterPlayer::SetBackColor(const rgb_color& color)
{
        fCallbacks->set_back_color(fUserData, color);
}


void
CallbackAdapterPlayer::SetStipplePattern(const pattern& stipplePattern)
{
        fCallbacks->set_stipple_pattern(fUserData, stipplePattern);
}


void
CallbackAdapterPlayer::SetScale(float scale)
{
        fCallbacks->set_scale(fUserData, scale);
}


void
CallbackAdapterPlayer::SetFontFamily(const char* _family, size_t length)
{
        char* family = strndup(_family, length);

        fCallbacks->set_font_family(fUserData, family);

        free(family);
}


void
CallbackAdapterPlayer::SetFontStyle(const char* _style, size_t length)
{
        char* style = strndup(_style, length);

        fCallbacks->set_font_style(fUserData, style);

        free(style);
}


void
CallbackAdapterPlayer::SetFontSpacing(uint8 spacing)
{
        fCallbacks->set_font_spacing(fUserData, spacing);
}


void
CallbackAdapterPlayer::SetFontSize(float size)
{
        fCallbacks->set_font_size(fUserData, size);
}


void
CallbackAdapterPlayer::SetFontRotation(float rotation)
{
        fCallbacks->set_font_rotate(fUserData, rotation);
}


void
CallbackAdapterPlayer::SetFontEncoding(uint8 encoding)
{
        fCallbacks->set_font_encoding(fUserData, encoding);
}


void
CallbackAdapterPlayer::SetFontFlags(uint32 flags)
{
        fCallbacks->set_font_flags(fUserData, flags);
}


void
CallbackAdapterPlayer::SetFontShear(float shear)
{
        fCallbacks->set_font_shear(fUserData, shear);
}


void
CallbackAdapterPlayer::SetFontFace(uint16 face)
{
        fCallbacks->set_font_face(fUserData, face);
}


void
CallbackAdapterPlayer::SetBlendingMode(source_alpha alphaSrcMode,
        alpha_function alphaFncMode)
{
        fCallbacks->set_blending_mode(fUserData, alphaSrcMode, alphaFncMode);
}


void
CallbackAdapterPlayer::SetTransform(const BAffineTransform& transform)
{
        fCallbacks->set_transform(fUserData, transform);
}


void
CallbackAdapterPlayer::TranslateBy(double x, double y)
{
        fCallbacks->translate_by(fUserData, x, y);
}


void
CallbackAdapterPlayer::ScaleBy(double x, double y)
{
        fCallbacks->scale_by(fUserData, x, y);
}


void
CallbackAdapterPlayer::RotateBy(double angleRadians)
{
        fCallbacks->rotate_by(fUserData, angleRadians);
}


void
CallbackAdapterPlayer::BlendLayer(Layer* layer)
{
        fCallbacks->blend_layer(fUserData, layer);
}


void
CallbackAdapterPlayer::ClipToRect(const BRect& rect, bool inverse)
{
        fCallbacks->clip_to_rect(fUserData, rect, inverse);
}


void
CallbackAdapterPlayer::ClipToShape(int32 opCount, const uint32 opList[],
        int32 ptCount, const BPoint ptList[], bool inverse)
{
        fCallbacks->clip_to_shape(fUserData, opCount, opList,
                        ptCount, ptList, inverse);
}


void
CallbackAdapterPlayer::DrawStringLocations(const char* _string, size_t length,
        const BPoint* locations, size_t locationCount)
{
        char* string = strndup(_string, length);

        fCallbacks->draw_string_locations(fUserData, string, locations,
                        locationCount);

        free(string);
}


void
CallbackAdapterPlayer::DrawRectGradient(const BRect& rect, BGradient& gradient, bool fill)
{
        if (fill)
                fCallbacks->fill_rect_gradient(fUserData, rect, gradient);
        else
                fCallbacks->stroke_rect_gradient(fUserData, rect, gradient);
}


void
CallbackAdapterPlayer::DrawRoundRectGradient(const BRect& rect, const BPoint& radii, BGradient& gradient,
        bool fill)
{
        if (fill)
                fCallbacks->fill_round_rect_gradient(fUserData, rect, radii, gradient);
        else
                fCallbacks->stroke_round_rect_gradient(fUserData, rect, radii, gradient);
}


void
CallbackAdapterPlayer::DrawBezierGradient(const BPoint _points[4], BGradient& gradient, bool fill)
{
        BPoint points[4] = { _points[0], _points[1], _points[2], _points[3] };

        if (fill)
                fCallbacks->fill_bezier_gradient(fUserData, points, gradient);
        else
                fCallbacks->stroke_bezier_gradient(fUserData, points, gradient);
}


void
CallbackAdapterPlayer::DrawArcGradient(const BPoint& center, const BPoint& radii,
        float startTheta, float arcTheta, BGradient& gradient, bool fill)
{
        if (fill)
                fCallbacks->fill_arc_gradient(fUserData, center, radii, startTheta, arcTheta, gradient);
        else
                fCallbacks->stroke_arc_gradient(fUserData, center, radii, startTheta, arcTheta, gradient);
}


void
CallbackAdapterPlayer::DrawEllipseGradient(const BRect& rect, BGradient& gradient, bool fill)
{
        BPoint radii((rect.Width() + 1) / 2.0f, (rect.Height() + 1) / 2.0f);
        BPoint center = rect.LeftTop() + radii;

        if (fill)
                fCallbacks->fill_ellipse_gradient(fUserData, center, radii, gradient);
        else
                fCallbacks->stroke_ellipse_gradient(fUserData, center, radii, gradient);
}


void
CallbackAdapterPlayer::DrawPolygonGradient(size_t numPoints, const BPoint _points[],
        bool isClosed, BGradient& gradient, bool fill)
{
        BStackOrHeapArray<BPoint, 200> points(numPoints);
        if (!points.IsValid())
                return;

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

        if (fill)
                fCallbacks->fill_polygon_gradient(fUserData, numPoints, points, isClosed, gradient);
        else
                fCallbacks->stroke_polygon_gradient(fUserData, numPoints, points, isClosed, gradient);
}


void
CallbackAdapterPlayer::DrawShapeGradient(const BShape& shape, BGradient& gradient, bool fill)
{
        if (fill)
                fCallbacks->fill_shape_gradient(fUserData, shape, gradient);
        else
                fCallbacks->stroke_shape_gradient(fUserData, shape, gradient);
}


void
CallbackAdapterPlayer::SetFillRule(int32 fillRule)
{
        fCallbacks->set_fill_rule(fUserData, fillRule);
}


void
CallbackAdapterPlayer::StrokeLineGradient(const BPoint& start, const BPoint& end, BGradient& gradient)
{
        fCallbacks->stroke_line_gradient(fUserData, start, end, gradient);
}


#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_STROKE_LINE_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)
{
        // 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];

        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;

                callBackTable = dummyTable;
        }

        CallbackAdapterPlayer callbackAdapterPlayer(userData, callBackTable);
        return _Play(callbackAdapterPlayer, fData, fSize, 0);
}


status_t
PicturePlayer::Play(PicturePlayerCallbacks& callbacks)
{
        return _Play(callbacks, 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(PicturePlayerCallbacks& callbacks,
        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 (!reader.Get(where))
                                        break;

                                callbacks.MovePenBy(*where);
                                break;
                        }

                        case B_PIC_STROKE_LINE:
                        {
                                const BPoint* start;
                                const BPoint* end;
                                if (!reader.Get(start) || !reader.Get(end))
                                        break;

                                callbacks.StrokeLine(*start, *end);
                                break;
                        }

                        case B_PIC_STROKE_RECT:
                        case B_PIC_FILL_RECT:
                        {
                                const BRect* rect;
                                if (!reader.Get(rect))
                                        break;

                                callbacks.DrawRect(*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 (!reader.Get(rect) || !reader.Get(radii))
                                        break;

                                callbacks.DrawRoundRect(*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 (!reader.Get(controlPoints, kNumControlPoints))
                                        break;

                                callbacks.DrawBezier(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 (!reader.Get(center)
                                        || !reader.Get(radii) || !reader.Get(startTheta)
                                        || !reader.Get(arcTheta)) {
                                        break;
                                }

                                callbacks.DrawArc(*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 (!reader.Get(rect))
                                        break;

                                callbacks.DrawEllipse(*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 (!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.DrawPolygon(*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 (!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.DrawShape(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 (!reader.Get(rect) || !reader.GetGradient(gradient))
                                        break;
                                ObjectDeleter<BGradient> gradientDeleter(gradient);

                                callbacks.DrawRectGradient(*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 (!reader.Get(rect)
                                        || !reader.Get(radii) || !reader.GetGradient(gradient)) {
                                        break;
                                }
                                ObjectDeleter<BGradient> gradientDeleter(gradient);

                                callbacks.DrawRoundRectGradient(*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 (!reader.Get(controlPoints, kNumControlPoints) || !reader.GetGradient(gradient))
                                        break;
                                ObjectDeleter<BGradient> gradientDeleter(gradient);

                                callbacks.DrawBezierGradient(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 (!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.DrawPolygonGradient(*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 (!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.DrawShapeGradient(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 (!reader.Get(center)
                                        || !reader.Get(radii) || !reader.Get(startTheta)
                                        || !reader.Get(arcTheta) || !reader.GetGradient(gradient)) {
                                        break;
                                }
                                ObjectDeleter<BGradient> gradientDeleter(gradient);

                                callbacks.DrawArcGradient(*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 (!reader.Get(rect) || !reader.GetGradient(gradient))
                                        break;
                                ObjectDeleter<BGradient> gradientDeleter(gradient);

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

                        case B_PIC_STROKE_LINE_GRADIENT:
                        {
                                const BPoint* start;
                                const BPoint* end;
                                BGradient* gradient;
                                if (!reader.Get(start)
                                        || !reader.Get(end) || !reader.GetGradient(gradient)) {
                                        break;
                                }

                                callbacks.StrokeLineGradient(*start, *end, *gradient);
                                break;
                        }

                        case B_PIC_DRAW_STRING:
                        {
                                const int32* length;
                                const char* string;
                                const escapement_delta* delta;
                                if (!reader.Get(length)
                                        || !reader.Get(string, *length)
                                        || !reader.Get(delta)) {
                                        break;
                                }

                                callbacks.DrawString(string, *length, *delta);
                                break;
                        }

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

                                callbacks.DrawStringLocations(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 int32* length;
                                const uint8* data;
                                if (!reader.Get(sourceRect)
                                        || !reader.Get(destinationRect) || !reader.Get(width)
                                        || !reader.Get(height) || !reader.Get(bytesPerRow)
                                        || !reader.Get(colorSpace) || !reader.Get(flags)
                                        || !reader.Get(length) || !reader.Get(data, *length)) {
                                        break;
                                }

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

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

                                callbacks.DrawPicture(*where, *token);
                                break;
                        }

                        case B_PIC_SET_CLIPPING_RECTS:
                        {
                                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.SetClippingRects(numRects, rects);
                                break;
                        }

                        case B_PIC_CLEAR_CLIPPING_RECTS:
                        {
                                callbacks.SetClippingRects(0, NULL);
                                break;
                        }

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

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

                        case B_PIC_PUSH_STATE:
                        {
                                callbacks.PushState();
                                break;
                        }

                        case B_PIC_POP_STATE:
                        {
                                callbacks.PopState();
                                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)
                                        callbacks.EnterStateChange();
                                else
                                        callbacks.EnterFontState();

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

                                if (header->op == B_PIC_ENTER_STATE_CHANGE)
                                        callbacks.ExitStateChange();
                                else
                                        callbacks.ExitFontState();

                                break;
                        }

                        case B_PIC_SET_ORIGIN:
                        {
                                const BPoint* origin;
                                if (!reader.Get(origin))
                                        break;

                                callbacks.SetOrigin(*origin);
                                break;
                        }

                        case B_PIC_SET_PEN_LOCATION:
                        {
                                const BPoint* location;
                                if (!reader.Get(location))
                                        break;

                                callbacks.SetPenLocation(*location);
                                break;
                        }

                        case B_PIC_SET_DRAWING_MODE:
                        {
                                const uint16* mode;
                                if (!reader.Get(mode))
                                        break;

                                callbacks.SetDrawingMode((drawing_mode)*mode);
                                break;
                        }

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

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

                        case B_PIC_SET_PEN_SIZE:
                        {
                                const float* penSize;
                                if (!reader.Get(penSize))
                                        break;

                                callbacks.SetPenSize(*penSize);
                                break;
                        }

                        case B_PIC_SET_FORE_COLOR:
                        {
                                const rgb_color* color;
                                if (!reader.Get(color))
                                        break;

                                callbacks.SetForeColor(*color);
                                break;
                        }

                        case B_PIC_SET_BACK_COLOR:
                        {
                                const rgb_color* color;
                                if (!reader.Get(color))
                                        break;

                                callbacks.SetBackColor(*color);
                                break;
                        }

                        case B_PIC_SET_STIPLE_PATTERN:
                        {
                                const pattern* stipplePattern;
                                if (!reader.Get(stipplePattern)) {
                                        break;
                                }

                                callbacks.SetStipplePattern(*stipplePattern);
                                break;
                        }

                        case B_PIC_SET_SCALE:
                        {
                                const float* scale;
                                if (!reader.Get(scale))
                                        break;

                                callbacks.SetScale(*scale);
                                break;
                        }

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

                                callbacks.SetFontFamily(family, *length);
                                break;
                        }

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

                                callbacks.SetFontStyle(style, *length);
                                break;
                        }

                        case B_PIC_SET_FONT_SPACING:
                        {
                                const uint32* spacing;
                                if (!reader.Get(spacing))
                                        break;

                                callbacks.SetFontSpacing(*spacing);
                                break;
                        }

                        case B_PIC_SET_FONT_SIZE:
                        {
                                const float* size;
                                if (!reader.Get(size))
                                        break;

                                callbacks.SetFontSize(*size);
                                break;
                        }

                        case B_PIC_SET_FONT_ROTATE:
                        {
                                const float* rotation;
                                if (!reader.Get(rotation))
                                        break;

                                callbacks.SetFontRotation(*rotation);
                                break;
                        }

                        case B_PIC_SET_FONT_ENCODING:
                        {
                                const uint32* encoding;
                                if (!reader.Get(encoding))
                                        break;

                                callbacks.SetFontEncoding(*encoding);
                                break;
                        }

                        case B_PIC_SET_FONT_FLAGS:
                        {
                                const uint32* flags;
                                if (!reader.Get(flags))
                                        break;

                                callbacks.SetFontFlags(*flags);
                                break;
                        }

                        case B_PIC_SET_FONT_SHEAR:
                        {
                                const float* shear;
                                if (!reader.Get(shear))
                                        break;

                                callbacks.SetFontShear(*shear);
                                break;
                        }

                        case B_PIC_SET_FONT_FACE:
                        {
                                const uint32* face;
                                if (!reader.Get(face))
                                        break;

                                callbacks.SetFontFace(*face);
                                break;
                        }

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

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

                        case B_PIC_SET_FILL_RULE:
                        {
                                const uint32* fillRule;
                                if (!reader.Get(fillRule))
                                        break;

                                callbacks.SetFillRule(*fillRule);
                                break;
                        }

                        case B_PIC_SET_TRANSFORM:
                        {
                                const double* transformValues;
                                if (!reader.Get(transformValues, 6))
                                        break;

                                BAffineTransform transform;
                                memcpy(&transform.sx, transformValues, 6 * sizeof(double));
                                callbacks.SetTransform(transform);
                                break;
                        }

                        case B_PIC_AFFINE_TRANSLATE:
                        {
                                const double* x;
                                const double* y;
                                if (!reader.Get(x) || !reader.Get(y))
                                        break;

                                callbacks.TranslateBy(*x, *y);
                                break;
                        }

                        case B_PIC_AFFINE_SCALE:
                        {
                                const double* x;
                                const double* y;
                                if (!reader.Get(x) || !reader.Get(y))
                                        break;

                                callbacks.ScaleBy(*x, *y);
                                break;
                        }

                        case B_PIC_AFFINE_ROTATE:
                        {
                                const double* angleRadians;
                                if (!reader.Get(angleRadians))
                                        break;

                                callbacks.RotateBy(*angleRadians);
                                break;
                        }

                        case B_PIC_BLEND_LAYER:
                        {
                                Layer* const* layer;
                                if (!reader.Get<Layer*>(layer))
                                        break;

                                callbacks.BlendLayer(*layer);
                                break;
                        }

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

                                if (!reader.Get(inverse) || !reader.Get(rect))
                                        break;

                                callbacks.ClipToRect(*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 (!reader.Get(inverse)
                                        || !reader.Get(opCount) || !reader.Get(pointCount)
                                        || !reader.Get(opList, *opCount)
                                        || !reader.Get(pointList, *pointCount)) {
                                        break;
                                }

                                callbacks.ClipToShape(*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;
}