/*->c.DrawCheck */

/****************************************************************************
 * This source file was written by Acorn Computers Limited. It is part of   *
 * the "DrawFile" library for rendering RISCOS Draw files from applications *
 * in C. It may be used freely in the creation of programs for Archimedes.  *
 * It should be used with Acorn's C Compiler Release 2 or later.            *
 *                                                                          *
 * No support can be given to programmers using this code and, while we     *
 * believe that it is correct, no correspondence can be entered into        *
 * concerning behaviour or bugs.                                            *
 *                                                                          *
 * Upgrades of this code may or may not appear, and while every effort will *
 * be made to keep such upgrades upwards compatible, no guarantees can be   *
 * given.                                                                   *
 ***************************************************************************/

/* -> c.drawCheck
 *
 * DrawFile module, internal code
 * History:
 * Version 0.1: 15 Feb 89, DAHE: created
 *
 * This file consists of code taken from the !Draw version of c.drawCheck
 * (0.51), for use in the DrawFile module (release 0.2).
 *
 * Purpose: 1. check an Draw file for consistency
 *          2. shift all coordinates in an Draw file
 *
 * Checks a Draw file held in a buffer, and reports any problems, with the
 * offset from the start of the buffer. No error is flagged on an unknown
 * object type.
 *
 * The (global) ok flag can indicate no error, recoverable error or fatal
 * error.
 * A fatal error is generally some sort of mistake in an object size.
 * On a fatal error, we return as soon as possible. The returned buffer
 * location need not be sensible.
 *
 * Note that the code here must be changed if there are any changes in the
 * internal structure of an object.
 */

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

#include "os.h"
#include "sprite.h"

#include "DrawFile1.h"
#include "DrawErrors.h"

/*-------------------------------------------------------------------------*/
/* Common stuff - both shift and check                                     */

/* Types for check/shift functions */
typedef void check_shift_fn(draw_objptr object);

typedef struct
{
    draw_tagtyp  tag;                /* Tag */
    int          sizelow, sizehigh;  /* Bounds on size */
    BOOL         box;                /* Flag - object has a bbox */
    check_shift_fn *fn;              /* Function to call, or NULL */
} check_shift_table;

/* Macro to help declare them */
#define Check_shift_fn(name)  void name(draw_objptr object)

/*-------------------------------------------------------------------------*/
/* Code for checking Draw files                                         */

/* Errors are represented as an integer. The type Draw_error1 must be made
   consistent with these values; we do not include the header file here, to
   avoid the DrawFile module invading the Draw source
 */
static int errorCode;
static int errorLocation;
#define error(code, at, level) {errorCode = code; errorLocation = (int)at; \
                                check_ok |= level;}

/* Error levels */
#define OK_OK    0
#define OK_ERROR 1
#define OK_FATAL 2

/* Flag for indicating errors */
static int check_ok;

/* 'Infinite' size value */
#define check_BIG (0x7fffffff)

/* Status flags */
static BOOL check_fontSeen, check_textSeen;

/* Forward reference */
draw_objptr check_object(draw_objptr object);

/*
 Function    : check_bbox
 Purpose     : check a bounding box
 Parameters  : bounding box pointer
 Returns     : void
 Description : checks that the coordinate in the box are in the right order.
*/

void  check_bbox(draw_bboxtyp *box)
{
    if (box->x0 > box->x1 || box->y0 > box->y1)
        error(Draw_BBoxWrong, (char *)box, OK_ERROR)
}

/*
 Function    : check_text
 Purpose     : check a string
 Parameters  : pointer to string
               maximum length
 Returns     : length of string
 Description : check the string for control characters, etc.
*/

int  check_text(char *buffer, int length)
{
    int  i;
    int  c;

    for (i = 0 ; i < length ; i++)
    {
        if (buffer[i] == 0) return (i);

        c = (int)buffer[i];
        if (!((31 < c && c < 127) || (127 < c && c <= 255)))
            error(Draw_BadCharacter, buffer, OK_ERROR)
    }

    return (length);
}

