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