Added ActionScript support
[swftools.git] / src / swfc.c
index 9fd752a..c9d4420 100644 (file)
@@ -4,8 +4,20 @@
    Part of the swftools package.
 
    Copyright (c) 2001 Matthias Kramm <kramm@quiss.org>
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   This file is distributed under the GPL, see file COPYING for details */
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <memory.h>
 #include <errno.h>
-#define logf logarithmf // logf is also used by ../lib/log.h
 #include <math.h>
-#undef logf
 #include "../config.h"
 #include "../lib/rfxswf.h"
+#include "../lib/drawer.h"
 #include "../lib/log.h"
 #include "../lib/args.h"
-#include "q.h"
+#include "../lib/q.h"
 #include "parser.h"
+#include "wav.h"
 
 //#define DEBUG
 
 static char * filename = 0;
 static char * outputname = "output.swf";
 static int verbose = 2;
+static int override_outputname = 0;
 
 static struct options_t options[] =
 {
@@ -45,6 +58,7 @@ int args_callback_option(char*name,char*val)
     }
     else if(!strcmp(name, "o")) {
        outputname = val;
+       override_outputname = 1;
        return 1;
     }
     else if(!strcmp(name, "v")) {
@@ -63,7 +77,7 @@ int args_callback_longoption(char*name,char*val)
 }
 void args_callback_usage(char*name)
 {
-    printf("Usage: %s [-o filename] file.wav\n", name);
+    printf("Usage: %s [-o filename] file.sc\n", name);
     printf("\t-v , --verbose\t\t\t Be more verbose\n");
     printf("\t-o , --output filename\t\t set output filename (default: output.swf)\n");
     printf("\t-V , --version\t\t\t Print program version and exit\n");
@@ -161,11 +175,15 @@ static struct level
    int oldframe;
    dictionary_t oldinstances;
    SRECT oldrect;
+   TAG* cut;
 
 } stack[256];
 static int stackpos = 0;
 
 static dictionary_t characters;
+static dictionary_t images;
+static dictionary_t outlines;
+static dictionary_t gradients;
 static char idmap[65536];
 static TAG*tag = 0; //current tag
 
@@ -175,6 +193,7 @@ static SRECT currentrect; //current bounding box in current level
 static U16 currentdepth;
 static dictionary_t instances;
 static dictionary_t fonts;
+static dictionary_t sounds;
 
 typedef struct _parameters {
     int x,y; 
@@ -200,6 +219,16 @@ typedef struct _instance {
     U16 lastFrame; //frame lastTag is in
 } instance_t;
 
+typedef struct _outline {
+    SHAPE* shape;
+    SRECT bbox;
+} outline_t;
+
+typedef struct _gradient {
+    GRADIENT gradient;
+    char radial;
+} gradient_t;
+
 static void character_init(character_t*c)
 {
     memset(c, 0, sizeof(character_t));
@@ -251,6 +280,17 @@ static void s_addcharacter(char*name, U16 id, TAG*ctag, SRECT r)
     swf_SetU16(tag, id);
     swf_SetString(tag, name);
 }
+static void s_addimage(char*name, U16 id, TAG*ctag, SRECT r)
+{
+    character_t* c = character_new();
+    c->definingTag = ctag;
+    c->id = id;
+    c->size = r;
+
+    if(dictionary_lookup(&images, name))
+       syntaxerror("image %s defined twice", name);
+    dictionary_put2(&images, name, c);
+}
 static instance_t* s_addinstance(char*name, character_t*c, U16 depth)
 {
     instance_t* i = instance_new();
@@ -323,10 +363,9 @@ static MATRIX s_instancepos(instance_t*i, parameters_t*p)
     return m;
 }
 
-void s_swf(char*name, SRECT r, int version, int fps, int compress)
+void s_swf(char*name, SRECT r, int version, int fps, int compress, RGBA background)
 {
     SWF*swf = (SWF*)malloc(sizeof(SWF));
-    RGBA rgb;
 
     if(stackpos)
        syntaxerror(".swf blocks can't be nested");
@@ -337,15 +376,18 @@ void s_swf(char*name, SRECT r, int version, int fps, int compress)
     swf->frameRate = fps;
     swf->firstTag = tag = swf_InsertTag(0, ST_SETBACKGROUNDCOLOR);
     swf->compressed = compress;
-    rgb.r = 0x00;rgb.g = 0x00;rgb.b = 0x00;
-    swf_SetRGB(tag,&rgb);
+    swf_SetRGB(tag,&background);
     
     if(stackpos==sizeof(stack)/sizeof(stack[0]))
        syntaxerror("too many levels of recursion");
     
     dictionary_init(&characters);
+    dictionary_init(&images);
+    dictionary_init(&outlines);
+    dictionary_init(&gradients);
     dictionary_init(&instances);
     dictionary_init(&fonts);
+    dictionary_init(&sounds);
 
     memset(&stack[stackpos], 0, sizeof(stack[0]));
     stack[stackpos].type = 0;
@@ -389,9 +431,25 @@ void s_sprite(char*name)
     incrementid();
 }
 
+TAG* removeFromTo(TAG*from, TAG*to)
+{
+    TAG*save = from->prev;
+    while(from!=to) {
+       TAG*next = from->next;
+       swf_DeleteTag(from);
+       from = next;
+    }
+    save->next = 0;
+    return save;
+}
+
 static void s_endSprite()
 {
     SRECT r = currentrect;
+    
+    if(stack[stackpos].cut)
+       tag = removeFromTo(stack[stackpos].cut, tag);
+
     stackpos--;
    
     /* TODO: before clearing, prepend "<spritename>." to names and
@@ -402,6 +460,7 @@ static void s_endSprite()
     currentrect = stack[stackpos].oldrect;
     currentdepth = stack[stackpos].olddepth;
     instances = stack[stackpos].oldinstances;
+
     tag = swf_InsertTag(tag, ST_END);
 
     tag = stack[stackpos].tag;
@@ -418,20 +477,30 @@ static void s_endSWF()
     int fi;
     SWF* swf;
     char*filename;
+    
+    if(stack[stackpos].cut)
+       tag = removeFromTo(stack[stackpos].cut, tag);
+
     stackpos--;
 
     swf = stack[stackpos].swf;
     filename = stack[stackpos].filename;
-    
-    tag = swf_InsertTag(tag, ST_SHOWFRAME);
+   
+    //tag = swf_InsertTag(tag, ST_SHOWFRAME); //?
+
     tag = swf_InsertTag(tag, ST_END);
 
     swf_OptimizeTagOrder(swf);
 
     if(!(swf->movieSize.xmax-swf->movieSize.xmin) || !(swf->movieSize.ymax-swf->movieSize.ymin))
        swf->movieSize = currentrect; /* "autocrop" */
+    
+    if(!(swf->movieSize.xmax-swf->movieSize.xmin) || !(swf->movieSize.ymax-swf->movieSize.ymin)) {
+       swf->movieSize.xmax += 20; /* 1 by 1 pixels */
+       swf->movieSize.ymax += 20;
+    }
 
-    fi = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+    fi = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0644);
     if(fi<0) {
        syntaxerror("couldn't create output file %s", filename);
     }
@@ -444,17 +513,22 @@ static void s_endSWF()
     
     dictionary_clear(&instances);
     dictionary_clear(&characters);
+    dictionary_clear(&images);
+    dictionary_clear(&outlines);
+    dictionary_clear(&gradients);
     dictionary_clear(&fonts);
+    dictionary_clear(&sounds);
 
     swf_FreeTags(swf);
     free(swf);
     free(filename);
 }