/*
 Function    : check_size
 Purpose     : check an object size
 Parameters  : object size
               lower bound
               upper bound (may be 'check_BIG' for any size)
               pointer to location for error report
 Returns     : void
*/

void check_size(int size, int low, int high, char *where)
{
    if (size < low)
        error(Draw_ObjectTooSmall, where, OK_FATAL)
    if (size > high)
        error(Draw_ObjectTooLarge, where, OK_FATAL)
    if (size & 3 != 0)
        error(Draw_ObjectNotMult4, where, OK_FATAL)
}

/*
 Function    : check_overrun
 Purpose     : check for data overrun
 Parameters  : remaining object size
               pointer to current location for error report
 Returns     : void
 Description :
*/

void check_overrun(int size, char *where)
{
    if (size < 0)
        error(Draw_ObjectOverrun, where, OK_FATAL)
}

/*--------------------------------------------------------------------------*/

/*
 Function    : check_<object>
 Purpose     : general object check routines
 Parameters  : pointer to start of object
 Returns     : void
*/

/* Font table: check we have only one, that it is after text, and that there
   is no rubbish at the end of it
 */

Check_shift_fn(check_fontList)
{
    int fontNum;
    int ptr  = 8;              /* Skip over type and size */
    int size;

    size = object.fontlistp->size;

    if (check_fontSeen)
        error(Draw_ManyFontTables, object.bytep, OK_ERROR)
    if (check_textSeen)
        error(Draw_LateFontTable, object.bytep, OK_ERROR)

    check_fontSeen = TRUE;

    while (ptr < size)
    {
        if ((fontNum = (int)object.bytep[ptr++]) == 0) break;
        ptr += check_text(object.bytep+ptr, check_BIG);
    }

    check_overrun(size - ptr, object.bytep + ptr);
}

/* Text: check style, text */
Check_shift_fn(check_textObject)
{
    int  ptr;

    check_textSeen = TRUE;

    if (object.textp->textstyle.reserved8 != 0
        || object.textp->textstyle.reserved16 != 0)
        error(Draw_BadTextStyle, object.bytep, OK_ERROR)
    ptr = sizeof(draw_textstrhdr) + check_text((char*)(&(object.textp->text)), check_BIG);

    check_overrun(object.textp->size - ptr, object.bytep + ptr);
}

/* Path: check elements of path. Must start with move, and have a line or
   curve in it somewhere */
Check_shift_fn(check_pathObject)
{
    path_eleptr path;
    int  extra;
    int  lineSeen = FALSE;

    path.move = address_pathstart(object);

    if (path.move->tag.tag != Draw_PathMOVE)
        error(Draw_MoveMissing, object.bytep, OK_ERROR)

    do
    {
        switch (path.move->tag.tag)
        {
            case Draw_PathTERM : break;
            case Draw_PathMOVE : path.bytep += sizeof(path_movestr)  ; break;
            case Draw_PathCLOSE: path.bytep += sizeof(path_closestr) ; break;
            case Draw_PathCURVE: path.bytep += sizeof(path_curvestr);
                                 lineSeen = TRUE; break;
            case Draw_PathLINE : path.bytep += sizeof(path_linestr);
                                 lineSeen = TRUE; break;
            default:

                error(Draw_BadPathTag, path.bytep, OK_FATAL)
                return;
        }
    } while (path.move->tag.tag != Draw_PathTERM);

    if (!lineSeen)
        error(Draw_NoPathElements, object.bytep, OK_ERROR)

    extra = object.pathp->size - 
                  (path.bytep - object.bytep + sizeof(path_termstr));
    if (extra > 0)
        error(Draw_PathExtraData, object.bytep, OK_FATAL)
    else
        check_overrun(extra, path.bytep);
}





/* Sprite: check size (based on minimum size for a sprite definition),
   and sprite header block */
