#help_index "Graphics/Sprite;Sprites"

U0 SpriteElem2Code(CDoc *doc, CSprite *tmpg)
{
    U8           buf1[STR_LEN], buf2[STR_LEN];
    I32         *ptr;
    I64          i, j, k, col, width_internal;
    CD3I32      *p;
    CMeshTri    *tri;

    if (!doc)
        doc = DocPut;
    DocPrint(doc, "%Z", tmpg->type & SPG_TYPE_MASK, "ST_SPRITE_ELEM_CODES");
    switch (tmpg->type & SPG_TYPE_MASK)
    {
        case SPT_COLOR:
            DocPrint(doc, "{%s}", Color2Str(buf1, tmpg->c.color));
            break;

        case SPT_DITHER_COLOR:
            DocPrint(doc, "{%s}", Color2Str(buf2,
                                    ROPF_DITHER | tmpg->d.dither_color.u8[0] | tmpg->d.dither_color.u8[1] << COLORROP_BITS));
            break;

        case SPT_TRANSFORM_ON:
            DocPrint(doc, "{ON}");
            break;

        case SPT_TRANSFORM_OFF:
            DocPrint(doc, "{OFF}");
            break;

        case SPT_LINE:
        case SPT_ARROW:
        case SPT_PLANAR_SYMMETRY:
            DocPrint(doc, "{(%d,%d),(%d,%d)}", tmpg->pp.x1, tmpg->pp.y1, tmpg->pp.x2, tmpg->pp.y2);
            break;

        case SPT_RECT:
            DocPrint(doc, "{(%d,%d):(%d,%d)}", tmpg->pp.x1, tmpg->pp.y1,
                                               tmpg->pp.x2 - tmpg->pp.x1, tmpg->pp.y2 - tmpg->pp.y1);
            break;

        case SPT_ROTATED_RECT:
            DocPrint(doc, "{(%d,%d):(%d,%d),%0.4f}", tmpg->ppa.x1, tmpg->ppa.y1, 
                                                     tmpg->ppa.x2 - tmpg->ppa.x1, tmpg->ppa.y2 - tmpg->ppa.y1,
                                                     180 / pi * Wrap(tmpg->ppa.angle));
            break;

        case SPT_PT:
        case SPT_FLOOD_FILL:
        case SPT_SHIFT:
            DocPrint(doc, "{(%d,%d)}", tmpg->p.x1, tmpg->p.y1);
            break;

        case SPT_FLOOD_FILL_NOT:
            DocPrint(doc, "{(%d,%d),TRUE}", tmpg->p.x1, tmpg->p.y1);
            break;

        case SPT_CIRCLE:
            DocPrint(doc, "{(%d,%d):%d}", tmpg->pr.x1, tmpg->pr.y1, tmpg->pr.radius);
            break;

        case SPT_THICK:
            DocPrint(doc, "{%d}", tmpg->t.thick);
            break;

        case SPT_ELLIPSE:
            DocPrint(doc, "{(%d,%d):(%d,%d),%0.4f}", tmpg->pwha.x1, tmpg->pwha.y1, tmpg->pwha.width, tmpg->pwha.height,
                                                     180 / pi * Wrap(tmpg->pwha.angle));
            break;

        case SPT_POLYGON:
            DocPrint(doc, "{%d,(%d,%d):(%d,%d),%0.4f}", tmpg->pwhas.sides, tmpg->pwhas.x1, tmpg->pwhas.y1, 
                                                        tmpg->pwhas.width, tmpg->pwhas.height, 
                                                        180 / pi * Wrap(tmpg->pwhas.angle));
            break;

        case SPT_TEXT:
        case SPT_TEXT_BOX:
        case SPT_TEXT_DIAMOND:
            DocPrint(doc, "{(%d,%d),\"%Q\"}", tmpg->ps.x1, tmpg->ps.y1, tmpg->ps.st);
            break;

        case SPT_POLYLINE:
            ptr = &tmpg->nu.u;
            DocPrint(doc, "{");
            for (i = 0; i < tmpg->nu.num; i++, ptr += 2)
            {
                DocPrint(doc, "(%d,%d)", ptr[0], ptr[1]);
                if (i + 1 < tmpg->nu.num)
                    DocPrint(doc, ",");
                if (i & 3 == 3 && i + 1 < tmpg->nu.num)
                    DocPrint(doc, "\n");
            }
            DocPrint(doc, "}");
            break;

        case SPT_BSPLINE2:
        case SPT_BSPLINE3:
        case SPT_BSPLINE2_CLOSED:
        case SPT_BSPLINE3_CLOSED:
            ptr = &tmpg->nu.u;
            DocPrint(doc, "{");
            for (i = 0; i < tmpg->nu.num; i++, ptr += 3)
            {
                DocPrint(doc, "(%d,%d,%d)", ptr[0], ptr[1], ptr[2]);
                if (i + 1 < tmpg->nu.num)
                    DocPrint(doc, ",");
                if (i & 3 == 3 && i + 1 < tmpg->nu.num)
                    DocPrint(doc, "\n");
            }
            if (tmpg->type & SPG_TYPE_MASK == SPT_BSPLINE2 || tmpg->type & SPG_TYPE_MASK == SPT_BSPLINE3)
                DocPrint(doc, ",FALSE}");
            else
                DocPrint(doc, ",TRUE}");
            break;

        case SPT_POLYPT:
            DocPrint(doc, "{(%d,%d),", tmpg->npu.x, tmpg->npu.y);
            ptr = &tmpg->npu.u;
            col = 16;
            for (i= 0 ; i < tmpg->npu.num; i++)
            {
                DocPrint(doc, "%d", BFieldExtU32(ptr, i * 3, 3));
                if (++col >= 64 && i + 1 < tmpg->npu.num)
                {
                    DocPrint(doc, "\n");
                    col = 0;
                }
            }
            DocPrint(doc, "}");
            break;

        case SPT_BITMAP:
            DocPrint(doc, "{(%d,%d):(%d,%d),\n", tmpg->pwhu.x1, tmpg->pwhu.y1, tmpg->pwhu.width, tmpg->pwhu.height);
            width_internal = (tmpg->pwhu.width + 7) & ~7;
            if (width_internal < 80)
                k = width_internal;
            else
                k = 64;
            ptr = &tmpg->pwhu.u;
            col = 0;
            for (j = 0; j < tmpg->pwhu.height; j++)
                for (i = 0; i < width_internal; i++, ptr(U8 *)++)
                {
                    if (i >= tmpg->pwhu.width)
                        DocPrint(doc, "_");
                    else if (*ptr(U8 *) < 16)
                        DocPrint(doc, "%X", *ptr(U8 *));
                    else
                        DocPrint(doc, " ");
                    if (++col >= k && (i + 1 < width_internal || j + 1 < tmpg->pwhu.height))
                    {
                        DocPrint(doc, "\n");
                        col = 0;
                    }
                }
            DocPrint(doc, "}");
            break;

        case SPT_MESH:
            DocPrint(doc, "{FALSE,");
            p = &tmpg->mu.u;
            col = 0;
            for (i = 0; i < tmpg->mu.vertex_count; i++, p++)
            {
                DocPrint(doc, "(%d,%d,%d)", p->x, p->y, p->z);
                if (i + 1 < tmpg->mu.vertex_count)
                    DocPrint(doc, ",");
                if (++col == 4) {
                    DocPrint(doc, "\t//%d\n", i);
                    col = 0;
                }
            }
            DocPrint(doc, ":");
            tri = p;
            for (i = 0; i < tmpg->mu.tri_count; i++, tri++)
            {
                DocPrint(doc, "(%s,%d,%d,%d)", Color2Str(buf1, tri->color), tri->nums[0], tri->nums[1], tri->nums[2]);
                if (i + 1 < tmpg->mu.tri_count)
                    DocPrint(doc, ",");
                if (++col >= 3 && i + 1 < tmpg->mu.tri_count)
                {
                    DocPrint(doc, "\n");
                    col = 0;
                }
            }
            DocPrint(doc, "}");
            break;

        case SPT_SHIFTABLE_MESH:
            DocPrint(doc, "{TRUE,(%d,%d,%d):", tmpg->pmu.x, tmpg->pmu.y, tmpg->pmu.z);
            p = &tmpg->pmu.u;
            col = 1;
            for (i = 0; i < tmpg->pmu.vertex_count; i++, p++)
            {
                DocPrint(doc, "(%d,%d,%d)", p->x, p->y, p->z);
                if (i + 1 < tmpg->pmu.vertex_count)
                    DocPrint(doc, ",");
                if (++col == 4)
                {
                    DocPrint(doc, "\t//%d\n", i);
                    col = 0;
                }
            }
            DocPrint(doc, ":");
            tri = p;
            for (i = 0; i < tmpg->pmu.tri_count; i++, tri++)
            {
                DocPrint(doc, "(%s,%d,%d,%d)", Color2Str(buf1, tri->color), tri->nums[0], tri->nums[1], tri->nums[2]);
                if (i + 1 < tmpg->pmu.tri_count)
                    DocPrint(doc, ",");
                if (++col >= 3 && i + 1 < tmpg->pmu.tri_count)
                {
                    DocPrint(doc, "\n");
                    col = 0;
                }
            }
            DocPrint(doc, "}");
            break;
    }
    DocPrint(doc, ";\n");
}