+
 void s_close()
 {
     if(stackpos) {
        if(stack[stackpos-1].type == 0)
-           syntaxerror("End of file encountered in .swf block");
+           syntaxerror("End of file encountered in .flash block");
        if(stack[stackpos-1].type == 1)
            syntaxerror("End of file encountered in .sprite block");
        if(stack[stackpos-1].type == 2)
@@ -467,31 +541,82 @@ int s_getframe()
     return currentframe;
 }
 
-void s_frame(int nr)
+void s_frame(int nr, int cut)
 {
     int t;
+    TAG*now = tag;
+
     for(t=currentframe;t<nr;t++) {
        tag = swf_InsertTag(tag, ST_SHOWFRAME);
     }
+
+    if(cut) {
+       if(now == tag) {
+           syntaxerror("Can't cut, frame empty");
+       }
+       stack[stackpos].cut = tag;
+    }
+
     currentframe = nr;
 }
+
+int parseColor2(char*str, RGBA*color);
+
+int addFillStyle(SHAPE*s, SRECT*r, char*texture)
+{
+    RGBA color;
+    character_t*image;
+    gradient_t*gradient;
+    if(texture[0] == '#') {
+       parseColor2(texture, &color);
+       return swf_ShapeAddSolidFillStyle(s, &color);
+    } else if((image = dictionary_lookup(&images, texture))) {
+       MATRIX m;
+       swf_GetMatrix(0, &m);
+       m.sx = 65536.0*20.0*(r->xmax - r->xmin)/image->size.xmax;
+       m.sy = 65536.0*20.0*(r->ymax - r->ymin)/image->size.ymax;
+       m.tx = r->xmin;
+       m.ty = r->ymin;
+       return swf_ShapeAddBitmapFillStyle(s, &m, image->id, 0);
+    } /*else if ((texture = dictionary_lookup(&textures, texture))) {
+    } */ else if ((gradient = dictionary_lookup(&gradients, texture))) {
+       MATRIX m;
+       swf_GetMatrix(0, &m);
+       m.sx = (r->xmax - r->xmin)*2;
+       m.sy = (r->ymax - r->ymin)*2;
+       m.tx = r->xmin + (r->xmax - r->xmin)/2;
+       m.ty = r->ymin + (r->ymax - r->ymin)/2;
+       return swf_ShapeAddGradientFillStyle(s, &m, &gradient->gradient, gradient->radial);
+    }  else if (parseColor2(texture, &color)) {
+       return swf_ShapeAddSolidFillStyle(s, &color);
+    } else {
+       syntaxerror("not a color/fillstyle: %s", texture);
+       return 0;
+    }
+}
        
 RGBA black={r:0,g:0,b:0,a:0};
-void s_box(char*name, int width, int height, RGBA color, int linewidth, RGBA fill, int dofill)
+void s_box(char*name, int width, int height, RGBA color, int linewidth, char*texture)
 {
-    SRECT r;
+    SRECT r,r2;
     SHAPE* s;
     int ls1,fs1=0;
+    r2.xmin = 0;
+    r2.ymin = 0;
+    r2.xmax = width;
+    r2.ymax = height;
     tag = swf_InsertTag(tag, ST_DEFINESHAPE);
     swf_ShapeNew(&s);
     ls1 = swf_ShapeAddLineStyle(s,linewidth,&color);
-    if(dofill)
-       fs1 = swf_ShapeAddSolidFillStyle(s,&fill);
+
+    if(texture)
+       fs1 = addFillStyle(s, &r2, texture);
+
     swf_SetU16(tag,id);
-    r.xmin = -linewidth-linewidth/2;
-    r.ymin = -linewidth-linewidth/2;
-    r.xmax = width+linewidth+linewidth/2;
-    r.ymax = height+linewidth+linewidth/2;
+    r.xmin = r2.xmin-linewidth-linewidth/2;
+    r.ymin = r2.ymin-linewidth-linewidth/2;
+    r.xmax = r2.xmax+linewidth+linewidth/2;
+    r.ymax = r2.ymax+linewidth+linewidth/2;
     swf_SetRect(tag,&r);
     swf_SetShapeHeader(tag,s);
     swf_ShapeSetAll(tag,s,0,0,ls1,fs1,0);
@@ -506,21 +631,60 @@ void s_box(char*name, int width, int height, RGBA color, int linewidth, RGBA fil
     incrementid();
 }
 
-void s_circle(char*name, int r, RGBA color, int linewidth, RGBA fill, int dofill)
+void s_filled(char*name, char*outlinename, RGBA color, int linewidth, char*texture)
 {
-    SRECT rect;
+    SRECT rect,r2;
     SHAPE* s;
     int ls1,fs1=0;
+    outline_t* outline;
+    outline = dictionary_lookup(&outlines, outlinename);
+    if(!outline) {
+       syntaxerror("outline %s not defined", outlinename);
+    }
+    r2 = outline->bbox;
+
+    tag = swf_InsertTag(tag, ST_DEFINESHAPE);
+    swf_ShapeNew(&s);
+    ls1 = swf_ShapeAddLineStyle(s,linewidth,&color);
+    if(texture)
+       fs1 = addFillStyle(s, &r2, texture);
+    else 
+       syntaxerror("non filled outlines not yet supported- please supply a fill=<color/texture> argument");
+    swf_SetU16(tag,id);
+    rect.xmin = r2.xmin-linewidth-linewidth/2;
+    rect.ymin = r2.ymin-linewidth-linewidth/2;
+    rect.xmax = r2.xmax+linewidth+linewidth/2;
+    rect.ymax = r2.ymax+linewidth+linewidth/2;
+
+    swf_SetRect(tag,&rect);
+    swf_SetShapeStyles(tag, s);
+    swf_SetShapeBits(tag, outline->shape); //does not count bits!
+    swf_SetBlock(tag, outline->shape->data, (outline->shape->bitlen+7)/8);
+    swf_ShapeFree(s);
+
+    s_addcharacter(name, id, tag, rect);
+    incrementid();
+}
+
+void s_circle(char*name, int r, RGBA color, int linewidth, char*texture)
+{
+    SRECT rect,r2;
+    SHAPE* s;
+    int ls1,fs1=0;
+    r2.xmin = r2.ymin = 0;
+    r2.xmax = 2*r;
+    r2.ymax = 2*r;
+
     tag = swf_InsertTag(tag, ST_DEFINESHAPE);
     swf_ShapeNew(&s);
     ls1 = swf_ShapeAddLineStyle(s,linewidth,&color);
-    if(dofill)
-       fs1 = swf_ShapeAddSolidFillStyle(s,&fill);
+    if(texture)
+       fs1 = addFillStyle(s, &r2, texture);
     swf_SetU16(tag,id);
-    rect.xmin = -linewidth-linewidth/2;
-    rect.ymin = -linewidth-linewidth/2;
-    rect.xmax = 2*r+linewidth+linewidth/2;
-    rect.ymax = 2*r+linewidth+linewidth/2;
+    rect.xmin = r2.xmin-linewidth-linewidth/2;
+    rect.ymin = r2.ymin-linewidth-linewidth/2;
+    rect.xmax = r2.xmax+linewidth+linewidth/2;
+    rect.ymax = r2.ymax+linewidth+linewidth/2;
 
     swf_SetRect(tag,&rect);
     swf_SetShapeHeader(tag,s);
@@ -533,44 +697,44 @@ void s_circle(char*name, int r, RGBA color, int linewidth, RGBA fill, int dofill
     incrementid();
 }
 
-void s_textshape(char*name, char*fontname, char*_text, RGBA color, int linewidth, RGBA fill, int dofill)
+void s_textshape(char*name, char*fontname, char*_text)
 {
-    SRECT rect;
-    SHAPE* s;
-    int ls1,fs1=0;
     int g;
     U8*text = (U8*)_text;
+    outline_t* outline;
 
     SWFFONT*font;
     font = dictionary_lookup(&fonts, fontname);
     if(!font)
        syntaxerror("font \"%s\" not known!", fontname);
-    if(!dofill)
-       syntaxerror("textshapes must be filled", fontname);
-
+    
     if(text[0] >= font->maxascii || font->ascii2glyph[text[0]]<0) {
        warning("no character 0x%02x (%c) in font \"%s\"", text[0], text[0], fontname);
-       s_box(name, 0, 0, black, 20, black, 0);
+       s_box(name, 0, 0, black, 20, 0);
        return;
     }
     g = font->ascii2glyph[text[0]];
-    rect = font->layout->bounds[g];
 
-    swf_ShapeNew(&s);
-    ls1 = swf_ShapeAddLineStyle(s,linewidth,&color);
-    if(dofill)
-       fs1 = swf_ShapeAddSolidFillStyle(s,&fill);
-     
-    tag = swf_InsertTag(tag, ST_DEFINESHAPE);
-    swf_SetU16(tag,id);
-    swf_SetRect(tag, &rect);
-    swf_SetShapeStyles(tag, s);
-    swf_SetSimpleShape(tag, font->glyph[g].shape);
-    swf_ShapeFree(s);
+    outline = malloc(sizeof(outline_t));
+    memset(outline, 0, sizeof(outline_t));
+    outline->shape = font->glyph[g].shape;
+    outline->bbox = font->layout->bounds[g];
     
-    s_addcharacter(name, id, tag, rect);
-    incrementid();
+    {
+       drawer_t draw;
+       swf_Shape11DrawerInit(&draw, 0);
+       swf_DrawText(&draw, font, _text);
+       draw.finish(&draw);
+       outline->shape = swf_ShapeDrawerToShape(&draw);
+       outline->bbox = swf_ShapeDrawerGetBBox(&draw);
+       draw.dealloc(&draw);
+    }
+
+    if(dictionary_lookup(&outlines, name))
+       syntaxerror("outline %s defined twice", name);
+    dictionary_put2(&outlines, name, outline);
 }
+
 void s_text(char*name, char*fontname, char*text, int size, RGBA color)
 {
     SRECT r;
@@ -583,7 +747,7 @@ void s_text(char*name, char*fontname, char*text, int size, RGBA color)
     tag = swf_InsertTag(tag, ST_DEFINETEXT2);
     swf_SetU16(tag, id);
     if(!font->numchars) {
-       s_box(name, 0, 0, black, 20, black, 0);
+       s_box(name, 0, 0, black, 20, 0);
        return;
     }
     r = swf_SetDefineText(tag, font, &color, text, size);
@@ -592,6 +756,48 @@ void s_text(char*name, char*fontname, char*text, int size, RGBA color)
     incrementid();
 }
 
+/* type: either "jpeg" or "png"
+ */
+void s_image(char*name, char*type, char*filename, int quality)
+{
+    /* an image is actually two folded: 1st bitmap, 2nd character.
+       Both of them can be used separately */
+    
+    /* step 1: the bitmap */
+    SRECT r;
+    int imageID = id;
+    int width, height;
+    if(type=="png") {
+       warning("image type \"png\" not supported yet!");
+       s_box(name, 0, 0, black, 20, 0);
+       return;
+    }
+    tag = swf_InsertTag(tag, ST_DEFINEBITSJPEG2);
+    swf_SetU16(tag, imageID);
+
+    if(swf_SetJPEGBits(tag, filename, quality) < 0) {
+       syntaxerror("Image \"%s\" not found, or contains errors", filename);
+    }
+
+    swf_GetJPEGSize(filename, &width, &height);
+
+    r.xmin = 0;
+    r.ymin = 0;
+    r.xmax = width*20;
+    r.ymax = height*20;
+
+    s_addimage(name, id, tag, r);
+    incrementid();
+
+    /* step 2: the character */
+    tag = swf_InsertTag(tag, ST_DEFINESHAPE); // todo: should be defineshape2/3 once images can be transparent.(?)
+    swf_SetU16(tag, id);
+    swf_ShapeSetBitmapRect(tag, imageID, width, height);
+
+    s_addcharacter(name, id, tag, r);
+    incrementid();
+}
+
 void dumpSWF(SWF*swf)
 {
     TAG* tag = swf->firstTag;
@@ -608,7 +814,9 @@ void s_font(char*name, char*filename)
     int f;
     SWF swf;
     SWFFONT* font;
-    f = open(filename,O_RDONLY);
+    font = swf_LoadFont(filename);
+    
+    /*f = open(filename,O_RDONLY|O_BINARY);
     if (f<0) { 
        warning("Couldn't open file \"%s\": %s", filename, strerror(errno));
        font = (SWFFONT*)malloc(sizeof(SWFFONT));
@@ -624,7 +832,7 @@ void s_font(char*name, char*filename)
     close(f);
     if (font==0) { 
        syntaxerror("File \"%s\" isn't a valid rfxswf font file", filename);
-    }
+    }*/
 
     if(0)
     {
@@ -647,7 +855,168 @@ void s_font(char*name, char*filename)
     dictionary_put2(&fonts, name, font);
 }
 
-void s_shape(char*name, char*filename)
+
+
+typedef struct _sound_t
+{
+    U16 id;
+    TAG*tag;
+} sound_t;
+
+void s_sound(char*name, char*filename)
+{
+    struct WAV wav, wav2;
+    sound_t* sound;
+    U16*samples;
+    int numsamples;
+
+    if(!readWAV(filename, &wav)) {
+       warning("Couldn't read wav file \"%s\"", filename);
+       samples = 0;
+       numsamples = 0;
+    } else {
+       convertWAV2mono(&wav, &wav2, 44100);
+       samples = (U16*)wav2.data;
+       numsamples = wav2.size/2;
+       free(wav.data);
+    }
+
+    tag = swf_InsertTag(tag, ST_DEFINESOUND);
+    swf_SetU16(tag, id); //id
+    swf_SetSoundDefine(tag, samples, numsamples);
+   
+    sound = (sound_t*)malloc(sizeof(sound_t)); /* mem leak */
+    sound->tag = tag;
+    sound->id = id;
+
+    if(dictionary_lookup(&sounds, name))
+       syntaxerror("sound %s defined twice", name);
+    dictionary_put2(&sounds, name, sound);
+    
+    incrementid();
+
+    if(samples)
+       free(samples);
+}
+
+static char* gradient_getToken(const char**p)
+{
+    const char*start;
+    char*result;
+    while(**p && strchr(" \t\n\r", **p)) {
+       (*p)++;
+    } 
+    start = *p;
+    while(**p && !strchr(" \t\n\r", **p)) {
+       (*p)++;
+    }
+    result = malloc((*p)-start+1);
+    memcpy(result,start,(*p)-start+1);
+    result[(*p)-start] = 0;
+    return result;
+}
+
+float parsePercent(char*str);
+RGBA parseColor(char*str);
+
+GRADIENT parseGradient(const char*str)
+{
+    GRADIENT gradient;
+    const char* p = str;
+    memset(&gradient, 0, sizeof(GRADIENT));
+    while(*p) {
+       char*posstr,*colorstr;
+       posstr = gradient_getToken(&p);
+       if(!*posstr)
+           break;
+       float pos = parsePercent(posstr);
+       if(!*p) syntaxerror("Error in shape data: Color expected after %s", posstr);
+       colorstr = gradient_getToken(&p);
+       RGBA color = parseColor(colorstr);
+       if(gradient.num == sizeof(gradient.ratios)/sizeof(gradient.ratios[0])) {
+           warning("gradient record too big- max size is 8, rest ignored");
+           break;
+       }
+       gradient.ratios[gradient.num] = (int)(pos*255.0);
+       gradient.rgba[gradient.num] = color;
+       gradient.num++;
+       free(posstr);
+       free(colorstr);
+    }
+    return gradient;
+}
+
+void s_gradient(char*name, const char*text, int radial)
+{
+    gradient_t* gradient;
+    gradient = malloc(sizeof(gradient_t));
+    memset(gradient, 0, sizeof(gradient_t));
+    gradient->gradient = parseGradient(text);
+    gradient->radial = radial;
+
+    if(dictionary_lookup(&gradients, name))
+       syntaxerror("gradient %s defined twice", name);
+    dictionary_put2(&gradients, name, gradient);
+}
+
+void s_action(const char*text)
+{
+    ActionTAG* a = 0;
+    a = swf_ActionCompile(text, stack[0].swf->fileVersion);
+
+    tag = swf_InsertTag(tag, ST_DOACTION);
+
+    swf_ActionSet(tag, a);
+
+    swf_ActionFree(a);
+}
+
+void s_outline(char*name, char*format, char*source)
+{
+    outline_t* outline;
+
+    drawer_t draw;
+    SHAPE* shape;
+    SHAPE2* shape2;
+    SRECT bounds;
+    
+    swf_Shape11DrawerInit(&draw, 0);
+    draw_string(&draw, source);
+    draw.finish(&draw);
+    shape = swf_ShapeDrawerToShape(&draw);
+    //shape2 = swf_ShapeToShape2(shape);
+    //bounds = swf_GetShapeBoundingBox(shape2);
+    //swf_Shape2Free(shape2);
+    bounds = swf_ShapeDrawerGetBBox(&draw);
+    draw.dealloc(&draw);
+    
+    outline = (outline_t*)malloc(sizeof(outline_t));
+    memset(outline, 0, sizeof(outline_t));
+    outline->shape = shape;
+    outline->bbox = bounds;
+    
+    if(dictionary_lookup(&outlines, name))
+       syntaxerror("outline %s defined twice", name);
+    dictionary_put2(&outlines, name, outline);
+}
+
+void s_playsound(char*name, int loops, int nomultiple, int stop)
+{
+    sound_t* sound = dictionary_lookup(&sounds, name);
+    SOUNDINFO info;
+    if(!sound)
+       syntaxerror("Don't know anything about sound \"%s\"", name);
+
+    tag = swf_InsertTag(tag, ST_STARTSOUND);
+    swf_SetU16(tag, sound->id); //id
+    memset(&info, 0, sizeof(info));
+    info.stop = stop;
+    info.loops = loops;
+    info.nomultiple = nomultiple;
+    swf_SetSoundInfo(tag, &info);
+}
+
+void s_includeswf(char*name, char*filename)
 {
     int f;
     SWF swf;
@@ -659,12 +1028,12 @@ void s_shape(char*name, char*filename)
     f = open(filename,O_RDONLY);
     if (f<0) { 
        warning("Couldn't open file \"%s\": %s", filename, strerror(errno));
-       s_box(name, 0, 0, black, 20, black, 0);
+       s_box(name, 0, 0, black, 20, 0);
        return;
     }
     if (swf_ReadSWF(f,&swf)<0) { 
        warning("Only SWF files supported in .shape for now. File \"%s\" wasn't SWF.", filename);
-       s_box(name, 0, 0, black, 20, black, 0);
+       s_box(name, 0, 0, black, 20, 0);
        return;
     }
     close(f);
@@ -854,7 +1223,7 @@ void s_change(char*instance, parameters_t p2)
     p1 = i->parameters;
     
     allframes = currentframe - i->lastFrame - 1;
-    if(!allframes) {
+    if(allframes < 0) {
        warning(".change ignored. can only .put/.change an object once per frame.");
        return;
     }
@@ -982,11 +1351,18 @@ int parseInt(char*str)
 }
 int parseTwip(char*str)
 {
-    char*dot = strchr(str, '.');
+    char*dot;
+    int sign=1;
+    if(str[0]=='+' || str[0]=='-') {
+       if(str[0]=='-')
+           sign = -1;
+       str++;
+    }
+    dot = strchr(str, '.');
     if(!dot) {
        int l=strlen(str);
        int t;
-       return parseInt(str)*20;
+       return sign*parseInt(str)*20;
     } else {
        int l=strlen(++dot);
        char*s;
@@ -1003,11 +1379,11 @@ int parseTwip(char*str)
            l=2;
        }
        if(l==0)
-           return atoi(str)*20;
+           return sign*atoi(str)*20;
        if(l==1)
-           return atoi(str)*20+atoi(dot)*2;
+           return sign*atoi(str)*20+atoi(dot)*2;
        if(l==2)
-           return atoi(str)*20+atoi(dot)/5;
+           return sign*atoi(str)*20+atoi(dot)/5;
     }
     return 0;
 }
@@ -1040,10 +1416,23 @@ int parseColor2(char*str, RGBA*color)
     int l = strlen(str);
     int r,g,b,a;
     int t;
-    char*names[8] = {"black", "blue", "green", "cyan",
-                    "red", "violet", "yellow", "white"};
-    a=255;
-    r=g=b=0;
+
+    struct {unsigned char r,g,b;char*name;} colors[] =
+    {{255,250,250,"snow"},{220,220,220,"gainsboro"},{250,240,230,"linen"},{255,228,196,"bisque"},
+    {255,228,181,"moccasin"},{255,248,220,"cornsilk"},{255,255,240,"ivory"},{255,245,238,"seashell"},
+    {240,255,240,"honeydew"},{240,255,255,"azure"},{230,230,250,"lavender"},{255,255,255,"white"},
+    {0,0,0,"black"},{190,190,190,"gray"},{190,190,190,"grey"},{0,0,128,"navy"},{0,0,255,"blue"},
+    {64,224,208,"turquoise"},{0,255,255,"cyan"},{127,255,212,"aquamarine"}, {0,255,0,"green"},
+    {127,255,0,"chartreuse"},{240,230,140,"khaki"},{255,255,0,"yellow"},
+    {255,215,0,"gold"},{218,165,32,"goldenrod"},{160,82,45,"sienna"},{205,133,63,"peru"},
+    {222,184,135,"burlywood"},{245,245,220,"beige"},{245,222,179,"wheat"},{210,180,140,"tan"},
+    {210,105,30,"chocolate"},{178,34,34,"firebrick"},{165,42,42,"brown"},{250,128,114,"salmon"},
+    {255,165,0,"orange"},{255,127,80,"coral"},{255,99,71,"tomato"},{255,0,0,"red"},
+    {255,192,203,"pink"},{176,48,96,"maroon"},{255,0,255,"magenta"},{238,130,238,"violet"},
+    {221,160,221,"plum"},{218,112,214,"orchid"},{160,32,240,"purple"},{216,191,216,"thistle"}};
+
+    a=255;r=g=b=0;
+
     if(str[0]=='#' && (l==7 || l==9)) {
        if(l == 7 && !(sscanf(str, "#%02x%02x%02x", &r, &g, &b)))
            return 0;
@@ -1052,14 +1441,12 @@ int parseColor2(char*str, RGBA*color)
        color->r = r; color->g = g; color->b = b; color->a = a;
        return 1;
     }
-    for(t=0;t<8;t++)
-       if(!strcmp(str, names[t])) {
-           if(t&1)
-               b = 255;
-           if(t&2)
-               g = 255;
-           if(t&4)
-               r = 255;
+    for(t=0;t<sizeof(colors)/sizeof(colors[0]);t++)
+       if(!strcmp(str, colors[t].name)) {
+           r = colors[t].r;
+           g = colors[t].g;
+           b = colors[t].b;
+           a = 255;
            color->r = r; color->g = g; color->b = b; color->a = a;
            return 1;
        }
@@ -1155,12 +1542,13 @@ static char* lu(map_t* args, char*name)
 {
     char* value = map_lookup(args, name);
     if(!value) {
+       map_dump(args, stdout, "");
        syntaxerror("internal error 2: value %s should be set", name);
     }
     return value;
 }
 
-static int c_swf(map_t*args) 
+static int c_flash(map_t*args) 
 {
     char* name = lu(args, "name");
     char* compressstr = lu(args, "compress");
@@ -1168,7 +1556,8 @@ static int c_swf(map_t*args)
     int version = parseInt(lu(args, "version"));
     int fps = (int)(parseFloat(lu(args, "fps"))*256);
     int compress = 0;
-    if(!strcmp(name, "!default!"))
+    RGBA color = parseColor(lu(args, "background"));
+    if(!strcmp(name, "!default!") || override_outputname)
        name = outputname;
     
     if(!strcmp(compressstr, "default"))
@@ -1179,7 +1568,7 @@ static int c_swf(map_t*args)
        compress = 0;
     else syntaxerror("value \"%s\" not supported for the compress argument", compressstr);
 
-    s_swf(name, bbox, version, fps, compress);
+    s_swf(name, bbox, version, fps, compress, color);
     return 0;
 }
 int isRelative(char*str)
@@ -1211,7 +1600,7 @@ int points_initialized = 0;
 
 SPOINT getPoint(SRECT r, char*name)
 {
-    int l;
+    int l=0;
     if(!strcmp(name, "center")) {
        SPOINT p;
        p.x = (r.xmin + r.xmax)/2;
@@ -1219,13 +1608,26 @@ SPOINT getPoint(SRECT r, char*name)
        return p;
     }
 
-    l = (int)dictionary_lookup(&points, name);
+    if(points_initialized)
+       l = (int)dictionary_lookup(&points, name);
     if(l==0) {
-       syntaxerror("Couldn't find point \"%s\".", name);
+       syntaxerror("Invalid point: \"%s\".", name);
     }
     l--;
     return *(SPOINT*)&mpoints.buffer[l];
 }
+static int c_gradient(map_t*args) 
+{
+    char*name = lu(args, "name");
+    int radial= strcmp(lu(args, "radial"), "radial")?0:1;
+
+    readToken();
+    if(type != RAWDATA)
+       syntaxerror("colon (:) expected");
+
+    s_gradient(name, text, radial);
+    return 0;
+}
 static int c_point(map_t*args) 
 {
     char*name = lu(args, "name");
@@ -1245,6 +1647,28 @@ static int c_point(map_t*args)
     dictionary_put(&points, s1, (void*)pos);
     return 0;
 }
+static int c_play(map_t*args) 
+{
+    char*name = lu(args, "sound");
+    char*loop = lu(args, "loop");
+    char*nomultiple = lu(args, "nomultiple");
+    int nm = 0;
+    if(!strcmp(nomultiple, "nomultiple"))
+       nm = 1;
+    else
+       nm = parseInt(nomultiple);
+
+    s_playsound(name, parseInt(loop), nm, 0);
+    return 0;
+}
+
+static int c_stop(map_t*args) 
+{
+    char*name = lu(args, "sound");
+    s_playsound(name, 0,0,1);
+    return 0;
+}
+
 static int c_placement(map_t*args, int type)
 {
     char*instance = lu(args, (type==0||type==4)?"instance":"name");
@@ -1476,10 +1900,16 @@ static int c_sprite(map_t*args)
 static int c_frame(map_t*args) 
 {
     char*framestr = lu(args, "n");
+    char*cutstr = lu(args, "cut");
     int frame;
-    if(framestr[0]=='+') {
+    int cut = 0;
+    if(strcmp(cutstr, "no"))
+       cut = 1;
+    if(isRelative(framestr)) {
        frame = s_getframe();
-       frame += parseInt(framestr+1);
+       if(getSign(framestr)<0)
+           syntaxerror("relative frame expressions must be positive");
+       frame += parseInt(getOffset(framestr));
     }
     else {
        frame = parseInt(framestr);
@@ -1487,7 +1917,7 @@ static int c_frame(map_t*args)
                && !(frame==0 && s_getframe()==frame)) // equality is o.k. for frame 0
            syntaxerror("frame expression must be >%d (is:%s)", s_getframe(), framestr);
     }
-    s_frame(frame);
+    s_frame(frame, cut);
     return 0;
 }
 static int c_primitive(map_t*args) 
@@ -1503,10 +1933,11 @@ static int c_primitive(map_t*args)
     int type=0;
     char* font;
     char* text;
+    char* outline;
     RGBA fill;
     if(!strcmp(command, "circle"))
        type = 1;
-    else if(!strcmp(command, "textshape"))
+    else if(!strcmp(command, "filled"))
        type = 2;
    
     if(type==0) {
@@ -1515,36 +1946,42 @@ static int c_primitive(map_t*args)
     } else if (type==1) {
        r = parseTwip(lu(args, "r"));
     } else if (type==2) {
-       text = lu(args, "text");
-       font = lu(args, "font");
+       outline = lu(args, "outline");
     }
 
     if(!strcmp(fillstr, "fill"))
        fillstr = colorstr;
     if(!strcmp(fillstr, "none"))
-       dofill = 0;
+       fillstr = 0;
     if(width<0 || height<0 || linewidth<0 || r<0)
        syntaxerror("values width, height, line, r must be positive");
-    if(!dofill || isColor(fillstr)) {
-       if(dofill) 
-           fill = parseColor(fillstr);
-    } else {
-       /* FIXME - texture fill */
-       fill.r = fill.g = 0;
-       fill.b = fill.a = 255;
-       warning("texture fill not supported yet. Filling with black.");
-    }
-    if(type == 0) s_box(name, width, height, color, linewidth, fill, dofill);
-    else if(type==1) s_circle(name, r, color, linewidth, fill, dofill);
-    else if(type==2) s_textshape(name, font, text, color, linewidth, fill, dofill);
+    
+    if(type == 0) s_box(name, width, height, color, linewidth, fillstr);
+    else if(type==1) s_circle(name, r, color, linewidth, fillstr);
+    else if(type==2) s_filled(name, outline, color, linewidth, fillstr);
     return 0;
 }
 
-static int c_shape(map_t*args) 
+static int c_textshape(map_t*args) 
+{
+    char*name = lu(args, "name");
+    char*text = lu(args, "text");
+    char*font = lu(args, "font");
+
+    s_textshape(name, font, text);
+    return 0;
+}
+
+
+
+static int c_swf(map_t*args) 
 {
     char*name = lu(args, "name");
     char*filename = lu(args, "filename");
-    s_shape(name, filename);
+    char*command = lu(args, "commandname");
+    if(!strcmp(command, "shape"))
+       warning("Please use .swf instead of .shape");
+    s_includeswf(name, filename);
     return 0;
 }
 
@@ -1556,6 +1993,14 @@ static int c_font(map_t*args)
     return 0;
 }
 
+static int c_sound(map_t*args) 
+{
+    char*name = lu(args, "name");
+    char*filename = lu(args, "filename");
+    s_sound(name, filename);
+    return 0;
+}
+
 static int c_text(map_t*args) 
 {
     char*name = lu(args, "name");
@@ -1567,43 +2012,83 @@ static int c_text(map_t*args)
     return 0;
 }
 
+static int c_soundtrack(map_t*args) 
+{
+    return 0;
+}
+
+static int c_image(map_t*args) 
+{
+    char*command = lu(args, "commandname");
+    char*name = lu(args, "name");
+    char*filename = lu(args, "filename");
+    if(!strcmp(command,"jpeg")) {
+       int quality = (int)(parsePercent(lu(args, "quality"))*100);
+       s_image(name, "jpeg", filename, quality);
+    } else {
+       s_image(name, "png", filename, 0);
+    }
+    return 0;
+}
+
+static int c_outline(map_t*args) 
+{
+    char*name = lu(args, "name");
+    char*format = lu(args, "format");
+
+    readToken();
+    if(type != RAWDATA)
+       syntaxerror("colon (:) expected");
+
+    s_outline(name, format, text);
+    return 0;
+}
+
 int fakechar(map_t*args)
 {
     char*name = lu(args, "name");
-    s_box(name, 0, 0, black, 20, black, 0);
+    s_box(name, 0, 0, black, 20, 0);
     return 0;
 }
-static int c_circle(map_t*args) {return fakechar(args);}
 
 static int c_egon(map_t*args) {return fakechar(args);}
-static int c_button(map_t*args) {return fakechar(args);}
+static int c_button(map_t*args) {
+    readToken();
+    if(type != RAWDATA)
+       syntaxerror("colon (:) expected");
+
+    return fakechar(args);
+}
 static int c_edittext(map_t*args) {return fakechar(args);}
 
 static int c_morphshape(map_t*args) {return fakechar(args);}
-static int c_image(map_t*args) {return fakechar(args);}
 static int c_movie(map_t*args) {return fakechar(args);}
-static int c_sound(map_t*args) {return fakechar(args);}
 
-static int c_play(map_t*args) {return 0;}
-static int c_stop(map_t*args) {return 0;}
-
-static int c_soundtrack(map_t*args) {return 0;}
 static int c_buttonsounds(map_t*args) {return 0;}
 static int c_buttonput(map_t*args) {return 0;}
 static int c_texture(map_t*args) {return 0;}
-static int c_action(map_t*args) {return 0;}
+
+static int c_action(map_t*args) 
+{
+    readToken();
+    if(type != RAWDATA)
+       syntaxerror("colon (:) expected");
+
+    s_action(text);
+   
+    return 0;
+}
 
 static struct {
     char*command;
     command_func_t* func;
     char*arguments;
 } arguments[] =
-{{"swf", c_swf, "bbox=autocrop version=5 fps=50 name=!default! @compress=default"},
- {"frame", c_frame, "n=+1"},
-
-    // "import" type stuff
- {"shape", c_shape, "name filename"},
- {"morphshape", c_morphshape, "name start end"},
+{{"flash", c_flash, "bbox=autocrop background=black version=5 fps=50 name=!default! @compress=default"},
+ {"frame", c_frame, "n=<plus>1 @cut=no"},
+ // "import" type stuff
+ {"swf", c_swf, "name filename"},
+ {"shape", c_swf, "name filename"},
  {"jpeg", c_image, "name filename quality=80%"},
  {"png", c_image, "name filename"},
  {"movie", c_movie, "name filename"},
@@ -1611,14 +2096,23 @@ static struct {
  {"font", c_font, "name filename"},
  {"soundtrack", c_soundtrack, "filename"},
 
+    // generators of primitives
+
+ {"point", c_point, "name x=0 y=0"},
+ {"gradient", c_gradient, "name @radial=0"},
+ {"outline", c_outline, "name format=simple"},
+ {"textshape", c_textshape, "name text font"},
+
     // character generators
  {"box", c_primitive, "name width height color=white line=1 @fill=none"},
  {"circle", c_primitive, "name r color=white line=1 @fill=none"},
- {"textshape", c_primitive, "name text font color=white line=1 @fill=none"},
+ {"filled", c_primitive, "name outline color=white line=1 @fill=none"},
+
  {"egon", c_egon, "name vertices color=white line=1 @fill=none"},
  {"button", c_button, "name shape over=*shape press=*shape area=*shape"},
  {"text", c_text, "name text font size=100% color=white"},
  {"edittext", c_edittext, "name font size width height text="" color=black maxlength=0 variable="" @password=0 @wordwrap=0 @multiline=0 @html=0 @noselect=0 @readonly=0"},
+ {"morphshape", c_morphshape, "name start end"},
  
  {"buttonsounds", c_buttonsounds, "name press=0 release=0 enter=0 leave=0"},
 
@@ -1637,7 +2131,6 @@ static struct {
     // virtual object placement
  {"buttonput", c_buttonput, "<i> x=0 y=0 red=+0 green=+0 blue=+0 alpha=+0 luminance= scale= scalex=100% scaley=100% shear=0 rotate=0 above= below="},
  {"texture", c_texture, "<i> x=0 y=0 scale= scalex=100% scaley=100% shear=0 rotate=0"},
- {"point", c_point, "name x=0 y=0"},
 
     // commands which start a block
 //startclip (see above)
@@ -1677,7 +2170,7 @@ static map_t parseArguments(char*command, char*pattern)
 
     if(!strncmp("<i> ", x, 3)) {
        readToken();
-       if(type == COMMAND || type == LABEL) {
+       if(type == COMMAND || type == RAWDATA) {
            pushBack();
            syntaxerror("character name expected");
        }
@@ -1731,13 +2224,13 @@ static map_t parseArguments(char*command, char*pattern)
     len = pos;
 
 /*    for(t=0;t<len;t++) {
-       printf("(%d) %s=%s %s\n", t, strndup(name[t], namelen[t]), strndup(value[t], valuelen[t]),
+       printf("(%d) %s=%s %s\n", t, strdup_n(name[t], namelen[t]), strdup_n(value[t], valuelen[t]),
                isboolean[t]?"(boolean)":"");
     }*/
 
     while(1) {
        readToken();
-       if(type == LABEL || type == COMMAND) {
+       if(type == RAWDATA || type == COMMAND) {
            pushBack();
            break;
        }
@@ -1752,8 +2245,8 @@ static map_t parseArguments(char*command, char*pattern)
                value[pos].str = text;
                value[pos].len = strlen(text);
                /*printf("setting boolean parameter %s (to %s)\n",
-                       strndup(name[pos], namelen[pos]),
-                       strndup(value[pos], valuelen[pos]));*/
+                       strdup_n(name[pos], namelen[pos]),
+                       strdup_n(value[pos], valuelen[pos]));*/
                break;
            }
        }
@@ -1765,7 +2258,7 @@ static map_t parseArguments(char*command, char*pattern)
            if((type == ASSIGNMENT && !strncmp(name[pos].str, text, name[pos].len>textlen?name[pos].len:textlen)) ||
               (type != ASSIGNMENT && !set[pos])) {
                if(set[pos]) {
-                   syntaxerror("value %s set twice (old value:%s)", text, strndup(value[pos].str, value[pos].len));
+                   syntaxerror("value %s set twice (old value:%s)", text, strdup_n(value[pos].str, value[pos].len));
                }
                if(type == ASSIGNMENT)
                    readToken();
@@ -1774,8 +2267,8 @@ static map_t parseArguments(char*command, char*pattern)
                value[pos].len = strlen(text);
 #if 0//def DEBUG
                printf("setting parameter %s (to %s)\n",
-                       strndup(name[pos].str, name[pos].len),
-                       strndup(value[pos].str, value[pos].len));
+                       strdup_n(name[pos].str, name[pos].len),
+                       strdup_n(value[pos].str, value[pos].len));
 #endif
                break;
            }
@@ -1786,7 +2279,7 @@ static map_t parseArguments(char*command, char*pattern)
     }
 #if 0//def DEBUG
     for(t=0;t<len;t++) {
-       printf("%s=%s\n", strndup(name[t].str, name[t].len), strndup(value[t].str, value[t].len));
+       printf("%s=%s\n", strdup_n(name[t].str, name[t].len), strdup_n(value[t].str, value[t].len));
     }
 #endif
     for(t=0;t<len;t++) {
@@ -1801,7 +2294,7 @@ static map_t parseArguments(char*command, char*pattern)
        }
        if(value[t].str == 0) {
            pushBack();
-           syntaxerror("value for parameter %s missing (no default)", strndup(name[t].str, name[t].len));
+           syntaxerror("value for parameter %s missing (no default)", strdup_n(name[t].str, name[t].len));
        }
     }
 
@@ -1820,8 +2313,18 @@ static void parseArgumentsForCommand(char*command)
     int nr = -1;
     for(t=0;t<sizeof(arguments)/sizeof(arguments[0]);t++) {
        if(!strcmp(arguments[t].command, command)) {
+
+           /* ugly hack- will be removed soon (once documentation and .sc generating
+              utilities have been changed) */
+           if(!strcmp(command, "swf") && !stackpos) {
+               warning("Please use .flash instead of .swf- this will be mandatory soon");      
+               command = "flash";
+               t = 0;
+           }
+
            args = parseArguments(command, arguments[t].arguments);
            nr = t;
+           break;
        }
     }
     if(nr<0)
@@ -1834,7 +2337,7 @@ static void parseArgumentsForCommand(char*command)
 
     (*arguments[nr].func)(&args);
 
-    if(!strcmp(command, "button") ||
+    /*if(!strcmp(command, "button") ||
        !strcmp(command, "action")) {
        while(1) {
            readToken();
@@ -1847,7 +2350,7 @@ static void parseArgumentsForCommand(char*command)
                }
            }
        }
-    }
+    }*/
 
     map_clear(&args);
     return;