Check_shift_fn(check_spriteObject)
{
    int  spriteSize;

    spriteSize = object.spritep->sprite.next;
    if (object.spritep->size - sizeof(draw_objhdr) < spriteSize)
     error(Draw_BadSpriteSize, object.bytep, OK_FATAL)
}

Check_shift_fn(check_groupObject)
{
    char *end;

    end = object.bytep + object.groupp->size;
    object.bytep += sizeof(draw_groustr);

    while ((check_ok & OK_FATAL) == 0 && object.bytep < end)
        object = check_object(object);
}

#ifdef draw_OBJTAGG
Check_shift_fn(check_tagObject)
{
    char *end;

    end = check_object(object.bytep + 28);
    check_overrun((int)(end - object.bytep) - object.objhdrp->size, end);
}
#endif

/* Text column: check size and tag. TRUE if it was a text column */
BOOL check_textColumn(draw_textcolhdr *column)
{
    if (column->tag == 0) return (FALSE);
    else if (column->tag != draw_OBJTEXTCOL)
    {
        error(Draw_BadTextColumnEnd, column, OK_FATAL)
        return (FALSE);
    }

    check_size(column->size, sizeof(draw_textcolhdr), sizeof(draw_textcolhdr), 
               (char *)column);
    return((check_ok & OK_FATAL) == 0);
}

/* Text area: check size and verify using text column code */
Check_shift_fn(check_textArea)
{
    int  columns, actualColumns;
    draw_objptr column, area;
    int code;
    char *location;

    for (column.textcolp = &(object.textareastrp->column), actualColumns = 0 ;
         check_ok != OK_FATAL && check_textColumn(column.textcolp) ;
         column.bytep += sizeof(draw_textcolhdr), actualColumns += 1) ;

    if (!draw_verifyTextArea(sizeof(draw_textareastrend) + column.bytep,
         &code, &location, &columns) || columns < 1)
     {
       error(code, location, OK_ERROR)
     }
    else if (columns != actualColumns)
    {
        error(Draw_ColumnsMismatch, object.bytep, OK_ERROR)
    }
    else /* Ensure reserved words are zero */
    {
        area.bytep = column.bytep;
        if (area.textareaendp->blank1 != 0 || area.textareaendp->blank2 != 0)
            error(Draw_NonZeroReserved, object.bytep, OK_ERROR)
    }
}




/*-------------------------------------------------------------------------*/

/*
 Function    : check_fileHeader
 Purpose     : check Draw file header
 Parameters  : object pointer
 Returns     : pointer to object after header
 Description : checks the file header for:
   - not being an Draw file
   - bad version
   - bad bbox
*/

draw_objptr check_fileHeader(draw_objptr object)
{
    int drawName = 0x77617244;      /* the text 'Draw' as an integer */

    if (object.wordp[0] != drawName)
    {
        error(Draw_NotDrawFile, 0, OK_FATAL)
    }
    else
    {
        if (object.filehdrp->majorstamp > majorformatversionstamp)
        {
            error(Draw_VersionTooHigh, 0, OK_FATAL)
        }
    }

    object.bytep += sizeof(draw_fileheader);
    return (object);
}

/*
 Data Group  : functions descriptions table
 Description :
*/

check_shift_table check_functions[] =
{
 {draw_OBJFONTLIST, sizeof(draw_fontliststrhdr), check_BIG, FALSE, 
   check_fontList},
 {draw_OBJTEXT,     sizeof(draw_textstrhdr),     check_BIG, TRUE,
   check_textObject},
 {draw_OBJPATH,     sizeof(draw_pathstrhdr),     check_BIG, TRUE,
   check_pathObject},



#ifdef draw_OBJRECT
 {draw_OBJRECT,     sizeof(draw_objhdr),         sizeof(draw_objhdr), TRUE,
   NULL},
#endif
#ifdef draw_OBJELLI
 {draw_OBJELLI,     sizeof(draw_objhdr),         sizeof(draw_objhdr), TRUE,
   NULL},
#endif
 {draw_OBJSPRITE,   sizeof(draw_spristrhdr),     check_BIG, TRUE,
   check_spriteObject},
 {draw_OBJGROUP,    sizeof(draw_groustr),        check_BIG, TRUE,
   check_groupObject},
#ifdef draw_OBJTAGG
 {draw_OBJTAGG,     sizeof(draw_objhdr),         check_BIG, TRUE,
   check_tagObject},
#endif
 {draw_OBJTEXTAREA, sizeof(draw_textareastrhdr), check_BIG, TRUE,
   check_textArea},




 {-1,               0,  0,         FALSE, NULL}
};

