fixed clean target (again), added support for $(EXEEXT).
[swftools.git] / src / swfc.c
1 /* swfc.c
2    Compiles swf code (.sc) files into .swf files.
3
4    Part of the swftools package.
5
6    Copyright (c) 2001 Matthias Kramm <kramm@quiss.org>
7
8    This file is distributed under the GPL, see file COPYING for details */
9
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <stdarg.h>
13 #include <string.h>
14 #include <memory.h>
15 #include <errno.h>
16 #include <math.h>
17 #include "../config.h"
18 #include "../lib/rfxswf.h"
19 #include "../lib/log.h"
20 #include "../lib/args.h"
21 #include "../lib/q.h"
22 #include "parser.h"
23 #include "wav.h"
24
25 //#define DEBUG
26
27 static char * filename = 0;
28 static char * outputname = "output.swf";
29 static int verbose = 2;
30 static int override_outputname = 0;
31
32 static struct options_t options[] =
33 {
34  {"o","output"},
35  {"v","verbose"},
36  {"V","version"},
37  {0,0}
38 };
39     
40 int args_callback_option(char*name,char*val)
41 {
42     if(!strcmp(name, "V")) {
43         printf("swfc - part of %s %s\n", PACKAGE, VERSION);
44         exit(0);
45     }
46     else if(!strcmp(name, "o")) {
47         outputname = val;
48         override_outputname = 1;
49         return 1;
50     }
51     else if(!strcmp(name, "v")) {
52         verbose ++;
53         return 0;
54     }
55     else {
56         printf("Unknown option: -%s\n", name);
57         exit(1);
58     }
59     return 0;
60 }
61 int args_callback_longoption(char*name,char*val)
62 {
63     return args_long2shortoption(options, name, val);
64 }
65 void args_callback_usage(char*name)
66 {
67     printf("Usage: %s [-o filename] file.wav\n", name);
68     printf("\t-v , --verbose\t\t\t Be more verbose\n");
69     printf("\t-o , --output filename\t\t set output filename (default: output.swf)\n");
70     printf("\t-V , --version\t\t\t Print program version and exit\n");
71 }
72 int args_callback_command(char*name,char*val)
73 {
74     if(filename) {
75         fprintf(stderr, "Only one file allowed. You supplied at least two. (%s and %s)\n",
76                  filename, name);
77     }
78     filename = name;
79     return 0;
80 }
81
82 static struct token_t* file;
83
84 static int pos;
85 static char*text;
86 static int textlen;
87 static int type;
88 static int line;
89 static int column;
90
91 static void syntaxerror(char*format, ...)
92 {
93     char buf[1024];
94     va_list arglist;
95     va_start(arglist, format);
96     vsprintf(buf, format, arglist);
97     va_end(arglist);
98     printf("\"%s\", line %d column %d: error- %s\n", filename, line, column, buf);
99     exit(1);
100 }
101
102 static void warning(char*format, ...)
103 {
104     char buf[1024];
105     va_list arglist;
106     va_start(arglist, format);
107     vsprintf(buf, format, arglist);
108     va_end(arglist);
109     printf("\"%s\", line %d column %d: warning- %s\n", filename, line, column, buf);
110 }
111    
112 static void readToken()
113 {
114     type = file[pos].type;
115     if(type == END) {
116         syntaxerror("unexpected end of file");
117     }
118     text = file[pos].text;
119     textlen = strlen(text);
120     line = file[pos].line;
121     column = file[pos].column;
122     pos++;
123     //printf("---> %d(%s) %s\n", type, type_names[type], text);
124 }
125
126 static void pushBack()
127 {
128     int p;
129     if(!pos) syntaxerror("internal error 3");
130     pos--;
131     p = pos;
132     if(p) p--;
133     text = file[p].text;
134     textlen = strlen(text);
135     type = file[p].type;
136     line = file[p].line;
137     column = file[p].column;
138 }
139
140 static int noMoreTokens()
141 {
142     if(file[pos].type == END)
143         return 1;
144     return 0;
145 }
146
147 // ------------------------------ swf routines ----------------------------
148 struct _character;
149 static struct level
150 {
151    int type; //0=swf, 1=sprite, 2=clip
152
153    /* for swf (0): */
154    SWF*swf;
155    char*filename; 
156
157    /* for sprites (1): */
158    TAG*tag;
159    U16 id;
160    char*name;
161    U16 olddepth;
162    int oldframe;
163    dictionary_t oldinstances;
164    SRECT oldrect;
165
166 } stack[256];
167 static int stackpos = 0;
168
169 static dictionary_t characters;
170 static char idmap[65536];
171 static TAG*tag = 0; //current tag
172
173 static int id; //current character id
174 static int currentframe; //current frame in current level
175 static SRECT currentrect; //current bounding box in current level
176 static U16 currentdepth;
177 static dictionary_t instances;
178 static dictionary_t fonts;
179 static dictionary_t sounds;
180
181 typedef struct _parameters {
182     int x,y; 
183     float scalex, scaley; 
184     CXFORM cxform;
185     float rotate;
186     float shear;
187     SPOINT pivot;
188     SPOINT pin;
189 } parameters_t;
190
191 typedef struct _character {
192     TAG*definingTag;
193     U16 id;
194     SRECT size;
195 } character_t;
196
197 typedef struct _instance {
198     character_t*character;
199     U16 depth;
200     parameters_t parameters;
201     TAG* lastTag; //last tag which set the object
202     U16 lastFrame; //frame lastTag is in
203 } instance_t;
204
205 static void character_init(character_t*c)
206 {
207     memset(c, 0, sizeof(character_t));
208 }
209 static character_t* character_new()
210 {
211     character_t*c;
212     c = (character_t*)malloc(sizeof(character_t));
213     character_init(c);
214     return c;
215 }
216 static void instance_init(instance_t*i)
217 {
218     memset(i, 0, sizeof(instance_t));
219 }
220 static instance_t* instance_new()
221 {
222     instance_t*c;
223     c = (instance_t*)malloc(sizeof(instance_t));
224     instance_init(c);
225     return c;
226 }
227
228 static void incrementid()
229 {
230     while(idmap[++id]) {
231         if(id==65535)
232             syntaxerror("Out of character ids.");
233     }
234     idmap[id] = 1;
235 }
236
237 static void s_addcharacter(char*name, U16 id, TAG*ctag, SRECT r)
238 {
239     character_t* c = character_new();
240     
241     c->definingTag = ctag;
242     c->id = id;
243     c->size = r;
244     if(dictionary_lookup(&characters, name))
245         syntaxerror("character %s defined twice", name);
246     dictionary_put2(&characters, name, c);
247
248     tag = swf_InsertTag(tag, ST_NAMECHARACTER);
249     swf_SetU16(tag, id);
250     swf_SetString(tag, name);
251     tag = swf_InsertTag(tag, ST_EXPORTASSETS);
252     swf_SetU16(tag, 1);
253     swf_SetU16(tag, id);
254     swf_SetString(tag, name);
255 }
256 static instance_t* s_addinstance(char*name, character_t*c, U16 depth)
257 {
258     instance_t* i = instance_new();
259     i->character = c;
260     i->depth = depth;
261     //swf_GetMatrix(0, &i->matrix);
262     if(dictionary_lookup(&instances, name))
263         syntaxerror("object %s defined twice", name);
264     dictionary_put2(&instances, name, i);
265     return i;
266 }
267
268 static void parameters_set(parameters_t*p, int x,int y, float scalex, float scaley, float rotate, float shear, SPOINT pivot, SPOINT pin, CXFORM cxform)
269 {
270     p->x = x; p->y = y; 
271     p->scalex = scalex; p->scaley = scaley;
272     p->pin    = pin; p->pivot = pivot;
273     p->rotate = rotate; p->cxform = cxform;
274     p->shear = shear;
275 }
276
277 static void parameters_clear(parameters_t*p)
278 {
279     p->x = 0; p->y = 0; 
280     p->scalex = 1.0; p->scaley = 1.0;
281     p->pin.x = 1; p->pin.y = 0;
282     p->pivot.x = 0; p->pivot.y = 0;
283     p->rotate = 0; 
284     p->shear = 0; 
285     swf_GetCXForm(0, &p->cxform, 1);
286 }
287
288 static void makeMatrix(MATRIX*m, parameters_t*p)
289 {
290     SPOINT h;
291     float sx,r1,r0,sy;
292
293     /*        /sx r1\ /x\ 
294      *        \r0 sy/ \y/
295      */
296
297     sx =  p->scalex*cos(p->rotate/360*2*3.14159265358979);
298     r1 = -p->scalex*sin(p->rotate/360*2*3.14159265358979)+sx*p->shear;
299     r0 =  p->scaley*sin(p->rotate/360*2*3.14159265358979);
300     sy =  p->scaley*cos(p->rotate/360*2*3.14159265358979)+r0*p->shear;
301
302     m->sx = (int)(sx*65536+0.5);
303     m->r1 = (int)(r1*65536+0.5);
304     m->r0 = (int)(r0*65536+0.5);
305     m->sy = (int)(sy*65536+0.5);
306
307     m->tx = m->ty = 0;
308
309     h = swf_TurnPoint(p->pin, m);
310     m->tx = p->x - h.x;
311     m->ty = p->y - h.y;
312 }
313
314 static MATRIX s_instancepos(instance_t*i, parameters_t*p)
315 {
316     MATRIX m;
317     SRECT r;
318     makeMatrix(&m, p);
319     r = swf_TurnRect(i->character->size, &m);
320     if(currentrect.xmin == 0 && currentrect.ymin == 0 && 
321        currentrect.xmax == 0 && currentrect.ymax == 0)
322         currentrect = r;
323     else
324         swf_ExpandRect2(&currentrect, &r);
325     return m;
326 }
327
328 void s_swf(char*name, SRECT r, int version, int fps, int compress)
329 {
330     SWF*swf = (SWF*)malloc(sizeof(SWF));
331     RGBA rgb;
332
333     if(stackpos)
334         syntaxerror(".swf blocks can't be nested");
335
336     memset(swf, 0, sizeof(swf));
337     swf->fileVersion = version;
338     swf->movieSize = r;
339     swf->frameRate = fps;
340     swf->firstTag = tag = swf_InsertTag(0, ST_SETBACKGROUNDCOLOR);
341     swf->compressed = compress;
342     rgb.r = 0x00;rgb.g = 0x00;rgb.b = 0x00;
343     swf_SetRGB(tag,&rgb);
344     
345     if(stackpos==sizeof(stack)/sizeof(stack[0]))
346         syntaxerror("too many levels of recursion");
347     
348     dictionary_init(&characters);
349     dictionary_init(&instances);
350     dictionary_init(&fonts);
351     dictionary_init(&sounds);
352
353     memset(&stack[stackpos], 0, sizeof(stack[0]));
354     stack[stackpos].type = 0;
355     stack[stackpos].filename = strdup(name);
356     stack[stackpos].swf = swf;
357     stack[stackpos].oldframe = -1;
358     stackpos++;
359     id = 0;
360
361     currentframe = 0;
362     memset(&currentrect, 0, sizeof(currentrect));
363     currentdepth = 1;
364     
365     memset(idmap, 0, sizeof(idmap));
366     incrementid();
367 }
368
369 void s_sprite(char*name)
370 {
371     tag = swf_InsertTag(tag, ST_DEFINESPRITE);
372     swf_SetU16(tag, id); //id
373     swf_SetU16(tag, 0); //frames
374
375     memset(&stack[stackpos], 0, sizeof(stack[0]));
376     stack[stackpos].type = 1;
377     stack[stackpos].oldframe = currentframe;
378     stack[stackpos].olddepth = currentdepth;
379     stack[stackpos].oldrect = currentrect;
380     stack[stackpos].oldinstances = instances;
381     stack[stackpos].tag = tag;
382     stack[stackpos].id = id;
383     stack[stackpos].name = strdup(name);
384    
385     /* FIXME: those four fields should be bundled together */
386     dictionary_init(&instances);
387     currentframe = 0;
388     currentdepth = 1;
389     memset(&currentrect, 0, sizeof(currentrect));
390
391     stackpos++;
392     incrementid();
393 }
394
395 static void s_endSprite()
396 {
397     SRECT r = currentrect;
398     stackpos--;
399    
400     /* TODO: before clearing, prepend "<spritename>." to names and
401              copy into old instances dict */
402     dictionary_clear(&instances);
403
404     currentframe = stack[stackpos].oldframe;
405     currentrect = stack[stackpos].oldrect;
406     currentdepth = stack[stackpos].olddepth;
407     instances = stack[stackpos].oldinstances;
408     tag = swf_InsertTag(tag, ST_END);
409
410     tag = stack[stackpos].tag;
411     swf_FoldSprite(tag);
412     if(tag->next != 0)
413         syntaxerror("internal error(7)");
414
415     s_addcharacter(stack[stackpos].name, stack[stackpos].id, stack[stackpos].tag, r);
416     free(stack[stackpos].name);
417 }
418
419 static void s_endSWF()
420 {
421     int fi;
422     SWF* swf;
423     char*filename;
424     stackpos--;
425
426     swf = stack[stackpos].swf;
427     filename = stack[stackpos].filename;
428     
429     tag = swf_InsertTag(tag, ST_SHOWFRAME);
430     tag = swf_InsertTag(tag, ST_END);
431
432     swf_OptimizeTagOrder(swf);
433
434     if(!(swf->movieSize.xmax-swf->movieSize.xmin) || !(swf->movieSize.ymax-swf->movieSize.ymin))
435         swf->movieSize = currentrect; /* "autocrop" */
436     
437     if(!(swf->movieSize.xmax-swf->movieSize.xmin) || !(swf->movieSize.ymax-swf->movieSize.ymin)) {
438         swf->movieSize.xmax += 20; /* 1 by 1 pixels */
439         swf->movieSize.ymax += 20;
440     }
441
442     fi = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0644);
443     if(fi<0) {
444         syntaxerror("couldn't create output file %s", filename);
445     }
446     if(swf->compressed) 
447         {if(swf_WriteSWC(fi, swf)<0) syntaxerror("WriteSWC() failed.\n");}
448     else
449         {if(swf_WriteSWF(fi, swf)<0) syntaxerror("WriteSWF() failed.\n");}
450
451     close(fi);
452     
453     dictionary_clear(&instances);
454     dictionary_clear(&characters);
455     dictionary_clear(&fonts);
456     dictionary_clear(&sounds);
457
458     swf_FreeTags(swf);
459     free(swf);
460     free(filename);
461 }
462 void s_close()
463 {
464     if(stackpos) {
465         if(stack[stackpos-1].type == 0)
466             syntaxerror("End of file encountered in .swf block");
467         if(stack[stackpos-1].type == 1)
468             syntaxerror("End of file encountered in .sprite block");
469         if(stack[stackpos-1].type == 2)
470             syntaxerror("End of file encountered in .clip block");
471     }
472 }
473
474 int s_getframe()
475 {
476     return currentframe;
477 }
478
479 void s_frame(int nr)
480 {
481     int t;
482     for(t=currentframe;t<nr;t++) {
483         tag = swf_InsertTag(tag, ST_SHOWFRAME);
484     }
485     currentframe = nr;
486 }
487         
488 RGBA black={r:0,g:0,b:0,a:0};
489 void s_box(char*name, int width, int height, RGBA color, int linewidth, RGBA fill, int dofill)
490 {
491     SRECT r;
492     SHAPE* s;
493     int ls1,fs1=0;
494     tag = swf_InsertTag(tag, ST_DEFINESHAPE);
495     swf_ShapeNew(&s);
496     ls1 = swf_ShapeAddLineStyle(s,linewidth,&color);
497     if(dofill)
498         fs1 = swf_ShapeAddSolidFillStyle(s,&fill);
499     swf_SetU16(tag,id);
500     r.xmin = -linewidth-linewidth/2;
501     r.ymin = -linewidth-linewidth/2;
502     r.xmax = width+linewidth+linewidth/2;
503     r.ymax = height+linewidth+linewidth/2;
504     swf_SetRect(tag,&r);
505     swf_SetShapeHeader(tag,s);
506     swf_ShapeSetAll(tag,s,0,0,ls1,fs1,0);
507     swf_ShapeSetLine(tag,s,width,0);
508     swf_ShapeSetLine(tag,s,0,height);
509     swf_ShapeSetLine(tag,s,-width,0);
510     swf_ShapeSetLine(tag,s,0,-height);
511     swf_ShapeSetEnd(tag);
512     swf_ShapeFree(s);
513    
514     s_addcharacter(name, id, tag, r);
515     incrementid();
516 }
517
518 void s_circle(char*name, int r, RGBA color, int linewidth, RGBA fill, int dofill)
519 {
520     SRECT rect;
521     SHAPE* s;
522     int ls1,fs1=0;
523     tag = swf_InsertTag(tag, ST_DEFINESHAPE);
524     swf_ShapeNew(&s);
525     ls1 = swf_ShapeAddLineStyle(s,linewidth,&color);
526     if(dofill)
527         fs1 = swf_ShapeAddSolidFillStyle(s,&fill);
528     swf_SetU16(tag,id);
529     rect.xmin = -linewidth-linewidth/2;
530     rect.ymin = -linewidth-linewidth/2;
531     rect.xmax = 2*r+linewidth+linewidth/2;
532     rect.ymax = 2*r+linewidth+linewidth/2;
533
534     swf_SetRect(tag,&rect);
535     swf_SetShapeHeader(tag,s);
536     swf_ShapeSetAll(tag,s,0,0,ls1,fs1,0);
537     swf_ShapeSetCircle(tag, s, r,r,r,r);
538     swf_ShapeSetEnd(tag);
539     swf_ShapeFree(s);
540    
541     s_addcharacter(name, id, tag, rect);
542     incrementid();
543 }
544
545 void s_textshape(char*name, char*fontname, char*_text, RGBA color, int linewidth, RGBA fill, int dofill)
546 {
547     SRECT rect;
548     SHAPE* s;
549     int ls1,fs1=0;
550     int g;
551     U8*text = (U8*)_text;
552
553     SWFFONT*font;
554     font = dictionary_lookup(&fonts, fontname);
555     if(!font)
556         syntaxerror("font \"%s\" not known!", fontname);
557     if(!dofill)
558         syntaxerror("textshapes must be filled", fontname);
559
560     if(text[0] >= font->maxascii || font->ascii2glyph[text[0]]<0) {
561         warning("no character 0x%02x (%c) in font \"%s\"", text[0], text[0], fontname);
562         s_box(name, 0, 0, black, 20, black, 0);
563         return;
564     }
565     g = font->ascii2glyph[text[0]];
566     rect = font->layout->bounds[g];
567
568     swf_ShapeNew(&s);
569     ls1 = swf_ShapeAddLineStyle(s,linewidth,&color);
570     if(dofill)
571         fs1 = swf_ShapeAddSolidFillStyle(s,&fill);
572      
573     tag = swf_InsertTag(tag, ST_DEFINESHAPE);
574     swf_SetU16(tag,id);
575     swf_SetRect(tag, &rect);
576     swf_SetShapeStyles(tag, s);
577     swf_SetSimpleShape(tag, font->glyph[g].shape);
578     swf_ShapeFree(s);
579     
580     s_addcharacter(name, id, tag, rect);
581     incrementid();
582 }
583 void s_text(char*name, char*fontname, char*text, int size, RGBA color)
584 {
585     SRECT r;
586
587     SWFFONT*font;
588     font = dictionary_lookup(&fonts, fontname);
589     if(!font)
590         syntaxerror("font \"%s\" not known!", fontname);
591     
592     tag = swf_InsertTag(tag, ST_DEFINETEXT2);
593     swf_SetU16(tag, id);
594     if(!font->numchars) {
595         s_box(name, 0, 0, black, 20, black, 0);
596         return;
597     }
598     r = swf_SetDefineText(tag, font, &color, text, size);
599    
600     s_addcharacter(name, id, tag, r);
601     incrementid();
602 }
603
604 void dumpSWF(SWF*swf)
605 {
606     TAG* tag = swf->firstTag;
607     printf("vvvvvvvvvvvvvvvvvvvvv\n");
608     while(tag) {
609         printf("%8d %s\n", tag->len, swf_TagGetName(tag));
610         tag = tag->next;
611     }
612     printf("^^^^^^^^^^^^^^^^^^^^^\n");
613 }
614     
615 void s_font(char*name, char*filename)
616 {
617     int f;
618     SWF swf;
619     SWFFONT* font;
620     f = open(filename,O_RDONLY|O_BINARY);
621     if (f<0) { 
622         warning("Couldn't open file \"%s\": %s", filename, strerror(errno));
623         font = (SWFFONT*)malloc(sizeof(SWFFONT));
624         memset(font, 0, sizeof(SWFFONT));
625         dictionary_put2(&fonts, name, font);
626         return;
627     }
628     font = 0;
629     if (swf_ReadSWF(f,&swf)>=0) { 
630         swf_FontExtract(&swf, 0x4e46, &font);
631         swf_FreeTags(&swf);
632     }
633     close(f);
634     if (font==0) { 
635         syntaxerror("File \"%s\" isn't a valid rfxswf font file", filename);
636     }
637
638     if(0)
639     {
640         /* fix the layout. Only needed for old fonts */
641         int t;
642         for(t=0;t<font->numchars;t++) {
643             font->glyph[t].advance = 0;
644         }
645         font->layout = 0;
646         swf_FontCreateLayout(font);
647     }
648
649     font->id = id;
650     tag = swf_InsertTag(tag, ST_DEFINEFONT2);
651     swf_FontSetDefine2(tag, font);
652     incrementid();
653
654     if(dictionary_lookup(&fonts, name))
655         syntaxerror("font %s defined twice", name);
656     dictionary_put2(&fonts, name, font);
657 }
658
659 typedef struct _sound_t
660 {
661     U16 id;
662     TAG*tag;
663 } sound_t;
664
665 void s_sound(char*name, char*filename)
666 {
667     struct WAV wav, wav2;
668     sound_t* sound;
669     U16*samples;
670     int numsamples;
671
672     if(!readWAV(filename, &wav)) {
673         warning("Couldn't read wav file \"%s\"", filename);
674         samples = 0;
675         numsamples = 0;
676     } else {
677         convertWAV2mono(&wav, &wav2, 44100);
678         samples = (U16*)wav2.data;
679         numsamples = wav2.size/2;
680         free(wav.data);
681     }
682
683     tag = swf_InsertTag(tag, ST_DEFINESOUND);
684     swf_SetU16(tag, id); //id
685     swf_SetSoundDefine(tag, samples, numsamples);
686    
687     sound = (sound_t*)malloc(sizeof(sound_t)); /* mem leak */
688     sound->tag = tag;
689     sound->id = id;
690
691     if(dictionary_lookup(&sounds, name))
692         syntaxerror("sound %s defined twice", name);
693     dictionary_put2(&sounds, name, sound);
694     
695     incrementid();
696
697     if(samples)
698         free(samples);
699 }
700
701 void s_playsound(char*name, int loops, int nomultiple, int stop)
702 {
703     sound_t* sound = dictionary_lookup(&sounds, name);
704     SOUNDINFO info;
705     if(!sound)
706         syntaxerror("Don't know anything about sound \"%s\"", name);
707
708     tag = swf_InsertTag(tag, ST_STARTSOUND);
709     swf_SetU16(tag, sound->id); //id
710     memset(&info, 0, sizeof(info));
711     info.stop = stop;
712     info.loops = loops;
713     info.nomultiple = nomultiple;
714     swf_SetSoundInfo(tag, &info);
715 }
716
717 void s_shape(char*name, char*filename)
718 {
719     int f;
720     SWF swf;
721     TAG* ftag;
722     SRECT r;
723     TAG* s;
724     int level = 0;
725     U16 cutout[] = {ST_SETBACKGROUNDCOLOR, ST_PROTECT, ST_FREEALL, ST_REFLEX};
726     f = open(filename,O_RDONLY);
727     if (f<0) { 
728         warning("Couldn't open file \"%s\": %s", filename, strerror(errno));
729         s_box(name, 0, 0, black, 20, black, 0);
730         return;
731     }
732     if (swf_ReadSWF(f,&swf)<0) { 
733         warning("Only SWF files supported in .shape for now. File \"%s\" wasn't SWF.", filename);
734         s_box(name, 0, 0, black, 20, black, 0);
735         return;
736     }
737     close(f);
738
739     /* FIXME: The following sets the bounding Box for the character. 
740               It is wrong for two reasons:
741               a) It may be too small (in case objects in the movie clip at the borders)
742               b) it may be too big (because the poor movie never got autocropped)
743     */
744     r = swf.movieSize;
745     
746     s = tag = swf_InsertTag(tag, ST_DEFINESPRITE);
747     swf_SetU16(tag, id);
748     swf_SetU16(tag, 0);
749
750     swf_Relocate(&swf, idmap);
751
752     ftag = swf.firstTag;
753     level = 1;
754     while(ftag) {
755         int t;
756         for(t=0;t<sizeof(cutout)/sizeof(cutout[0]);t++)
757             if(cutout[t] == ftag->id) {
758                 ftag = ftag->next;
759                 continue;
760             }
761         if(ftag->id == ST_DEFINESPRITE && !swf_IsFolded(ftag))
762             level++;
763         if(ftag->id == ST_END)
764             level--;
765         if(!level)
766             break;
767         /* We simply dump all tags right after the sprite
768            header, relying on the fact that swf_OptimizeTagOrder() will
769            sort things out for us later. 
770            We also rely on the fact that the imported SWF is well-formed.
771          */
772         tag = swf_InsertTag(tag, ftag->id);
773         swf_SetBlock(tag, ftag->data, ftag->len);
774         ftag = ftag->next;
775     }
776     if(!ftag)
777         syntaxerror("Included file %s contains errors", filename);
778     tag = swf_InsertTag(tag, ST_END);
779
780     swf_FreeTags(&swf);
781
782     s_addcharacter(name, id, tag, r);
783     incrementid();
784 }
785 SRECT s_getCharBBox(char*name)
786 {
787     character_t* c = dictionary_lookup(&characters, name);
788     if(!c) syntaxerror("character '%s' unknown(2)", name);
789     return c->size;
790 }
791 SRECT s_getInstanceBBox(char*name)
792 {
793     instance_t * i = dictionary_lookup(&instances, name);
794     character_t * c;
795     if(!i) syntaxerror("instance '%s' unknown(4)", name);
796     c = i->character;
797     if(!c) syntaxerror("internal error(5)");
798     return c->size;
799 }
800 parameters_t s_getParameters(char*name)
801 {
802     instance_t * i = dictionary_lookup(&instances, name);
803     if(!i) syntaxerror("instance '%s' unknown(10)", name);
804     return i->parameters;
805 }
806 void s_startclip(char*instance, char*character, parameters_t p)
807 {
808     character_t* c = dictionary_lookup(&characters, character);
809     instance_t* i;
810     MATRIX m;
811     if(!c) {
812         syntaxerror("character %s not known", character);
813     }
814     i = s_addinstance(instance, c, currentdepth);
815     i->parameters = p;
816     m = s_instancepos(i, &p);
817     
818     tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
819     /* TODO: should be ObjectPlaceClip, with clipdepth backpatched */
820     swf_ObjectPlace(tag, c->id, currentdepth, &m, &p.cxform, instance);
821     i->lastTag = tag;
822     i->lastFrame= currentframe;
823
824     stack[stackpos].tag = tag;
825     stack[stackpos].type = 2;
826     stackpos++;
827
828     currentdepth++;
829 }
830 void s_endClip()
831 {
832     SWFPLACEOBJECT p;
833     stackpos--;
834     swf_SetTagPos(stack[stackpos].tag, 0);
835     swf_GetPlaceObject(stack[stackpos].tag, &p);
836     p.clipdepth = currentdepth;
837     swf_ClearTag(stack[stackpos].tag);
838     swf_SetPlaceObject(stack[stackpos].tag, &p);
839     currentdepth++;
840 }
841
842 void s_put(char*instance, char*character, parameters_t p)
843 {
844     character_t* c = dictionary_lookup(&characters, character);
845     instance_t* i;
846     MATRIX m;
847     if(!c) {
848         syntaxerror("character %s not known (in .put %s=%s)", character, instance, character);
849     }
850     
851     i = s_addinstance(instance, c, currentdepth);
852     i->parameters = p;
853     m = s_instancepos(i, &p);
854     
855     tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
856     swf_ObjectPlace(tag, c->id, currentdepth, &m, &p.cxform, instance);
857     i->lastTag = tag;
858     i->lastFrame = currentframe;
859     currentdepth++;
860 }
861
862 void s_jump(char*instance, parameters_t p)
863 {
864     instance_t* i = dictionary_lookup(&instances, instance);
865     MATRIX m;
866     if(!i) {
867         syntaxerror("instance %s not known", instance);
868     }
869
870     i->parameters = p;
871     m = s_instancepos(i, &p);
872
873     tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
874     swf_ObjectMove(tag, i->depth, &m, &p.cxform);
875     i->lastTag = tag;
876     i->lastFrame = currentframe;
877 }
878
879 parameters_t s_interpolate(parameters_t*p1, parameters_t*p2, int pos, int num)
880 {
881     parameters_t p;
882     float ratio;
883     if(num==0 || num==1)
884         return *p1;
885     ratio = (float)pos/(float)num;
886     
887     p.x = (p2->x-p1->x)*ratio + p1->x;
888     p.y = (p2->y-p1->y)*ratio + p1->y;
889     p.scalex = (p2->scalex-p1->scalex)*ratio + p1->scalex;
890     p.scaley = (p2->scaley-p1->scaley)*ratio + p1->scaley;
891     p.rotate = (p2->rotate-p1->rotate)*ratio + p1->rotate;
892     p.shear = (p2->shear-p1->shear)*ratio + p1->shear;
893
894     p.cxform.r0 = ((float)p2->cxform.r0-(float)p1->cxform.r0)*ratio + p1->cxform.r0;
895     p.cxform.g0 = ((float)p2->cxform.g0-(float)p1->cxform.g0)*ratio + p1->cxform.g0;
896     p.cxform.b0 = ((float)p2->cxform.b0-(float)p1->cxform.b0)*ratio + p1->cxform.b0;
897     p.cxform.a0 = ((float)p2->cxform.a0-(float)p1->cxform.a0)*ratio + p1->cxform.a0;
898
899     p.cxform.r1 = (p2->cxform.r1-p1->cxform.r1)*ratio + p1->cxform.r1;
900     p.cxform.g1 = (p2->cxform.g1-p1->cxform.g1)*ratio + p1->cxform.g1;
901     p.cxform.b1 = (p2->cxform.b1-p1->cxform.b1)*ratio + p1->cxform.b1;
902     p.cxform.a1 = (p2->cxform.a1-p1->cxform.a1)*ratio + p1->cxform.a1;
903
904     p.pivot.x = (p2->pivot.x-p1->pivot.x)*ratio + p1->pivot.x;
905     p.pivot.y = (p2->pivot.y-p1->pivot.y)*ratio + p1->pivot.y;
906     p.pin.x = (p2->pin.x-p1->pin.x)*ratio + p1->pin.x;
907     p.pin.y = (p2->pin.y-p1->pin.y)*ratio + p1->pin.y;
908     return p;
909 }
910
911 void s_change(char*instance, parameters_t p2)
912 {
913     instance_t* i = dictionary_lookup(&instances, instance);
914     MATRIX m;
915     parameters_t p1;
916     TAG*t;
917     int frame, allframes;
918     if(!i) {
919         syntaxerror("instance %s not known", instance);
920     }
921     p1 = i->parameters;
922     
923     allframes = currentframe - i->lastFrame - 1;
924     if(allframes < 0) {
925         warning(".change ignored. can only .put/.change an object once per frame.");
926         return;
927     }
928     
929     m = s_instancepos(i, &p2);
930     tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
931     swf_ObjectMove(tag, i->depth, &m, &p2.cxform);
932     i->parameters = p2;
933
934     /* o.k., we got the start and end point set. Now iterate though all the
935        tags in between, inserting object changes after each new frame */
936     t = i->lastTag;
937     i->lastTag = tag;
938     if(!t) syntaxerror("internal error(6)");
939     frame = 0;
940     while(frame < allframes) {
941         if(t->id == ST_SHOWFRAME) {
942             parameters_t p;
943             MATRIX m;
944             TAG*lt;
945             frame ++;
946             p = s_interpolate(&p1, &p2, frame, allframes);
947             m = s_instancepos(i, &p); //needed?
948             lt = swf_InsertTag(t, ST_PLACEOBJECT2);
949             i->lastFrame = currentframe;
950             swf_ObjectMove(lt, i->depth, &m, &p.cxform);
951             t = lt;
952             if(frame == allframes)
953                 break;
954         }
955         t = t->next;
956         if(!t) 
957             syntaxerror("internal error(8) (frame=%d/%d)", frame, allframes);
958     }
959 }
960
961 void s_delinstance(char*instance)
962 {
963     instance_t* i = dictionary_lookup(&instances, instance);
964     if(!i) {
965         syntaxerror("instance %s not known", instance);
966     }
967     tag = swf_InsertTag(tag, ST_REMOVEOBJECT2);
968     swf_SetU16(tag, i->depth);
969     dictionary_del(&instances, instance);
970 }
971
972 void s_qchange(char*instance, parameters_t p)
973 {
974 }
975
976 void s_end()
977 {
978     if(!stackpos)
979         syntaxerror(".end unexpected");
980     if(stack[stackpos-1].type == 0)
981         s_endSWF();
982     else if(stack[stackpos-1].type == 1)
983         s_endSprite();
984     else if(stack[stackpos-1].type == 2)
985         s_endClip();
986     else syntaxerror("internal error 1");
987 }
988
989 // ------------------------------------------------------------------------
990
991 typedef int command_func_t(map_t*args);
992
993 SRECT parseBox(char*str)
994 {
995     SRECT r;
996     float xmin, xmax, ymin, ymax;
997     char*x = strchr(str, 'x');
998     char*d1=0,*d2=0;
999     if(!strcmp(str, "autocrop")) {
1000         r.xmin = r.ymin = r.xmax = r.ymax = 0;
1001         return r;
1002     }
1003     if(!x) goto error;
1004     d1 = strchr(x+1, ':');
1005     if(d1)
1006         d2 = strchr(d1+1, ':');
1007     if(!d1) {
1008         if(sscanf(str, "%fx%f", &xmax, &ymax) < 2)
1009             goto error;
1010         xmin = ymin = 0;
1011     }
1012     else if(d1 && !d2) {
1013         if(sscanf(str, "%fx%f:%f", &xmax, &ymax, &xmin) < 3)
1014             goto error;
1015         xmax += xmin;
1016         ymin = 0;
1017     }
1018     else {
1019         if(sscanf(str, "%fx%f:%f:%f", &xmax, &ymax, &xmin, &ymin) < 4)
1020             goto error;
1021         xmax += xmin;
1022         ymax += ymin;
1023     }
1024     r.xmin = (SCOORD)(xmin*20);
1025     r.ymin = (SCOORD)(ymin*20);
1026     r.xmax = (SCOORD)(xmax*20);
1027     r.ymax = (SCOORD)(ymax*20);
1028     return r;
1029 error:
1030     syntaxerror("expression %s is not a valid bound Box.\nE.g. 1024x768 or 1024x768:30:30 would have been valid bounding Boxes.", str);
1031     return r;
1032 }
1033 float parseFloat(char*str)
1034 {
1035     return atof(str);
1036 }
1037 int parseInt(char*str)
1038 {
1039     int t;
1040     int l=strlen(str);
1041     int s=0;
1042     if(str[0]=='+' || str[0]=='-')
1043         s++;
1044
1045     for(t=s;t<l;t++)
1046         if(str[t]<'0' || str[t]>'9')
1047             syntaxerror("Not an Integer: \"%s\"", str);
1048     return atoi(str);
1049 }
1050 int parseTwip(char*str)
1051 {
1052     char*dot;
1053     int sign=1;
1054     if(str[0]=='+' || str[0]=='-') {
1055         if(str[0]=='-')
1056             sign = -1;
1057         str++;
1058     }
1059     dot = strchr(str, '.');
1060     if(!dot) {
1061         int l=strlen(str);
1062         int t;
1063         return sign*parseInt(str)*20;
1064     } else {
1065         int l=strlen(++dot);
1066         char*s;
1067         for(s=str;s<dot-1;s++)
1068             if(*s<'0' || *s>'9')
1069                 syntaxerror("Not a coordinate: \"%s\"", str);
1070         for(s=dot;*s;s++) {
1071             if(*s<'0' || *s>'9')
1072                 syntaxerror("Not a coordinate: \"%s\"", str);
1073         }
1074         if(l>2 || (l==2 && (dot[1]!='0' || dot[1]!='5'))) {
1075             warning("precision loss: %s converted to twip", str);
1076             dot[2] = 0;
1077             l=2;
1078         }
1079         if(l==0)
1080             return sign*atoi(str)*20;
1081         if(l==1)
1082             return sign*atoi(str)*20+atoi(dot)*2;
1083         if(l==2)
1084             return sign*atoi(str)*20+atoi(dot)/5;
1085     }
1086     return 0;
1087 }
1088
1089 int isPoint(char*str)
1090 {
1091     if(strchr(str, '('))
1092         return 1;
1093     else
1094         return 0;
1095 }
1096
1097 SPOINT parsePoint(char*str)
1098 {
1099     SPOINT p;
1100     char tmp[80];
1101     int l = strlen(str);
1102     char*comma = strchr(str, ',');
1103     if(str[0]!='(' || str[l-1]!=')' || !comma || l>70)
1104         syntaxerror("\"%s\" is not a valid point of the form (x,y)", str);
1105     strncpy(tmp, str+1, comma-(str+1));tmp[comma-(str+1)]=0;
1106     p.x = parseTwip(tmp);
1107     strncpy(tmp, comma+1, l-1-(comma+1-str));tmp[l-1-(comma+1-str)]=0;
1108     p.y = parseTwip(tmp);
1109     return p;
1110 }
1111
1112 int parseColor2(char*str, RGBA*color)
1113 {
1114     int l = strlen(str);
1115     int r,g,b,a;
1116     int t;
1117
1118     struct {unsigned char r,g,b;char*name;} colors[] =
1119     {{255,250,250,"snow"},{220,220,220,"gainsboro"},{250,240,230,"linen"},{255,228,196,"bisque"},
1120     {255,228,181,"moccasin"},{255,248,220,"cornsilk"},{255,255,240,"ivory"},{255,245,238,"seashell"},
1121     {240,255,240,"honeydew"},{240,255,255,"azure"},{230,230,250,"lavender"},{255,255,255,"white"},
1122     {0,0,0,"black"},{190,190,190,"gray"},{190,190,190,"grey"},{0,0,128,"navy"},{0,0,255,"blue"},
1123     {64,224,208,"turquoise"},{0,255,255,"cyan"},{127,255,212,"aquamarine"}, {0,255,0,"green"},
1124     {127,255,0,"chartreuse"},{240,230,140,"khaki"},{255,255,0,"yellow"},
1125     {255,215,0,"gold"},{218,165,32,"goldenrod"},{160,82,45,"sienna"},{205,133,63,"peru"},
1126     {222,184,135,"burlywood"},{245,245,220,"beige"},{245,222,179,"wheat"},{210,180,140,"tan"},
1127     {210,105,30,"chocolate"},{178,34,34,"firebrick"},{165,42,42,"brown"},{250,128,114,"salmon"},
1128     {255,165,0,"orange"},{255,127,80,"coral"},{255,99,71,"tomato"},{255,0,0,"red"},
1129     {255,192,203,"pink"},{176,48,96,"maroon"},{255,0,255,"magenta"},{238,130,238,"violet"},
1130     {221,160,221,"plum"},{218,112,214,"orchid"},{160,32,240,"purple"},{216,191,216,"thistle"}};
1131
1132     a=255;r=g=b=0;
1133
1134     if(str[0]=='#' && (l==7 || l==9)) {
1135         if(l == 7 && !(sscanf(str, "#%02x%02x%02x", &r, &g, &b)))
1136             return 0;
1137         if(l == 9 && !(sscanf(str, "#%02x%02x%02x%02x", &r, &g, &b, &a)))
1138             return 0;
1139         color->r = r; color->g = g; color->b = b; color->a = a;
1140         return 1;
1141     }
1142     for(t=0;t<sizeof(colors)/sizeof(colors[0]);t++)
1143         if(!strcmp(str, colors[t].name)) {
1144             r = colors[t].r;
1145             g = colors[t].g;
1146             b = colors[t].b;
1147             a = 255;
1148             color->r = r; color->g = g; color->b = b; color->a = a;
1149             return 1;
1150         }
1151     return 0;
1152
1153 }
1154 RGBA parseColor(char*str)
1155 {
1156     RGBA c;
1157     if(!parseColor2(str, &c))
1158         syntaxerror("Expression '%s' is not a color", str);
1159     return c;
1160 }
1161
1162 typedef struct _muladd {
1163     S16 mul;
1164     S16 add;
1165 } MULADD;
1166
1167 MULADD parseMulAdd(char*str)
1168 {
1169     float add, mul;
1170     char* str2 = (char*)malloc(strlen(str)+5);
1171     int i;
1172     MULADD m;
1173     strcpy(str2, str);
1174     strcat(str2, " 0");
1175     add = 0;
1176     mul = 1.0;
1177     if(sscanf(str2, "%f%f %d", &mul, &add, &i)==2+1) { add/=256.0; i=1;}
1178     else if(sscanf(str2, "%f%%%f %d", &mul, &add, &i)==2+1) { mul/=100.0; add/=256.0; i=2;}
1179     else if(sscanf(str2, "%f%f%% %d", &mul, &add, &i)==2+1) { add/=100.0; i=3;}
1180     else if(sscanf(str2, "%f%%%f%% %d", &mul, &add, &i)==2+1) { mul/=100.0; add/=100.0; i=4;}
1181     else if(sscanf(str2, "+%f%% %d", &add, &i)==1+1) { mul=1.0;add/=100.0; i=5;}
1182     else if(sscanf(str2, "+%f %d", &add, &i)==1+1) { mul=1.0;add/=256.0; i=6;}
1183     else if(sscanf(str2, "-%f%% %d", &add, &i)==1+1) { mul=1.0;add/=-100.0; i=7;}
1184     else if(sscanf(str2, "-%f %d", &add, &i)==1+1) { mul=1.0;add/=-256.0; i=8;}
1185     else if(sscanf(str2, "%f%% %d", &mul, &i)==1+1) { mul/=100.0;add=0; i=9;}
1186     else if(sscanf(str2, "%f %d", &mul, &i)==1+1) { add=0; i=10;}
1187     else {
1188         syntaxerror("'%s' is not a valid color transform expression", str);
1189     }
1190     m.add = (int)(add*256);
1191     m.mul = (int)(mul*256);
1192     free(str2);
1193     return m;
1194 }
1195
1196 MULADD mergeMulAdd(MULADD m1, MULADD m2)
1197 {
1198     int a = (int)((double)m2.add+((double)m1.add*(double)m2.mul)/256.0);
1199     double m = ((double)m1.mul*(double)m2.mul)/256.0;
1200     MULADD r;
1201     if(a<-32768) a=-32768;
1202     if(a>32767) a=32767;
1203     if(m<-32768) m=-32768;
1204     if(m>32767) m=32767;
1205     r.add = a;
1206     r.mul = (int)m;
1207     return r;
1208 }
1209
1210 float parsePercent(char*str)
1211 {
1212     int l = strlen(str);
1213     if(!l)
1214         return 1.0;
1215     if(str[l-1]=='%') {
1216         return atoi(str)/100.0;
1217     }
1218     syntaxerror("Expression '%s' is not a percentage", str);
1219     return 0;
1220 }
1221 int isPercent(char*str)
1222 {
1223     return str[strlen(str)-1]=='%';
1224 }
1225 int parseNewSize(char*str, int size)
1226 {
1227     if(isPercent(str))
1228         return parsePercent(str)*size;
1229     else
1230         return (int)(atof(str)*20);
1231 }
1232
1233 int isColor(char*str)
1234 {
1235     RGBA c;
1236     return parseColor2(str, &c);
1237 }
1238
1239 static char* lu(map_t* args, char*name)
1240 {
1241     char* value = map_lookup(args, name);
1242     if(!value) {
1243         map_dump(args, stdout, "");
1244         syntaxerror("internal error 2: value %s should be set", name);
1245     }
1246     return value;
1247 }
1248
1249 static int c_swf(map_t*args) 
1250 {
1251     char* name = lu(args, "name");
1252     char* compressstr = lu(args, "compress");
1253     SRECT bbox = parseBox(lu(args, "bbox"));
1254     int version = parseInt(lu(args, "version"));
1255     int fps = (int)(parseFloat(lu(args, "fps"))*256);
1256     int compress = 0;
1257     if(!strcmp(name, "!default!") || override_outputname)
1258         name = outputname;
1259     
1260     if(!strcmp(compressstr, "default"))
1261         compress = version==6;
1262     else if(!strcmp(compressstr, "yes") || !strcmp(compressstr, "compress"))
1263         compress = 1;
1264     else if(!strcmp(compressstr, "no"))
1265         compress = 0;
1266     else syntaxerror("value \"%s\" not supported for the compress argument", compressstr);
1267
1268     s_swf(name, bbox, version, fps, compress);
1269     return 0;
1270 }
1271 int isRelative(char*str)
1272 {
1273     return !strncmp(str, "<plus>", 6) ||
1274            !strncmp(str, "<minus>", 7);
1275 }
1276 char* getOffset(char*str)
1277 {
1278     if(!strncmp(str, "<plus>", 6))
1279         return str+6;
1280     if(!strncmp(str, "<minus>", 7))
1281         return str+7;
1282     syntaxerror("internal error (347)");
1283     return 0;
1284 }
1285 int getSign(char*str)
1286 {
1287     if(!strncmp(str, "<plus>", 6))
1288         return 1;
1289     if(!strncmp(str, "<minus>", 7))
1290         return -1;
1291     syntaxerror("internal error (348)");
1292     return 0;
1293 }
1294 static dictionary_t points;
1295 static mem_t mpoints;
1296 int points_initialized = 0;
1297
1298 SPOINT getPoint(SRECT r, char*name)
1299 {
1300     int l=0;
1301     if(!strcmp(name, "center")) {
1302         SPOINT p;
1303         p.x = (r.xmin + r.xmax)/2;
1304         p.y = (r.ymin + r.ymax)/2;
1305         return p;
1306     }
1307
1308     if(points_initialized)
1309         l = (int)dictionary_lookup(&points, name);
1310     if(l==0) {
1311         syntaxerror("Invalid point: \"%s\".", name);
1312     }
1313     l--;
1314     return *(SPOINT*)&mpoints.buffer[l];
1315 }
1316 static int c_gradient(map_t*args) 
1317 {
1318     return 0;
1319 }
1320 static int c_point(map_t*args) 
1321 {
1322     char*name = lu(args, "name");
1323     int pos;
1324     string_t s1;
1325     SPOINT p;
1326     if(!points_initialized) {
1327         dictionary_init(&points);
1328         mem_init(&mpoints);
1329         points_initialized = 1;
1330     }
1331     p.x = parseTwip(lu(args, "x"));
1332     p.y = parseTwip(lu(args, "y"));
1333     pos = mem_put(&mpoints, &p, sizeof(p));
1334     string_set(&s1, name);
1335     pos++;
1336     dictionary_put(&points, s1, (void*)pos);
1337     return 0;
1338 }
1339 static int c_play(map_t*args) 
1340 {
1341     char*name = lu(args, "sound");
1342     char*loop = lu(args, "loop");
1343     char*nomultiple = lu(args, "nomultiple");
1344     int nm = 0;
1345     if(!strcmp(nomultiple, "nomultiple"))
1346         nm = 1;
1347     else
1348         nm = parseInt(nomultiple);
1349
1350     s_playsound(name, parseInt(loop), nm, 0);
1351     return 0;
1352 }
1353
1354 static int c_stop(map_t*args) 
1355 {
1356     char*name = lu(args, "sound");
1357     s_playsound(name, 0,0,1);
1358     return 0;
1359 }
1360
1361 static int c_placement(map_t*args, int type)
1362 {
1363     char*instance = lu(args, (type==0||type==4)?"instance":"name");
1364     char*character = 0;
1365
1366     char* luminancestr = lu(args, "luminance");
1367     char* scalestr = lu(args, "scale");
1368     char* scalexstr = lu(args, "scalex");
1369     char* scaleystr = lu(args, "scaley");
1370     char* rotatestr = lu(args, "rotate");
1371     char* shearstr = lu(args, "shear");
1372     char* xstr="", *pivotstr="";
1373     char* ystr="", *anglestr="";
1374     char*above = lu(args, "above"); /*FIXME*/
1375     char*below = lu(args, "below");
1376     char* rstr = lu(args, "red");
1377     char* gstr = lu(args, "green");
1378     char* bstr = lu(args, "blue");
1379     char* astr = lu(args, "alpha");
1380     char* pinstr = lu(args, "pin");
1381     MULADD r,g,b,a;
1382     float oldwidth;
1383     float oldheight;
1384     SRECT oldbbox;
1385     MULADD luminance;
1386     parameters_t p;
1387
1388     if(type==5) {
1389         pivotstr = lu(args, "pivot");
1390         anglestr = lu(args, "angle");
1391     } else {
1392         xstr = lu(args, "x");
1393         ystr = lu(args, "y");
1394     }
1395     if(luminancestr[0])
1396         luminance = parseMulAdd(luminancestr);
1397     else {
1398         luminance.add = 0;
1399         luminance.mul = 256;
1400     }
1401
1402     if(scalestr[0]) {
1403         if(scalexstr[0]||scaleystr[0])
1404             syntaxerror("scalex/scaley and scale cannot both be set");
1405         scalexstr = scaleystr = scalestr;
1406     }
1407     
1408     if(type == 0 || type == 4)  {
1409         // put or startclip
1410         character = lu(args, "character");
1411         parameters_clear(&p);
1412     } else {
1413         p = s_getParameters(instance);
1414     }
1415
1416     /* x,y position */
1417     if(xstr[0]) {
1418         if(isRelative(xstr)) {
1419             if(type == 0 || type == 4)
1420                 syntaxerror("relative x values not allowed for initial put or startclip");
1421             p.x += parseTwip(getOffset(xstr))*getSign(xstr);
1422         } else {
1423             p.x = parseTwip(xstr);
1424         }
1425     }
1426     if(ystr[0]) {
1427         if(isRelative(ystr)) {
1428             if(type == 0 || type == 4)
1429                 syntaxerror("relative y values not allowed for initial put or startclip");
1430             p.y += parseTwip(getOffset(ystr))*getSign(ystr);
1431         } else {
1432             p.y = parseTwip(ystr);
1433         }
1434     }
1435
1436     /* scale, scalex, scaley */
1437     if(character) {
1438         oldbbox = s_getCharBBox(character);
1439     } else {
1440         oldbbox = s_getInstanceBBox(instance);
1441     }
1442     oldwidth = oldbbox.xmax - oldbbox.xmin;
1443     oldheight = oldbbox.ymax - oldbbox.ymin;
1444     if(scalexstr[0]) {
1445         if(oldwidth==0) p.scalex = 1.0;
1446         else {      
1447             if(scalexstr[0])
1448                 p.scalex = (float)(parseNewSize(scalexstr, oldwidth))/oldwidth;
1449         }
1450     }
1451     if(scaleystr[0]) {
1452         if(oldheight==0) p.scaley = 1.0;
1453         else {
1454             if(scaleystr[0])
1455                 p.scaley = (float)(parseNewSize(scaleystr, oldheight))/oldheight;
1456         }
1457     }
1458    
1459     /* rotation */
1460     if(rotatestr[0]) {
1461         if(isRelative(rotatestr)) {
1462             p.rotate += parseFloat(getOffset(rotatestr))*getSign(rotatestr);
1463         } else {
1464             p.rotate = parseFloat(rotatestr);
1465         }
1466     }
1467
1468     /* shearing */
1469     if(shearstr[0]) {
1470         if(isRelative(shearstr)) {
1471             p.shear += parseFloat(getOffset(shearstr))*getSign(shearstr);
1472         } else {
1473             p.shear = parseFloat(shearstr);
1474         }
1475     }
1476
1477     if(pivotstr[0]) {
1478         if(isPoint(pivotstr)) 
1479             p.pivot = parsePoint(pivotstr);
1480         else 
1481             p.pivot = getPoint(oldbbox, pivotstr);
1482     }
1483     if(pinstr[0]) {
1484         if(isPoint(pinstr))
1485             p.pin = parsePoint(pinstr);
1486         else
1487             p.pin = getPoint(oldbbox, pinstr);
1488     }
1489         
1490     /* color transform */
1491
1492     if(rstr[0] || luminancestr[0]) {
1493         MULADD r;
1494         if(rstr[0])
1495             r = parseMulAdd(rstr);
1496         else {
1497             r.add = p.cxform.r0;
1498             r.mul = p.cxform.r1;
1499         }
1500         r = mergeMulAdd(r, luminance);
1501         p.cxform.r0 = r.mul;p.cxform.r1 = r.add;
1502     }
1503     if(gstr[0] || luminancestr[0]) {
1504         MULADD g;
1505         if(gstr[0])
1506             g = parseMulAdd(gstr);
1507         else {
1508             g.add = p.cxform.g0;
1509             g.mul = p.cxform.g1;
1510         }
1511         g = mergeMulAdd(g, luminance);
1512         p.cxform.g0 = g.mul;p.cxform.g1 = g.add;
1513     }
1514     if(bstr[0] || luminancestr[0]) {
1515         MULADD b;
1516         if(bstr[0])
1517             b = parseMulAdd(bstr);
1518         else {
1519             b.add = p.cxform.b0;
1520             b.mul = p.cxform.b1;
1521         }
1522         b = mergeMulAdd(b, luminance);
1523         p.cxform.b0 = b.mul;p.cxform.b1 = b.add;
1524     }
1525     if(astr[0]) {
1526         MULADD a = parseMulAdd(astr);
1527         p.cxform.a0 = a.mul;p.cxform.a1 = a.add;
1528     }
1529
1530     if(type == 0)
1531         s_put(instance, character, p);
1532     if(type == 1)
1533         s_change(instance, p);
1534     if(type == 2)
1535         s_qchange(instance, p);
1536     if(type == 3)
1537         s_jump(instance, p);
1538     if(type == 4)
1539         s_startclip(instance, character, p);
1540     return 0;
1541 }
1542 static int c_put(map_t*args) 
1543 {
1544     c_placement(args, 0);
1545     return 0;
1546 }
1547 static int c_change(map_t*args) 
1548 {
1549     c_placement(args, 1);
1550     return 0;
1551 }
1552 static int c_qchange(map_t*args) 
1553 {
1554     c_placement(args, 2);
1555     return 0;
1556 }
1557 static int c_arcchange(map_t*args) 
1558 {
1559     c_placement(args, 2);
1560     return 0;
1561 }
1562 static int c_jump(map_t*args) 
1563 {
1564     c_placement(args, 3);
1565     return 0;
1566 }
1567 static int c_startclip(map_t*args) 
1568 {
1569     c_placement(args, 4);
1570     return 0;
1571 }
1572 static int c_del(map_t*args) 
1573 {
1574     char*instance = lu(args, "name");
1575     s_delinstance(instance);
1576     return 0;
1577 }
1578 static int c_end(map_t*args) 
1579 {
1580     s_end();
1581     return 0;
1582 }
1583 static int c_sprite(map_t*args) 
1584 {
1585     char* name = lu(args, "name");
1586     s_sprite(name);
1587     return 0;
1588 }
1589 static int c_frame(map_t*args) 
1590 {
1591     char*framestr = lu(args, "n");
1592     int frame;
1593     if(isRelative(framestr)) {
1594         frame = s_getframe();
1595         if(getSign(framestr)<0)
1596             syntaxerror("relative frame expressions must be positive");
1597         frame += parseInt(getOffset(framestr));
1598     }
1599     else {
1600         frame = parseInt(framestr);
1601         if(s_getframe() >= frame
1602                 && !(frame==0 && s_getframe()==frame)) // equality is o.k. for frame 0
1603             syntaxerror("frame expression must be >%d (is:%s)", s_getframe(), framestr);
1604     }
1605     s_frame(frame);
1606     return 0;
1607 }
1608 static int c_primitive(map_t*args) 
1609 {
1610     char*name = lu(args, "name");
1611     char*command = lu(args, "commandname");
1612     int width=0, height=0, r=0;
1613     int linewidth = parseTwip(lu(args, "line"));
1614     char*colorstr = lu(args, "color");
1615     RGBA color = parseColor(colorstr);
1616     char*fillstr = lu(args, "fill");
1617     int dofill = 1;
1618     int type=0;
1619     char* font;
1620     char* text;
1621     RGBA fill;
1622     if(!strcmp(command, "circle"))
1623         type = 1;
1624     else if(!strcmp(command, "textshape"))
1625         type = 2;
1626    
1627     if(type==0) {
1628         width = parseTwip(lu(args, "width"));
1629         height = parseTwip(lu(args, "height"));
1630     } else if (type==1) {
1631         r = parseTwip(lu(args, "r"));
1632     } else if (type==2) {
1633         text = lu(args, "text");
1634         font = lu(args, "font");
1635     }
1636
1637     if(!strcmp(fillstr, "fill"))
1638         fillstr = colorstr;
1639     if(!strcmp(fillstr, "none"))
1640         dofill = 0;
1641     if(width<0 || height<0 || linewidth<0 || r<0)
1642         syntaxerror("values width, height, line, r must be positive");
1643     if(!dofill || isColor(fillstr)) {
1644         if(dofill) 
1645             fill = parseColor(fillstr);
1646     } else {
1647         /* FIXME - texture fill */
1648         fill.r = fill.g = 0;
1649         fill.b = fill.a = 255;
1650         warning("texture fill not supported yet. Filling with black.");
1651     }
1652     if(type == 0) s_box(name, width, height, color, linewidth, fill, dofill);
1653     else if(type==1) s_circle(name, r, color, linewidth, fill, dofill);
1654     else if(type==2) s_textshape(name, font, text, color, linewidth, fill, dofill);
1655     return 0;
1656 }
1657
1658 static int c_shape(map_t*args) 
1659 {
1660     char*name = lu(args, "name");
1661     char*filename = lu(args, "filename");
1662     s_shape(name, filename);
1663     return 0;
1664 }
1665
1666 static int c_font(map_t*args) 
1667 {
1668     char*name = lu(args, "name");
1669     char*filename = lu(args, "filename");
1670     s_font(name, filename);
1671     return 0;
1672 }
1673
1674 static int c_sound(map_t*args) 
1675 {
1676     char*name = lu(args, "name");
1677     char*filename = lu(args, "filename");
1678     s_sound(name, filename);
1679     return 0;
1680 }
1681
1682 static int c_text(map_t*args) 
1683 {
1684     char*name = lu(args, "name");
1685     char*text = lu(args, "text");
1686     char*font = lu(args, "font");
1687     float size = parsePercent(lu(args, "size"));
1688     RGBA color = parseColor(lu(args, "color"));
1689     s_text(name, font, text, (int)(size*100), color);
1690     return 0;
1691 }
1692
1693 static int c_soundtrack(map_t*args) 
1694 {
1695     return 0;
1696 }
1697
1698 int fakechar(map_t*args)
1699 {
1700     char*name = lu(args, "name");
1701     s_box(name, 0, 0, black, 20, black, 0);
1702     return 0;
1703 }
1704
1705 static int c_egon(map_t*args) {return fakechar(args);}
1706 static int c_button(map_t*args) {return fakechar(args);}
1707 static int c_edittext(map_t*args) {return fakechar(args);}
1708
1709 static int c_morphshape(map_t*args) {return fakechar(args);}
1710 static int c_image(map_t*args) {return fakechar(args);}
1711 static int c_movie(map_t*args) {return fakechar(args);}
1712
1713 static int c_buttonsounds(map_t*args) {return 0;}
1714 static int c_buttonput(map_t*args) {return 0;}
1715 static int c_texture(map_t*args) {return 0;}
1716 static int c_action(map_t*args) {return 0;}
1717
1718 static struct {
1719     char*command;
1720     command_func_t* func;
1721     char*arguments;
1722 } arguments[] =
1723 {{"swf", c_swf, "bbox=autocrop version=5 fps=50 name=!default! @compress=default"},
1724  {"frame", c_frame, "n=<plus>1"},
1725
1726     // "import" type stuff
1727  {"shape", c_shape, "name filename"},
1728  {"morphshape", c_morphshape, "name start end"},
1729  {"jpeg", c_image, "name filename quality=80%"},
1730  {"png", c_image, "name filename"},
1731  {"movie", c_movie, "name filename"},
1732  {"sound", c_sound, "name filename"},
1733  {"font", c_font, "name filename"},
1734  {"soundtrack", c_soundtrack, "filename"},
1735
1736     // generators of primitives
1737
1738  {"point", c_point, "name x=0 y=0"},
1739  {"gradient", c_gradient, "name"},
1740
1741     // character generators
1742  {"box", c_primitive, "name width height color=white line=1 @fill=none"},
1743  {"circle", c_primitive, "name r color=white line=1 @fill=none"},
1744  {"textshape", c_primitive, "name text font color=white line=1 @fill=none"},
1745  {"egon", c_egon, "name vertices color=white line=1 @fill=none"},
1746  {"button", c_button, "name shape over=*shape press=*shape area=*shape"},
1747  {"text", c_text, "name text font size=100% color=white"},
1748  {"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"},
1749  
1750  {"buttonsounds", c_buttonsounds, "name press=0 release=0 enter=0 leave=0"},
1751
1752     // control tags
1753  {"play", c_play, "sound loop=0 @nomultiple=0"},
1754  {"stop", c_stop, "sound"},
1755
1756     // object placement tags
1757  {"put", c_put,             "<i> x=0 y=0 red=+0 green=+0 blue=+0 alpha=+0 luminance= scale= scalex= scaley= pivot= pin= shear= rotate= above= below="},
1758  {"startclip", c_startclip, "<i> x=0 y=0 red=+0 green=+0 blue=+0 alpha=+0 luminance= scale= scalex= scaley= pivot= pin= shear= rotate= above= below="},
1759  {"change", c_change,   "name x= y= red= green= blue= alpha= luminance= scale= scalex= scaley= pivot= pin= shear= rotate= above= below="},
1760  {"arcchange", c_arcchange,   "name pivot= angle= red= green= blue= alpha= luminance= scale= scalex= scaley= pivot= pin= shear= rotate= above= below="},
1761  {"qchange", c_qchange, "name x= y= red= green= blue= alpha= luminance= scale= scalex= scaley= pivot= pin= shear= rotate= above= below="},
1762  {"jump", c_jump,       "name x= y= red= green= blue= alpha= luminance= scale= scalex= scaley= pivot= pin= shear= rotate= above= below="},
1763  {"del", c_del, "name"},
1764     // virtual object placement
1765  {"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="},
1766  {"texture", c_texture, "<i> x=0 y=0 scale= scalex=100% scaley=100% shear=0 rotate=0"},
1767
1768     // commands which start a block
1769 //startclip (see above)
1770  {"sprite", c_sprite, "name"},
1771  {"action", c_action, ""},
1772
1773  {"end", c_end, ""}
1774 };
1775
1776
1777 static map_t parseArguments(char*command, char*pattern)
1778 {
1779     char*x;
1780     char*d,*e;
1781    
1782     string_t name[64];
1783     string_t value[64];
1784     int set[64];
1785     int isboolean[64];
1786     int pos;
1787     int len;
1788     int t;
1789     string_t t1,t2;
1790     map_t result;
1791     map_init(&result);
1792
1793     string_set(&t1, "commandname");
1794     string_set(&t2, command);
1795     map_put(&result, t1, t2);
1796
1797     if(!pattern || !*pattern)
1798         return result;
1799
1800     x = pattern;
1801
1802     pos = 0;
1803
1804     if(!strncmp("<i> ", x, 3)) {
1805         readToken();
1806         if(type == COMMAND || type == LABEL) {
1807             pushBack();
1808             syntaxerror("character name expected");
1809         }
1810         name[pos].str = "instance";
1811         name[pos].len = 8;
1812         value[pos].str = text;
1813         value[pos].len = strlen(text);
1814         set[pos] = 1;
1815         pos++;
1816
1817         if(type == ASSIGNMENT)
1818             readToken();
1819
1820         name[pos].str = "character";
1821         name[pos].len = 9;
1822         value[pos].str = text;
1823         value[pos].len = strlen(text);
1824         set[pos] = 1;
1825         pos++;
1826
1827         x+=4;
1828     }
1829
1830     while(*x) {
1831         isboolean[pos] = (x[0] =='@');
1832         if(isboolean[pos])
1833             x++;
1834
1835         d = strchr(x, ' ');
1836         e = strchr(x, '=');
1837         if(!d)
1838             d=&x[strlen(x)];
1839         set[pos] = 0;
1840
1841         if(!e || d<e) { 
1842             // no default
1843             name[pos].str = x;
1844             name[pos].len = d-x;
1845             value[pos].str = 0;
1846             value[pos].len = 0;
1847         } else {
1848             name[pos].str = x;
1849             name[pos].len = e-x;
1850             value[pos].str = e+1;
1851             value[pos].len = d-e-1;
1852         }
1853         pos++;
1854         if(!*d) break;
1855         x=d+1;
1856     }
1857     len = pos;
1858
1859 /*    for(t=0;t<len;t++) {
1860         printf("(%d) %s=%s %s\n", t, strndup(name[t], namelen[t]), strndup(value[t], valuelen[t]),
1861                 isboolean[t]?"(boolean)":"");
1862     }*/
1863
1864     while(1) {
1865         readToken();
1866         if(type == LABEL || type == COMMAND) {
1867             pushBack();
1868             break;
1869         }
1870
1871         // first, search for boolean arguments
1872         for(pos=0;pos<len;pos++)
1873         {
1874             if(!set[pos] && isboolean[pos] && !strncmp(name[pos].str, text, name[pos].len>textlen?name[pos].len:textlen)) {
1875                 set[pos] = 1;
1876                 if(type == ASSIGNMENT)
1877                     readToken();
1878                 value[pos].str = text;
1879                 value[pos].len = strlen(text);
1880                 /*printf("setting boolean parameter %s (to %s)\n",
1881                         strndup(name[pos], namelen[pos]),
1882                         strndup(value[pos], valuelen[pos]));*/
1883                 break;
1884             }
1885         }
1886
1887         // second, search for normal arguments
1888         if(pos==len)
1889         for(pos=0;pos<len;pos++)
1890         {
1891             if((type == ASSIGNMENT && !strncmp(name[pos].str, text, name[pos].len>textlen?name[pos].len:textlen)) ||
1892                (type != ASSIGNMENT && !set[pos])) {
1893                 if(set[pos]) {
1894                     syntaxerror("value %s set twice (old value:%s)", text, strndup(value[pos].str, value[pos].len));
1895                 }
1896                 if(type == ASSIGNMENT)
1897                     readToken();
1898                 set[pos] = 1;
1899                 value[pos].str = text;
1900                 value[pos].len = strlen(text);
1901 #if 0//def DEBUG
1902                 printf("setting parameter %s (to %s)\n",
1903                         strndup(name[pos].str, name[pos].len),
1904                         strndup(value[pos].str, value[pos].len));
1905 #endif
1906                 break;
1907             }
1908         }
1909         if(pos==len) {
1910             syntaxerror("don't know what to do with \"%s\". (All parameters for .%s already set)", text, command);
1911         }
1912     }
1913 #if 0//def DEBUG
1914     for(t=0;t<len;t++) {
1915         printf("%s=%s\n", strndup(name[t].str, name[t].len), strndup(value[t].str, value[t].len));
1916     }
1917 #endif
1918     for(t=0;t<len;t++) {
1919         if(value[t].str && value[t].str[0] == '*') {
1920             //relative default- take value from some other parameter
1921             int s;
1922             for(s=0;s<len;s++) {
1923                 if(value[s].len == value[t].len-1 &&
1924                    !strncmp(&value[t].str[1], value[s].str, value[s].len))
1925                     value[t].str = value[s].str;
1926             }
1927         }
1928         if(value[t].str == 0) {
1929             pushBack();
1930             syntaxerror("value for parameter %s missing (no default)", strndup(name[t].str, name[t].len));
1931         }
1932     }
1933
1934     /* ok, now construct the dictionary from the parameters */
1935
1936     for(t=0;t<len;t++) 
1937     {
1938         map_put(&result, name[t], value[t]);
1939     }
1940     return result;
1941 }
1942 static void parseArgumentsForCommand(char*command)
1943 {
1944     int t;
1945     map_t args;
1946     int nr = -1;
1947     for(t=0;t<sizeof(arguments)/sizeof(arguments[0]);t++) {
1948         if(!strcmp(arguments[t].command, command)) {
1949             args = parseArguments(command, arguments[t].arguments);
1950             nr = t;
1951         }
1952     }
1953     if(nr<0)
1954         syntaxerror("command %s not known", command);
1955
1956 #ifdef DEBUG
1957     printf(".%s\n", command);fflush(stdout);
1958     map_dump(&args, stdout, "\t");fflush(stdout);
1959 #endif
1960
1961     (*arguments[nr].func)(&args);
1962
1963     if(!strcmp(command, "button") ||
1964        !strcmp(command, "action")) {
1965         while(1) {
1966             readToken();
1967             if(type == COMMAND) {
1968                 if(!strcmp(text, "end"))
1969                     break;
1970                 else {
1971                     pushBack();
1972                     break;
1973                 }
1974             }
1975         }
1976     }
1977
1978     map_clear(&args);
1979     return;
1980 }
1981
1982 int main (int argc,char ** argv)
1983
1984     int t;
1985     processargs(argc, argv);
1986     initLog(0,-1,0,0,-1,verbose);
1987
1988     if(!filename) {
1989         args_callback_usage(argv[0]);
1990         exit(1);
1991     }
1992     file = generateTokens(filename);
1993     if(!file) {
1994         printf("parser returned error.\n");
1995         return 1;
1996     }
1997     pos=0;
1998
1999     t=0;
2000
2001     while(!noMoreTokens()) {
2002         readToken();
2003         if(type != COMMAND)
2004             syntaxerror("command expected");
2005         parseArgumentsForCommand(text);
2006     }
2007
2008     s_close();
2009     freeTokens(file);
2010
2011     return 0;
2012 }
2013