public U0 Sprite2Code(CDoc *doc=NULL, U8 *elems)
{//Sprite to text.
    CSprite *tmpg = elems - offset(CSprite.start);

    while (tmpg->type & SPG_TYPE_MASK)
    {
        SpriteElem2Code(doc, tmpg);
        tmpg(U8 *) += SpriteElemSize(tmpg);
    }
}

CSprite *Code2SpriteElem(CCompCtrl *cc, I64 type)
{
    I64              i, num1, num2, size;
    CSprite         *res, g;
    CColorROPU32     color;
    U8              *st, *ptr;
    CQueueD3I32      headp, *tmpp, *tmpa1;
    CQueueMeshTri    headt, *tmpt, *tmpt1;
    CQueueVectU8    *tmpv;

    MemSet(&g, 0, sizeof(CSprite));
    switch (type)
    {
        start:
            case SPT_COLOR:
            case SPT_DITHER_COLOR:
                st = LexFirstRemove(cc, "}");
                color = Str2ColorU32(st);
                Free(st);
                Lex(cc); //Skip color
                g.c.color = color.c0.color;
                if (color & ROPF_DITHER)
                {
                    g.d.dither_color.u8[1] = color.c1.color;
                    g.type = SPT_DITHER_COLOR;
                }
                else
                    g.type = SPT_COLOR;
                break;

            case SPT_TRANSFORM_ON:
            case SPT_TRANSFORM_OFF:
                Lex(cc); //Skip {
                if (LexExpressionI64(cc))
                    g.type = SPT_TRANSFORM_ON;
                else
                    g.type = SPT_TRANSFORM_OFF;
                break;

            case SPT_LINE:
            case SPT_ARROW:
            case SPT_PLANAR_SYMMETRY:
                Lex(cc); //Skip {
                g.type = type;
                LexD2I32(cc, &g.pp.x1);
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");
                Lex(cc); //Skip ,
                LexD2I32(cc, &g.pp.x2);
                break;

            case SPT_RECT:
            case SPT_ROTATED_RECT:
                Lex(cc); //Skip {
                LexD2I32(cc, &g.pp.x1);
                if (cc->token != ':')
                    LexExcept(cc, "Expecting ':' at ");
                Lex(cc); //Skip :
                LexD2I32(cc, &g.pp.x2);
                g.ppa.x2 += g.pp.x1;
                g.ppa.y2 += g.pp.y1;
                if (cc->token == ',')
                {
                    Lex(cc); //Skip ,
                    g.ppa.angle = pi / 180 * LexExpressionF64(cc);
                    g.type = SPT_ROTATED_RECT;
                }
                else
                    g.type = SPT_RECT;
                break;

            case SPT_PT:
            case SPT_SHIFT:
                Lex(cc); //Skip {
                g.type = type;
                LexD2I32(cc, &g.p.x1);
                break;

            case SPT_FLOOD_FILL:
            case SPT_FLOOD_FILL_NOT:
                Lex(cc); //Skip {
                LexD2I32(cc, &g.p.x1);
                if (cc->token == ',')
                {
                    Lex(cc); //Skip ,
                    i = LexExpressionI64(cc);
                }
                else
                    i = 0;
                if (i)
                    g.type = SPT_FLOOD_FILL_NOT;
                else
                    g.type = SPT_FLOOD_FILL;
                break;

            case SPT_THICK:
                Lex(cc); //Skip {
                g.t.thick = LexExpressionI64(cc);
                g.type = SPT_THICK;
                break;

            case SPT_CIRCLE:
                Lex(cc); //Skip {
                g.type = SPT_CIRCLE;
                LexD2I32(cc, &g.pr.x1);
                if (cc->token != ':')
                    LexExcept(cc, "Expecting ':' at ");
                Lex(cc); //Skip :
                g.pr.radius = LexExpressionI64(cc);
                break;

            case SPT_POLYGON:
                Lex(cc); //Skip {
                g.pwhas.sides = LexExpressionI64(cc);
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");

            case SPT_ELLIPSE:
                Lex(cc); //Skip {
                g.type = type;
                LexD2I32(cc, &g.pwha.x1);
                if (cc->token != ':')
                    LexExcept(cc, "Expecting ':' at ");
                Lex(cc); //Skip :
                LexD2I32(cc, &g.pwha.width);
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");
                Lex(cc); //Skip ,
                g.pwha.angle = pi / 180 * LexExpressionF64(cc);
                break;
        end:
            size = SpriteElemSize(&g) + offset(CSprite.start);
            res = MAlloc(size);
            MemCopy(res, &g, size);
            break;

        case SPT_TEXT:
        case SPT_TEXT_BOX:
        case SPT_TEXT_DIAMOND:
            Lex(cc); //Skip {
            g.type = type;
            LexD2I32(cc, &g.ps.x1);
            if (cc->token != ',')
                LexExcept(cc, "Expecting ',' at ");
            if (Lex(cc) == TK_STR)  //Skip ,
                st = LexExtStr(cc);
            else
                LexExcept(cc, "Expecting string at ");
            size = SpriteElemQueuedBaseSize(type);
            i = StrLen(st) + 1;
            res = MAlloc(size + i);
            MemCopy(res, &g, size);
            MemCopy(res(U8 *) + size, st, i);
            Free(st);
            break;

        case SPT_POLYLINE:
            Lex(cc); //Skip {
            g.type = SPT_POLYLINE;
            QueueInit(&headp);
            while (cc->token == '(') {
                tmpp = CAlloc(sizeof(CQueueD3I32));
                LexD2I32(cc, &tmpp->p);
                QueueInsert(tmpp, headp.last);
                g.nu.num++;
                if (cc->token == ',')
                    Lex(cc); //Skip ,
            }
            if (g.nu.num < 2)
                LexExcept(cc, "Expecting point at ");
            size = SpriteElemQueuedBaseSize(SPT_POLYLINE);
            res = MAlloc(size + g.nu.num * sizeof(CD2I32));
            MemCopy(res, &g, size);
            ptr = &res->nu.u;
            tmpp = headp.next;
            while (tmpp != &headp)
            {
                tmpa1 = tmpp->next;
                MemCopy(ptr, &tmpp->p, sizeof(CD2I32));
                ptr += sizeof(CD2I32);
                Free(tmpp);
                tmpp = tmpa1;
            }
            break;

        case SPT_BSPLINE2:
        case SPT_BSPLINE3:
        case SPT_BSPLINE2_CLOSED:
        case SPT_BSPLINE3_CLOSED:
            Lex(cc); //Skip {
            QueueInit(&headp);
            while (cc->token == '(') {
                tmpp = CAlloc(sizeof(CQueueD3I32));
                LexD3I32(cc, &tmpp->p);
                QueueInsert(tmpp, headp.last);
                g.nu.num++;
                if (cc->token == ',')
                    Lex(cc); //Skip ,
            }
            if (g.nu.num < 2)
                LexExcept(cc, "Expecting point at ");
            size = SpriteElemQueuedBaseSize(type);
            res = MAlloc(size + g.nu.num * sizeof(CD3I32));
            if (LexExpressionI64(cc))
            {
                if (type == SPT_BSPLINE2 || type == SPT_BSPLINE2_CLOSED)
                    g.type = SPT_BSPLINE2_CLOSED;
                else
                    g.type = SPT_BSPLINE3_CLOSED;
            }
            else
            {
                if (type == SPT_BSPLINE2 || type == SPT_BSPLINE2_CLOSED)
                    g.type = SPT_BSPLINE2;
                else
                    g.type = SPT_BSPLINE3;
            }
            MemCopy(res, &g, size);
            ptr = &res->nu.u;
            tmpp = headp.next;
            while (tmpp != &headp)
            {
                tmpa1 = tmpp->next;
                MemCopy(ptr, &tmpp->p, sizeof(CD3I32));
                ptr += sizeof(CD3I32);
                Free(tmpp);
                tmpp = tmpa1;
            }
            break;

        case SPT_POLYPT:
            Lex(cc); //Skip {
            LexD2I32(cc, &g.npu.x);
            if (cc->token != ',')
                LexExcept(cc, "Expecting ',' at ");
            tmpv = QueueVectU8New;
            while (TRUE)
            {
                if (!(i = LexCharGet(cc)))
                    LexExcept(cc, "Expecting '}' at ");
                if (i == '}')
                    break;
                if ('0' <= i <= '7')
                    QueueVectU8Put(tmpv, g.npu.num++, i - '0');
            }
            Bts(&cc->flags, CCf_USE_LAST_U16);
            Lex(cc); //Load '}'
            g.type = SPT_POLYPT;
            size = SpriteElemQueuedBaseSize(SPT_POLYPT);
            res = CAlloc(size+(g.npu.num * 3 + 7 ) >> 3);
            MemCopy(res, &g, size);
            ptr = &res->npu.u;
            for (i = 0; i < g.npu.num; i++)
                BFieldOrU32(ptr, i * 3, QueueVectU8Get(tmpv, i));
            QueueVectU8Del(tmpv);
            break;

        case SPT_BITMAP:
            Lex(cc); //Skip {
            LexD2I32(cc, &g.pwhu.x1);
            if (cc->token != ':')
                LexExcept(cc, "Expecting ':' at ");
            Lex(cc); //Skip :
            LexD2I32(cc, &g.pwhu.width);
            if (cc->token != ',')
                LexExcept(cc, "Expecting ',' at ");
            tmpv = QueueVectU8New;
            num1 = 0;
            while (TRUE)
            {
                if (!(i = ToUpper(LexCharGet(cc))))
                    LexExcept(cc, "Expecting '}' at ");
                if (i == '}')
                    break;
                if ('0' <= i <= '9')
                    QueueVectU8Put(tmpv, num1++, i - '0');
                else if ('A' <= i <= 'F')
                    QueueVectU8Put(tmpv, num1++, i - 'A' + 10);
                else if (i == CH_SPACE)
                    QueueVectU8Put(tmpv, num1++, TRANSPARENT);
                else if (i == '_')
                    QueueVectU8Put(tmpv, num1++, 0);
            }
            Bts(&cc->flags, CCf_USE_LAST_U16);
            Lex(cc); //Load '}'
            g.type = SPT_BITMAP;
            size = SpriteElemQueuedBaseSize(SPT_BITMAP);
            res = CAlloc(size + num1);
            MemCopy(res, &g, size);
            ptr = &res->pwhu.u;
            for (i = 0; i < num1; i++)
                *ptr++ = QueueVectU8Get(tmpv, i);
            QueueVectU8Del(tmpv);
            break;

        case SPT_MESH:
        case SPT_SHIFTABLE_MESH:
            Lex(cc); //Skip {
            if (LexExpressionI64(cc))
            {
                g.type = SPT_SHIFTABLE_MESH;
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");
                Lex(cc); //Skip ,
                LexD3I32(cc, &g.pmu.x);
                if (cc->token != ':')
                    LexExcept(cc, "Expecting ':' at ");
                Lex(cc); //Skip :
            }
            else
            {
                g.type = SPT_MESH;
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");
                Lex(cc); //Skip ,
            }
            num1 = 0;
            QueueInit(&headp);
            while (cc->token == '(')
            {
                tmpp = CAlloc(sizeof(CQueueD3I32));
                LexD3I32(cc, &tmpp->p);
                QueueInsert(tmpp, headp.last);
                num1++;
                if (cc->token == ',')
                    Lex(cc); //Skip ,
            }
            if (cc->token != ':')
                LexExcept(cc, "Expecting ':' at ");
            Lex(cc); //Skip :
            num2 = 0;
            QueueInit(&headt);
            while (cc->token == '(') {
                tmpt = CAlloc(sizeof(CQueueMeshTri));
                st = LexFirstRemove(cc, ",");
                tmpt->color = Str2ColorU32(st);
                Free(st);
                Lex(cc); //Skip color
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");
                Lex(cc); //Skip ,
                tmpt->nums[0] = LexExpressionI64(cc);
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");
                Lex(cc); //Skip ,
                tmpt->nums[1] = LexExpressionI64(cc);
                if (cc->token != ',')
                    LexExcept(cc, "Expecting ',' at ");
                Lex(cc); //Skip , 
                tmpt->nums[2] = LexExpressionI64(cc);
                if (cc->token != ')')
                    LexExcept(cc, "Expecting ')' at ");
                Lex(cc); //Skip )
                QueueInsert(tmpt, headt.last);
                num2++;
                if (cc->token == ',')
                    Lex(cc); //Skip ,
            }
            if (g.type == SPT_MESH)
            {
                g.mu.vertex_count = num1;
                g.mu.tri_count    = num2;
                size = SpriteElemQueuedBaseSize(SPT_MESH);
            }
            else
            {
                g.pmu.vertex_count  = num1;
                g.pmu.tri_count     = num2;
            }
            size = SpriteElemQueuedBaseSize(g.type);
            res = MAlloc(size + num1 * sizeof(CD3I32) + num2 * sizeof(CMeshTri));
            MemCopy(res, &g, size);
            ptr = res(U8 *) + size;
            tmpp = headp.next;
            while (tmpp != &headp)
            {
                tmpa1 = tmpp->next;
                MemCopy(ptr, &tmpp->p, sizeof(CD3I32));
                ptr += sizeof(CD3I32);
                Free(tmpp);
                tmpp = tmpa1;
            }
            tmpt = headt.next;
            while (tmpt != &headt)
            {
                tmpt1 = tmpt->next;
                MemCopy(ptr, &tmpt->start, sizeof(CMeshTri));
                ptr += sizeof(CMeshTri);
                Free(tmpt);
                tmpt = tmpt1;
            }
            break;
    }
    if (cc->token != '}')
        LexExcept(cc, "Expecting '}' at ");
    if (Lex(cc) != ';')
        LexExcept(cc, "Expecting ';' at ");

    return res;
}