/*
 Function    : check_object
 Purpose     : check an Draw object
 Parameters  : pointer to object data
 Returns     : pointer to next object
*/

draw_objptr check_object(draw_objptr object)
{
    draw_tagtyp type;
    check_shift_table *c;

    type = object.objhdrp->tag;

    for (c = check_functions ; c->tag != -1 ; c++)
    {
        if (c->tag == type)
        {
            /* Check size */
            check_size(object.objhdrp->size, c->sizelow, c->sizehigh,
                       object.bytep);
            if (check_ok & OK_FATAL)
                object.bytep = NULL;
            else
            {
                /* Check bbox */
                if (c->box) check_bbox(&(object.objhdrp->bbox));

                /* Additional checks */
                if (c->fn) (c->fn)(object);
            }
            break;
        }
    }

    object.bytep += object.objhdrp->size;
    return (object);
}

/*
 Function    : check_Draw_object
 Purpose     : check an individual object (for DrawFile module)
 Parameters  : object pointer
               OUT: error code
               OUT: error location
 Returns     : TRUE if object is OK
*/

BOOL check_Draw_object(draw_objptr object, int *code, int *location)
{
    check_fontSeen = check_textSeen = FALSE;
    check_ok       = OK_OK;

    check_object(object);

    if (check_ok == OK_OK)
      return (TRUE);
    else
    {
      *code     = errorCode;
      *location = errorLocation;
      return (FALSE);
    }
}

/*
 Function    : check_Draw_file
 Purpose     : check Draw file (for DrawFile module)
 Parameters  : pointer to buffer
               length of buffer
               OUT: code, location
 Returns     : TRUE if ok
*/

BOOL check_Draw_file(char *buffer, int length, int *code, int *location)
{
    draw_objptr object;
    char *end = buffer + length;

    check_fontSeen = check_textSeen = FALSE;
    check_ok       = OK_OK;
    object.bytep   = buffer;
    object         = check_fileHeader(object);

    while ((check_ok & OK_FATAL) == 0 && object.bytep < end)
        object = check_object(object);

    if (check_ok == OK_OK)
    {
      return (TRUE);
    }
    else
    {
        *code     = errorCode;
        *location = errorLocation - (int)buffer;
        return (FALSE);
    }
}






/*-------------------------------------------------------------------------*/
/* Code for shifting files                                                 */

/* Globals to hold the current base */
static int shift_x, shift_y;

/* Forward reference */
draw_objptr shift_object(draw_objptr object);

/*
 Function    : shift_coord
 Purpose     : shift a coordinate
 Parameters  : pointer to x (y assumed to be 1 word after x)
 Returns     : void
*/

void shift_coord(int *at)
{
    at[0] += shift_x;
    at[1] += shift_y;
}

/*
 Function    : shift_bbox
 Purpose     : shift a bounding box
 Parameters  : box pointer
 Returns     : void
 Description : shifts each coordinate
*/

void  shift_bbox(draw_bboxtyp *box)
{
    box->x0 += shift_x;
    box->y0 += shift_y;
    box->x1 += shift_x;
    box->y1 += shift_y;
}

/* Text - shift base location */
Check_shift_fn(shift_textObject)
{
    shift_coord(&(object.textp->coord.x));
}

