fixed preloader/viewer code
[swftools.git] / src / swfbbox.c
1 /* swfbbox.c
2    Tool for playing around with SWF bounding boxes.
3
4    Part of the swftools package.
5    
6    Copyright (c) 2003 Matthias Kramm <kramm@quiss.org>
7  
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
21
22 #include "../config.h"
23 #include <stdio.h>
24 #include <stdarg.h>
25 #include <assert.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include "../lib/rfxswf.h"
29 #include "../lib/args.h"
30 #include "../lib/log.h"
31
32 static char * filename = 0;
33 static char * outfilename = "output.swf";
34 static int optimize = 0;
35 static int swifty = 0;
36 static int verbose = 0;
37 static int showbbox = 0;
38 static int showorigbbox = 1;
39 static int expand = 0;
40 static int clip = 0;
41
42 static struct options_t options[] = {
43 {"h", "help"},
44 {"b", "bbox"},
45 {"B", "newbbox"},
46 {"e", "expand"},
47 {"O", "optimize"},
48 {"S", "swifty"},
49 {"c", "clip"},
50 {"o", "output"},
51 {"v", "verbose"},
52 {"V", "version"},
53 {0,0}
54 };
55
56 int args_callback_option(char*name,char*val)
57 {
58     if(!strcmp(name, "V")) {
59         printf("swfbbox - part of %s %s\n", PACKAGE, VERSION);
60         exit(0);
61     } 
62     else if(!strcmp(name, "b")) {
63         showorigbbox = 2;
64         if(showbbox == 1) showbbox = 0;
65         return 0;
66     } 
67     else if(!strcmp(name, "B")) {
68         showbbox = 2;
69         return 0;
70     } 
71     else if(!strcmp(name, "O")) {
72         optimize = 1;
73         if(showorigbbox == 1) showorigbbox = 0;
74         return 0;
75     } 
76     else if(!strcmp(name, "S")) {
77         swifty = 1;
78         if(showorigbbox == 1) showorigbbox = 0;
79         return 0;
80     } 
81     else if(!strcmp(name, "c")) {
82         if(showorigbbox == 1) showorigbbox = 0;
83         optimize = 1;
84         clip = 1;
85         return 0;
86     } 
87     else if(!strcmp(name, "v")) {
88         verbose ++;
89         return 0;
90     } 
91     else if(!strcmp(name, "q")) {
92         if(verbose)
93             verbose --;
94         return 0;
95     } 
96     else if(!strcmp(name, "e")) {
97         expand = 1;
98         return 0;
99     } 
100     else if(!strcmp(name, "o")) {
101         outfilename = val;
102         return 1;
103     } 
104     else {
105         printf("Unknown option: -%s\n", name);
106         exit(1);
107     }
108
109     return 0;
110 }
111 int args_callback_longoption(char*name,char*val)
112 {
113     return args_long2shortoption(options, name, val);
114 }
115 void args_callback_usage(char *name)
116 {
117     printf("\n");
118     printf("Usage: %s [-OS] file.swf\n", name);
119     printf("\n");
120     printf("-h , --help                    Print help and exit\n");
121     printf("-b , --bbox                    Show movie bounding box (default)\n");
122     printf("-B , --newbbox                 Show recalculated (optimized/expanded) bounding box\n");
123     printf("-e , --expand                  Write out a new file using the recalculated bounding box\n");
124     printf("-O , --optimize                Recalculate bounding boxes\n");
125     printf("-S , --swifty                  Print out transformed bounding boxes\n");
126     printf("-c , --clip                    Clip bounding boxes to movie size\n");
127     printf("-o , --output <filename>       Set output filename to <filename> (for -O)\n");
128     printf("-v , --verbose                 Be more verbose\n");
129     printf("-V , --version                 Print program version and exit\n");
130     printf("\n");
131 }
132 int args_callback_command(char*name,char*val)
133 {
134     if(filename) {
135         fprintf(stderr, "Only one file allowed. You supplied at least two. (%s and %s)\n",
136                  filename, name);
137     }
138     filename = name;
139     return 0;
140 }
141
142 #define swf_ResetReadBits(tag)   if (tag->readBit)  { tag->pos++; tag->readBit = 0; }
143
144 void swf_Shape2Optimize(SHAPE2*shape)
145 {
146     if(!shape->bbox)
147         shape->bbox = malloc(sizeof(SRECT));
148     *(shape->bbox) = swf_GetShapeBoundingBox(shape);
149 }
150
151 /*
152    {char {x1 y1 x2 y2 x3 y3 x4 y4]]
153 */
154
155 SRECT bboxes[65536];
156 U16 depth2id[65536];
157 char*depth2name[65536];
158
159 int hasid(TAG*tag)
160 {
161     if(tag->id == ST_PLACEOBJECT)
162         return 1;
163     if(tag->id == ST_PLACEOBJECT2 && (tag->data[0] & 2))
164         return 1;
165     return 0;
166 }
167
168 int hasname(TAG*tag)
169 {
170     if(tag->id == ST_PLACEOBJECT)
171         return 0;
172     if(tag->id == ST_PLACEOBJECT2 && (tag->data[0] & 0x20))
173         return 1;
174     return 0;
175 }
176
177 char* getname(TAG*tag)
178 {
179     if(tag->id == ST_PLACEOBJECT)
180         return 0;
181     if(tag->id == ST_PLACEOBJECT2 && (tag->data[0] & 0x20)) {
182         SWFPLACEOBJECT o;
183         tag->pos = 0;tag->readBit = 0;
184         swf_GetPlaceObject(tag, &o);
185         return o.name;
186     }
187     return 0;
188 }
189
190 MATRIX getmatrix(TAG*tag)
191 {
192     SWFPLACEOBJECT o;
193     tag->pos = 0;tag->readBit = 0;
194     swf_GetPlaceObject(tag, &o);
195     return o.matrix;
196 }
197
198
199 static int fontnum = -1;
200 static SWFFONT**fonts;
201 static SWF*c_swf;
202 static void fontcallback1(void*self, U16 id,U8 * name)
203 { fontnum++;
204 }
205 static void fontcallback2(void*self, U16 id,U8 * name)
206
207     fonts[fontnum] = 0;
208     swf_FontExtract(c_swf,id,&fonts[fontnum]);
209     if(verbose) {
210         if(fonts[fontnum]) printf("Extracting font %d (%s)\n", id, name);
211         else               printf("Extracting font %d (%s) failed\n", id, name);
212         fflush(stdout);
213     }
214     fontnum++;
215 }
216 typedef struct _textbounds
217 {
218     SRECT r;
219     MATRIX m; // character transform matrix
220 } textbounds_t;
221
222 typedef struct _placement
223 {
224     SWFPLACEOBJECT* po;
225     int num;
226 } placement_t;
227
228 static placement_t* placements;
229
230 static placement_t* readPlacements(SWF*swf)
231 {
232     placement_t* p = (placement_t*)rfx_calloc(sizeof(placement_t)*65536);
233     TAG*tag = swf->firstTag;
234     while(tag) {
235         if(tag->id == ST_PLACEOBJECT || tag->id == ST_PLACEOBJECT2) {
236             SWFPLACEOBJECT*po = rfx_alloc(sizeof(SWFPLACEOBJECT));
237             int id;
238             swf_GetPlaceObject(tag, po);
239             id = po->id;
240             if(po->move) {
241                 fprintf(stderr, "MOVE tags not supported with -c");
242             }
243             p[id].po = po;
244             p[id].num++;
245         }
246         tag = tag->next;
247     }
248
249     return p;
250 }
251
252 static void freePlacements(placement_t*p)
253 {
254     int t;
255     for(t=0;t<65536;t++) {
256         if(p[t].po) {
257             swf_PlaceObjectFree(p[t].po); p[t].po = 0;
258         }
259     }
260     rfx_free(p);
261 }
262
263 SRECT swf_ClipRect(SRECT border, SRECT r)
264 {
265     if(r.xmax > border.xmax) r.xmax = border.xmax;
266     if(r.ymax > border.ymax) r.ymax = border.ymax;
267     if(r.xmax < border.xmin) r.xmax = border.xmin;
268     if(r.ymax < border.ymin) r.ymax = border.ymin;
269     
270     if(r.xmin > border.xmax) r.xmin = border.xmax;
271     if(r.ymin > border.ymax) r.ymin = border.ymax;
272     if(r.xmin < border.xmin) r.xmin = border.xmin;
273     if(r.ymin < border.ymin) r.ymin = border.ymin;
274     return r;
275 }
276
277 static SRECT clipBBox(TAG*tag, SRECT mbbox, SRECT r)
278 {
279     int id = swf_GetDefineID(tag);
280     MATRIX m;
281     if(!placements[id].po) {
282         if(verbose)
283             printf("Id %d is never set\n", id);
284         return r;
285     }
286     if(placements[id].num>1) {
287         if(verbose)
288             printf("Id %d is set more than once\n", id);
289         return r;
290     }
291     m = placements[id].po->matrix;
292     if(m.r0 || m.r1)  {
293         fprintf(stderr, "Rotating PLACEOBJECTS are not supported with -c\n");
294         return r;
295     }
296
297     if(verbose) {
298         printf("ID %d\n", id);
299         swf_DumpMatrix(stdout, &m);
300     }
301     mbbox.xmin -= m.tx;
302     mbbox.ymin -= m.ty;
303     mbbox.xmax -= m.tx;
304     mbbox.ymax -= m.ty;
305     mbbox.xmin *= 65536.0/m.sx;
306     mbbox.xmax *= 65536.0/m.sx;
307     mbbox.ymin *= 65536.0/m.sy;
308     mbbox.ymax *= 65536.0/m.sy;
309    
310     if(verbose) {
311         printf("border: %f/%f/%f/%f - rect: %f/%f/%f/%f\n",
312                 mbbox.xmin /20.0,
313                 mbbox.ymin /20.0,
314                 mbbox.xmax /20.0,
315                 mbbox.ymax /20.0,
316                 r.xmin /20.0,
317                 r.ymin /20.0,
318                 r.xmax /20.0,
319                 r.ymax /20.0);
320     }
321     
322
323     r = swf_ClipRect(mbbox, r);
324    
325     if(verbose) {
326         printf("new rect: %f/%f/%f/%f\n",
327                 r.xmin /20.0,
328                 r.ymin /20.0,
329                 r.xmax /20.0,
330                 r.ymax /20.0);
331     }
332
333     return r;
334 }
335
336
337 static void textcallback(void*self, int*chars, int*xpos, int nr, int fontid, int fontsize, 
338                     int xstart, int ystart, RGBA* color)
339 {
340     textbounds_t * bounds = (textbounds_t*)self;
341     SWFFONT*font = 0;
342     int t;
343     for(t=0;t<fontnum;t++) {
344         if(fonts[t]->id == fontid) {
345             font = fonts[t];
346             break;
347         }
348     }
349     if(!font) {
350         fprintf(stderr, "Font %d unknown\n", fontid);
351         exit(1);
352     }
353     if(!font->layout) {
354         /* This is an expensive operation- but what should we do, we
355            need the glyph's bounding boxes */
356         swf_FontCreateLayout(font);
357     }
358
359     if(verbose)
360         printf("%d chars, font %d, size %d, at (%d,%d)\n", nr, fontid, fontsize, xstart, ystart);
361
362     for(t=0;t<nr;t++) {
363         /* not tested yet- the matrix/fontsize calculation is probably all wrong */
364         int x = xstart + xpos[t];
365         int y = ystart;
366         int ch = 0;
367         SRECT newglyphbbox, glyphbbox = font->layout->bounds[chars[t]];
368         MATRIX m = bounds->m;
369         SPOINT p;
370
371         if(chars[t] < font->numchars && font->glyph2ascii) {
372             ch = font->glyph2ascii[chars[t]];
373         }
374
375         p.x = x; p.y = y;
376         p = swf_TurnPoint(p, &m);
377
378         m.sx = (m.sx * fontsize) / 1024;
379         m.sy = (m.sy * fontsize) / 1024;
380         m.r0 = (m.r0 * fontsize) / 1024;
381         m.r1 = (m.r1 * fontsize) / 1024;
382
383         m.tx += p.x;
384         m.ty += p.y;
385         newglyphbbox = swf_TurnRect(glyphbbox, &m);
386
387         if(ch<32) ch='?';
388             
389         swf_ExpandRect2(&(bounds->r), &newglyphbbox);
390         if(verbose >= 2) {
391             printf("%5d %c, %d %d %d %d (%d %d %d %d) -> %d %d %d %d\n", 
392                 xpos[t], ch, 
393                 glyphbbox.xmin, glyphbbox.ymin, glyphbbox.xmax, glyphbbox.ymax,
394                 newglyphbbox.xmin, newglyphbbox.ymin, newglyphbbox.xmax, newglyphbbox.ymax,
395                 bounds->r.xmin, bounds->r.ymin, bounds->r.xmax, bounds->r.ymax);
396         }
397
398     }
399 }
400
401 static void swf_OptimizeBoundingBoxes(SWF*swf)
402 {
403     TAG* tag = swf->firstTag;
404     
405     while (tag) {
406         if (tag->id == ST_DEFINESHAPE ||
407             tag->id == ST_DEFINESHAPE2 ||
408             tag->id == ST_DEFINESHAPE3) {
409             SHAPE2 s;
410             if(verbose) printf("%s\n", swf_TagGetName(tag));
411             swf_ParseDefineShape(tag, &s);
412             swf_Shape2Optimize(&s);
413             tag->len = 2;
414             tag->pos = 0;
415             if(!s.bbox) {
416                 fprintf(stderr, "Internal error (5)\n");
417                 exit(1);
418             }
419             if(clip) {
420                 *s.bbox = clipBBox(tag, swf->movieSize, *s.bbox);
421             }
422             swf_SetShape2(tag, &s);
423         }
424         if (tag->id == ST_DEFINETEXT || tag->id == ST_DEFINETEXT2) {
425             SRECT oldbox;
426             int matrix_offset;
427             int len;
428             U8*data;
429             textbounds_t bounds;
430             if(verbose) printf("%s\n", swf_TagGetName(tag));
431             if(fontnum < 0) {
432                 if(verbose) printf("Extracting fonts...\n");
433                 c_swf = swf;
434                 fontnum = 0;
435                 swf_FontEnumerate(swf,&fontcallback1,0);
436                 fonts = (SWFFONT**)malloc(fontnum*sizeof(SWFFONT*));
437                 memset(fonts, 0, fontnum*sizeof(SWFFONT*));
438                 fontnum = 0;
439                 swf_FontEnumerate(swf,&fontcallback2,0);
440             }
441
442             memset(&bounds, 0, sizeof(bounds));
443
444             swf_SetTagPos(tag, 0);
445             swf_GetU16(tag);
446             swf_GetRect(tag,&oldbox);
447             swf_ResetReadBits(tag);
448             matrix_offset = tag->pos;
449             swf_GetMatrix(tag,&bounds.m);
450             swf_ParseDefineText(tag, textcallback, &bounds);
451             if(verbose) {
452                 printf("\n");
453                 swf_DumpMatrix(stdout, &bounds.m);
454                 printf("old: %d %d %d %d\n", oldbox.xmin, oldbox.ymin, oldbox.xmax, oldbox.ymax);
455                 printf("new: %d %d %d %d\n", bounds.r.xmin, bounds.r.ymin, bounds.r.xmax, bounds.r.ymax);
456             }
457             if(clip) {
458                 bounds.r = clipBBox(tag, swf->movieSize, bounds.r);
459             }
460             
461             /* now comes the tricky part: 
462                we have to fiddle the data back in 
463                thank heavens that the bbox is follow by a matrix
464                struct, which always starts on a byte boundary.
465              */
466             len = tag->len - matrix_offset;
467             data = malloc(len);
468             memcpy(data, &tag->data[matrix_offset], len);
469             tag->writeBit = 0;
470             tag->len = 2;
471             swf_SetRect(tag, &bounds.r);
472             swf_SetBlock(tag, data, len);
473             free(data);
474             tag->pos = tag->readBit = 0;
475         }
476         tag = tag->next;
477     }
478 }
479
480 static void showSwiftyOutput(SWF*swf) 
481 {
482     TAG*tag = swf->firstTag;
483     int frame=0;
484     printf("{\n\t{frame %d}\n", frame++);
485
486     while (tag) {
487         if (tag->id == ST_SHOWFRAME) {
488             printf("}\n{\n\t{frame %d}\n", frame++);
489         }
490         if (tag->id == ST_PLACEOBJECT || tag->id == ST_PLACEOBJECT2) {
491             if(hasid(tag)) {
492                 depth2id[swf_GetDepth(tag)] = swf_GetPlaceID(tag);
493             }
494             if(hasname(tag)) {
495                 depth2name[swf_GetDepth(tag)] = getname(tag);
496             }
497         }
498         if (tag->id == ST_PLACEOBJECT || tag->id == ST_PLACEOBJECT2) {
499             MATRIX m = getmatrix(tag);
500             U16 id = depth2id[swf_GetDepth(tag)];
501             char*name = depth2name[swf_GetDepth(tag)];
502             char buf[40];
503             SRECT bbox = bboxes[id];
504             SPOINT p1,p2,p3,p4;
505             p1.x = bbox.xmin; p1.y = bbox.ymin;
506             p2.x = bbox.xmax; p2.y = bbox.ymin;
507             p3.x = bbox.xmin; p3.y = bbox.ymax;
508             p4.x = bbox.xmax; p4.y = bbox.ymax;
509             p1 = swf_TurnPoint(p1, &m);
510             p2 = swf_TurnPoint(p2, &m);
511             p3 = swf_TurnPoint(p3, &m);
512             p4 = swf_TurnPoint(p4, &m);
513             if(!name) {
514                 sprintf(buf, "ID%d", id);name = buf;
515             }
516             //printf("\t#%.4f %.4f %.4f %.4f | %.4f %.4f\n", m.sx/65536.0, m.r1/65536.0, m.r0/65536.0, m.sy/65536.0, m.tx/20.0, m.ty/20.0);
517             printf("\t{%s {%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f}}\n", name, 
518                     p1.x/20.0, p1.y/20.0, p2.x/20.0, p2.y/20.0,
519                     p3.x/20.0, p3.y/20.0, p4.x/20.0, p4.y/20.0);
520         }
521         tag = tag->next;
522     }
523     printf("}\n");
524 }
525 static SRECT getMovieClipBBox(TAG*tag) 
526 {
527     //TAG*tag = swf->firstTag;
528     int frame=0;
529     SRECT movieSize;
530     U16 depth2id[65536];
531     memset(depth2id, 0, sizeof(depth2id));
532
533     memset(&movieSize,0,sizeof(SRECT));
534
535     while (tag->id != ST_END) {
536         if (tag->id == ST_PLACEOBJECT || tag->id == ST_PLACEOBJECT2) {
537             if(hasid(tag)) {
538                 depth2id[swf_GetDepth(tag)] = swf_GetPlaceID(tag);
539             }
540         }
541         if (tag->id == ST_PLACEOBJECT || tag->id == ST_PLACEOBJECT2) {
542             MATRIX m = getmatrix(tag);
543             U16 id = depth2id[swf_GetDepth(tag)];
544             SRECT bbox = bboxes[id];
545             
546             SRECT tbbox = swf_TurnRect(bbox, &m);
547             swf_ExpandRect2(&movieSize, &tbbox);
548         }
549         tag = tag->next;
550     }
551     return movieSize;
552 }
553
554 static SRECT getSWFBBox(SWF*swf)
555 {
556     SRECT movieSize = getMovieClipBBox(swf->firstTag);
557     
558     return movieSize;
559 }
560
561 int main (int argc,char ** argv)
562
563     TAG*tag;
564     SWF swf;
565     int fi;
566     SRECT oldMovieSize;
567     SRECT newMovieSize;
568     memset(bboxes, 0, sizeof(bboxes));
569     memset(depth2name, 0, sizeof(depth2name));
570
571     processargs(argc, argv);
572     initLog(0,0,0,0,0,verbose?LOGLEVEL_DEBUG:LOGLEVEL_WARNING);
573
574     if(!filename) {
575         fprintf(stderr, "You must supply a filename.\n");
576         return 1;
577     }
578
579     fi = open(filename,O_RDONLY|O_BINARY);
580
581     if (fi<0)
582     { 
583         perror("Couldn't open file: ");
584         exit(1);
585     }
586     if FAILED(swf_ReadSWF(fi,&swf))
587     { 
588         fprintf(stderr, "%s is not a valid SWF file or contains errors.\n",filename);
589         close(fi);
590         exit(1);
591     }
592     close(fi);
593
594     swf_OptimizeTagOrder(&swf);
595
596     if(clip) {
597         placements = readPlacements(&swf);
598     }
599
600     swf_FoldAll(&swf);
601
602     /* Optimize bounding boxes in case -O flag was set */
603     if(optimize) {
604         swf_OptimizeBoundingBoxes(&swf);
605     }
606     
607     /* Create an ID to Bounding Box table */
608     tag = swf.firstTag;
609     while (tag) {
610         if(swf_isDefiningTag(tag)) {
611             int id = swf_GetDefineID(tag);
612             if(tag->id != ST_DEFINESPRITE) {
613                 bboxes[id] = swf_GetDefineBBox(tag);
614             } else {
615                 swf_UnFoldSprite(tag);
616                 bboxes[id] = getMovieClipBBox(tag);
617                 swf_FoldSprite(tag);
618                 if(verbose) {
619                     printf("sprite %d is %.2fx%.2f\n", id, 
620                             (bboxes[id].xmax - bboxes[id].xmin)/20.0,
621                             (bboxes[id].ymax - bboxes[id].ymin)/20.0);
622                 }
623             }
624         }
625         tag = tag->next;
626     }
627     
628     /* Create an ID->Bounding Box table for all bounding boxes */
629     if(swifty) {
630         showSwiftyOutput(&swf);
631     }
632
633     oldMovieSize = swf.movieSize;
634     newMovieSize = getSWFBBox(&swf);
635
636     if(optimize || expand) {
637
638         if(expand)
639             swf.movieSize = newMovieSize;
640
641         fi = open(outfilename, O_BINARY | O_RDWR | O_CREAT | O_TRUNC, 0666);
642         if(swf_WriteSWF(fi, &swf) < 0) {
643             fprintf(stderr, "Error writing file %s", outfilename);
644             close(fi);
645             exit(1);
646         }
647         close(fi);
648     }
649     
650     if(showbbox) {
651         if(verbose>=0)
652             printf("Real Movie Size: ");
653         printf("%.2f x %.2f :%.2f :%.2f\n", 
654                 (newMovieSize.xmax-newMovieSize.xmin)/20.0,
655                 (newMovieSize.ymax-newMovieSize.ymin)/20.0,
656                 (newMovieSize.xmin)/20.0,
657                 (newMovieSize.ymin)/20.0
658                 );
659     }
660     if(showorigbbox) {
661         if(verbose>=0)
662             printf("Original Movie Size: ");
663         printf("%.2f x %.2f :%.2f :%.2f\n", 
664                 (oldMovieSize.xmax-oldMovieSize.xmin)/20.0,
665                 (oldMovieSize.ymax-oldMovieSize.ymin)/20.0,
666                 (oldMovieSize.xmin)/20.0,
667                 (oldMovieSize.ymin)/20.0
668                 );
669     }
670
671     swf_FreeTags(&swf);
672
673     if(placements) {
674         freePlacements(placements);
675     }
676     return 0;
677 }