public U8 *Code2Sprite(CDoc *doc, I64 *_size=NULL)
{//Text to sprite.
    CSprite          head;
    U8              *res;
    Bool             okay = TRUE, unlock_doc = DocLock(doc);
    CCompCtrl       *cc = CompCtrlNew(, CCF_DONT_FREE_BUF);
    CHashTable      *old_hash_table_list = cc->htc.hash_table_list;
    CHashGeneric    *tmph;
    I64              i, size = 0;

    QueueInit(&head);
    LexAttachDoc(cc,, doc);
    try
    {
        do
        {
            cc->htc.hash_table_list = NULL;
            if (Lex(cc) == TK_IDENT /*Skip ; */ && (tmph = HashFind(cc->cur_str, gr.sprite_hash, SPHT_ELEM_CODE)))
            {
                i = tmph->user_data0;
                cc->htc.hash_table_list = old_hash_table_list;
                if (Lex(cc) == '{') //Skip ident
                    QueueInsert(Code2SpriteElem(cc, i), head.last);
            }
            else if (cc->token)
                LexExcept(cc, "Expecting sprite element type name at ");
        }
        while (cc->token);

        okay = TRUE;
    }
    catch
    {
        Fs->catch_except = TRUE;
        okay = FALSE;
    }
    if (unlock_doc)
        DocUnlock(doc);
    if (okay)
    {
        CompCtrlDel(cc); //TODO: can crash
        res = SpriteQueue2Sprite(&head, &size);
    }
    else
    {
        res = NULL;
        size = 0;
    }
    if (_size)
        *_size = size;
    QueueDel(&head);

    return res;
}