/* Path: shift each element of path */
Check_shift_fn(shift_pathObject)
{
    path_eleptr path;

    path.move = address_pathstart(object);
    do
    {
        switch (path.move->tag.tag)
        {
            case Draw_PathTERM:
                path.bytep += sizeof(path_termstr); break;
            case Draw_PathMOVE: 
                shift_coord(&(path.move->x)); 
                path.bytep += sizeof(path_movestr); break;
            case Draw_PathLINE:
                shift_coord(&(path.line->x));
                path.bytep += sizeof(path_linestr); break;
            case Draw_PathCLOSE:
                path.bytep += sizeof(path_closestr); break;
            case Draw_PathCURVE:
                shift_coord(&(path.curve->x1));
                shift_coord(&(path.curve->x2));
                shift_coord(&(path.curve->x3));
                path.bytep += sizeof(path_curvestr); break;
        }
    } while (path.move->tag.tag != Draw_PathTERM);
}

/* Group - recurse on contents */
Check_shift_fn(shift_groupObject)
{
    char *end;

    end = object.bytep + object.groupp->size;
    object.bytep += sizeof(draw_groustr);

    while (object.bytep < end)
        object = shift_object(object);
}

#ifdef draw_OBJTAGG
/* Tagged - recurse */
Check_shift_fn(shift_tagObject)
{
    shift_object(object.bytep + 28);
}
#endif

/* Text area: shift each column */
Check_shift_fn(shift_textArea)
{
    /* Point to first column */
    for (object.bytep += sizeof(draw_textareahdr) ; 
         object.objhdrp->tag == draw_OBJTEXTCOL ;
         object.bytep += sizeof(draw_textcolhdr))
    {
        shift_bbox(&(object.textcolp->bbox));
    }
}

/* Functions table - size fields are not used */
check_shift_table shift_functions[] =
{
 {draw_OBJFONTLIST, 0, 0, FALSE, NULL},
 {draw_OBJTEXT,     0, 0, TRUE,  shift_textObject},
 {draw_OBJPATH,     0, 0, TRUE,  shift_pathObject},
#ifdef draw_OBJRECT
 {draw_OBJRECT,     0, 0, TRUE,  NULL},
#endif
#ifdef draw_OBJELLI
 {draw_OBJELLI,     0, 0, TRUE,  NULL},
#endif
 {draw_OBJSPRITE,   0, 0, TRUE,  NULL},
 {draw_OBJGROUP,    0, 0, TRUE,  shift_groupObject},
#ifdef draw_OBJTAGG
 {draw_OBJTAGG,     0, 0, TRUE,  shift_tagObject},
#endif
 {draw_OBJTEXTAREA, 0, 0, TRUE,  shift_textArea},
 {-1,               0, 0, FALSE, NULL}
};

/*
 Function    : shift_object
 Purpose     : shift an arc draw object
 Parameters  : object pointer
 Returns     : pointer to next object
*/

draw_objptr shift_object(draw_objptr object)
{
    draw_tagtyp type;
    check_shift_table *s;

    type = object.objhdrp->tag;

    for (s = shift_functions ; s->tag != -1 ; s++)
    {
        if (s->tag == type)
        {
            /* Shift bbox */
            if (s->box) shift_bbox(&(object.objhdrp->bbox));

            /* Additional shifting */
            if (s->fn) (s->fn)(object);
            break;
        }
    }

    object.bytep += object.objhdrp->size;
    return (object);
}

/*
 Function    : shift_Draw_file
 Purpose     : transform all coordinates to a new origin
 Parameters  : pointer to buffer
               buffer length
               x base, y base
 Returns     : void
 Description : this shifts all coordinates in the Draw file held in the
               buffer to the given base. It uses code similar to the checking
               code to follow the structure of the file.
               Assumes the buffer has already been checked.
*/

void shift_Draw_file(char *buffer, int length, int xMove, int yMove)
{
    draw_objptr object;
    char *end = buffer + length;

    shift_x = xMove;
    shift_y = yMove;

    object.bytep = buffer;
    shift_bbox(&object.filehdrp->bbox);    

    object.bytep = buffer + sizeof(draw_fileheader);

    while (object.bytep < end)
        object = shift_object(object);
}








