added access levels to flags
[swftools.git] / lib / modules / swfrender.c
1 /* swfrender.c
2
3    functions for rendering swf content
4       
5    Extension module for the rfxswf library.
6    Part of the swftools package.
7
8    Copyright (c) 2004 Mederra Oy <http://www.mederra.fi>
9    Copyright (c) 2004 Matthias Kramm
10  
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 2 of the License, or
14    (at your option) any later version.
15
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20
21    You should have received a copy of the GNU General Public License
22    along with this program; if not, write to the Free Software
23    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
24
25 #include <assert.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include "../rfxswf.h"
29
30 /* one bit flag: */
31 #define clip_type 0
32 #define fill_type 1
33
34 typedef struct _renderpoint
35 {
36     float x;
37     U32 depth;
38
39     SHAPELINE*shapeline;
40     SHAPE2*s;
41     
42 } renderpoint_t;
43
44 /* 
45     enum {clip_type, solidfill_type, texturefill_type, gradientfill_type} type;
46     float fx;
47     int x;
48     U32 depth;
49     U32 clipdepth;
50
51     // solidfill;
52     RGBA color; 
53     
54     // texturefill
55     bitmap_t* bitmap;
56
57     // gradientfill
58     gradient_t* gradient;
59
60     // texture- & gradientfill;
61     U32 x,y;
62     U32 dx,dy;
63
64 */
65
66 typedef struct _renderline
67 {
68     TAG*points; //incremented in 128 byte steps
69     int num;
70     U32 pending_clipdepth;
71 } renderline_t;
72
73 typedef struct _bitmap {
74     int width;
75     int height;
76     RGBA*data;
77     int id;
78     struct _bitmap*next;
79 } bitmap_t;
80
81 typedef struct _renderbuf_internal
82 {
83     renderline_t*lines;
84     bitmap_t*bitmaps;
85     int antialize;
86     int multiply;
87     int width2,height2;
88     int shapes;
89     int ymin, ymax;
90     
91     RGBA* img;
92     int* zbuf; 
93 } renderbuf_internal;
94
95 #define DEBUG 0
96
97 static inline void add_pixel(RENDERBUF*dest, float x, int y, renderpoint_t*p)
98 {
99     renderbuf_internal*i = (renderbuf_internal*)dest->internal;
100     if(x >= i->width2 || y >= i->height2 || y<0) return;
101     p->x = x;
102     if(y<i->ymin) i->ymin = y;
103     if(y>i->ymax) i->ymax = y;
104
105     i->lines[y].num++;
106     swf_SetBlock(i->lines[y].points, (U8*)p, sizeof(renderpoint_t));
107 }
108
109 /* set this to 0.777777 or something if the "both fillstyles set while not inside shape"
110    problem appears to often */
111 #define CUT 0.77887789
112
113 #define INT(x) ((int)((x)+16)-16)
114
115 static void add_line(RENDERBUF*buf, double x1, double y1, double x2, double y2, renderpoint_t*p)
116 {
117     renderbuf_internal*i = (renderbuf_internal*)buf->internal;
118     double diffx, diffy;
119     double ny1, ny2, stepx;
120 /*    if(DEBUG&4) {
121         int l = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
122         printf(" l[%d - %.2f/%.2f -> %.2f/%.2f]\n", l, x1/20.0, y1/20.0, x2/20.0, y2/20.0);
123     }*/
124     assert(p->shapeline);
125
126     y1=y1*i->multiply;
127     y2=y2*i->multiply;
128     x1=x1*i->multiply;
129     x2=x2*i->multiply;
130     
131     y1 = y1/20.0;
132     y2 = y2/20.0;
133     x1 = x1/20.0;
134     x2 = x2/20.0;
135
136     if(y2 < y1) {
137         double x;
138         double y;
139         x = x1;x1 = x2;x2=x;
140         y = y1;y1 = y2;y2=y;
141     }
142     
143     diffx = x2 - x1;
144     diffy = y2 - y1;
145     
146     ny1 = INT(y1)+CUT;
147     ny2 = INT(y2)+CUT;
148
149     if(ny1 < y1) {
150         ny1 = INT(y1) + 1.0 + CUT;
151     }
152     if(ny2 >= y2) {
153         ny2 = INT(y2) - 1.0 + CUT;
154     }
155
156     if(ny1 > ny2)
157         return;
158
159     stepx = diffx/diffy;
160     x1 = x1 + (ny1-y1)*stepx;
161     x2 = x2 + (ny2-y2)*stepx;
162
163     {
164         int posy=INT(ny1);
165         int endy=INT(ny2);
166         double posx=0;
167         double startx = x1;
168
169         while(posy<=endy) {
170             float xx = (float)(startx + posx);
171             add_pixel(buf, xx ,posy, p);
172             posx+=stepx;
173             posy++;
174         }
175     }
176 }
177 #define PI 3.14159265358979
178 static void add_solidline(RENDERBUF*buf, double x1, double y1, double x2, double y2, double width, renderpoint_t*p)
179 {
180     renderbuf_internal*i = (renderbuf_internal*)buf->internal;
181
182     double dx = x2-x1;
183     double dy = y2-y1;
184     double sd;
185     double d;
186
187     int t;
188     int segments;
189     double lastx,lasty;
190     double vx,vy;
191     double xx,yy;
192   
193     /* Make sure the line is always at least one pixel wide */
194 #ifdef LINEMODE1
195     /* That's what Macromedia's Player does at least at zoom level >= 1.  */
196     width += 20;
197 #else
198     /* That's what Macromedia's Player seems to do at zoom level 0.  */
199     /* TODO: needs testing */
200
201     /* TODO: how does this interact with scaling? */
202     if(width * i->multiply < 20)
203         width = 20 / i->multiply;
204 #endif
205
206     sd = (double)dx*(double)dx+(double)dy*(double)dy;
207     d = sqrt(sd);
208
209     if(!dx && !dy) {
210         vx = 1;
211         vy = 0;
212     } else {
213         vx = ( dy/d);
214         vy = (-dx/d);
215     }
216
217     segments = (int)(width/2);
218     if(segments < 2)
219         segments = 2;
220
221     segments = 8;
222
223     vx=vx*width*0.5;
224     vy=vy*width*0.5;
225
226     xx = x2+vx;
227     yy = y2+vy;
228     add_line(buf, x1+vx, y1+vy, xx, yy, p);
229     lastx = xx;
230     lasty = yy;
231     for(t=1;t<segments;t++) {
232         double s = sin(t*PI/segments);
233         double c = cos(t*PI/segments);
234         xx = (x2 + vx*c - vy*s);
235         yy = (y2 + vx*s + vy*c);
236         add_line(buf, lastx, lasty, xx, yy, p);
237         lastx = xx;
238         lasty = yy;
239     }
240     
241     xx = (x2-vx);
242     yy = (y2-vy);
243     add_line(buf, lastx, lasty, xx, yy, p);
244     lastx = xx;
245     lasty = yy;
246     xx = (x1-vx);
247     yy = (y1-vy);
248     add_line(buf, lastx, lasty, xx, yy, p);
249     lastx = xx;
250     lasty = yy;
251     for(t=1;t<segments;t++) {
252         double s = sin(t*PI/segments);
253         double c = cos(t*PI/segments);
254         xx = (x1 - vx*c + vy*s);
255         yy = (y1 - vx*s - vy*c);
256         add_line(buf, lastx, lasty, xx, yy, p);
257         lastx = xx;
258         lasty = yy;
259     }
260     add_line(buf, lastx, lasty, (x1+vx), (y1+vy), p);
261 }
262
263 static inline void transform_point(MATRIX*m, int x, int y, int*dx, int*dy)
264 {
265     SPOINT p,d;
266     p.x = x;
267     p.y = y;
268     d = swf_TurnPoint(p, m);
269     *dx = d.x;
270     *dy = d.y;
271 }
272
273 static int compare_renderpoints(const void * _a, const void * _b)
274 {
275     renderpoint_t*a = (renderpoint_t*)_a;
276     renderpoint_t*b = (renderpoint_t*)_b;
277     if(a->x < b->x) return -1;
278     if(a->x > b->x) return 1;
279     return 0;
280 }
281
282 void swf_Render_Init(RENDERBUF*buf, int posx, int posy, int width, int height, int antialize, int multiply)
283 {
284     renderbuf_internal*i;
285     int y;
286     memset(buf, 0, sizeof(RENDERBUF));
287     buf->width = width*multiply;
288     buf->height = height*multiply;
289     buf->posx = posx;
290     buf->posy = posy;
291     buf->internal = (renderbuf_internal*)rfx_calloc(sizeof(renderbuf_internal));
292     i = (renderbuf_internal*)buf->internal;
293     if(antialize < 1)
294         antialize = 1;
295     i->antialize = antialize;
296     i->multiply = multiply*antialize;
297     i->height2 = antialize*buf->height;
298     i->width2 = antialize*buf->width;
299     i->lines = (renderline_t*)rfx_alloc(i->height2*sizeof(renderline_t));
300     for(y=0;y<i->height2;y++) {
301         memset(&i->lines[y], 0, sizeof(renderline_t));
302         i->lines[y].points = swf_InsertTag(0, 0);
303         i->lines[y].num = 0;
304     }
305     i->zbuf = (int*)rfx_calloc(sizeof(int)*i->width2*i->height2);
306     i->img = (RGBA*)rfx_calloc(sizeof(RGBA)*i->width2*i->height2);
307     i->shapes = 0;
308     i->ymin = 0x7fffffff;
309     i->ymax = -0x80000000;
310 }
311 void swf_Render_SetBackground(RENDERBUF*buf, RGBA*img, int width, int height)
312 {
313     renderbuf_internal*i = (renderbuf_internal*)buf->internal;
314     int x,xx,y,yy;
315     int xstep=width*65536/i->width2;
316     int ystep=height*65536/i->height2;
317     if(i->shapes) {
318         fprintf(stderr, "rfxswf: Warning: swf_Render_SetBackground() called after drawing shapes\n");
319     }
320     for(y=0,yy=0;y<i->height2;y++,yy+=ystep) {
321         RGBA*src = &img[(yy>>16) * width];
322         RGBA*line = &i->img[y * i->width2];
323         for(x=0,xx=0;x<i->width2;x++,xx+=xstep) {
324             line[x] = src[xx>>16];
325         }
326     }
327 }
328 void swf_Render_SetBackgroundColor(RENDERBUF*buf, RGBA color)
329 {
330     swf_Render_SetBackground(buf, &color, 1, 1);
331 }
332 void swf_Render_AddImage(RENDERBUF*buf, U16 id, RGBA*img, int width, int height)
333 {
334     renderbuf_internal*i = (renderbuf_internal*)buf->internal;
335
336     bitmap_t*bm = (bitmap_t*)rfx_calloc(sizeof(bitmap_t));
337     bm->id = id;
338     bm->width = width;
339     bm->height = height;
340     bm->data = (RGBA*)rfx_alloc(width*height*4);
341     memcpy(bm->data, img, width*height*4);
342
343     bm->next = i->bitmaps;
344     i->bitmaps = bm;
345 }
346 void swf_Render_ClearCanvas(RENDERBUF*dest)
347 {
348     renderbuf_internal*i = (renderbuf_internal*)dest->internal;
349     int y;
350     for(y=0;y<i->height2;y++) {
351         swf_ClearTag(i->lines[y].points);
352     }
353     memset(i->zbuf, 0, sizeof(int)*i->width2*i->height2);
354     memset(i->img, 0, sizeof(RGBA)*i->width2*i->height2);
355 }
356 void swf_Render_Delete(RENDERBUF*dest)
357 {
358     renderbuf_internal*i = (renderbuf_internal*)dest->internal;
359     int y;
360     bitmap_t*b = i->bitmaps;
361
362     /* delete canvas */
363     rfx_free(i->zbuf);
364     rfx_free(i->img);
365
366     /* delete line buffers */
367     for(y=0;y<i->height2;y++) {
368         swf_DeleteTag(0, i->lines[y].points);
369         i->lines[y].points = 0;
370     }
371
372     /* delete bitmaps */
373     while(b) {
374         bitmap_t*next = b->next;
375         free(b->data);b->data=0;
376         rfx_free(b);
377         b = next;
378     }
379
380     rfx_free(i->lines); i->lines = 0;
381     rfx_free(dest->internal); dest->internal = 0;
382 }
383
384 static SHAPE2* linestyle2fillstyle(SHAPE2*shape)
385 {
386     SHAPE2*s = (SHAPE2*)rfx_calloc(sizeof(SHAPE2));
387     int t;
388     s->numfillstyles = shape->numlinestyles;
389     s->fillstyles = (FILLSTYLE*)rfx_calloc(sizeof(FILLSTYLE)*shape->numlinestyles);
390     s->lines = (SHAPELINE*)rfx_calloc(sizeof(SHAPELINE)*shape->numlinestyles);
391     for(t=0;t<shape->numlinestyles;t++) {
392         s->lines[t].fillstyle0 = t+1;
393         s->fillstyles[t].type = FILL_SOLID;
394         s->fillstyles[t].color = shape->linestyles[t].color;
395     }
396     return s;
397 }
398
399 void swf_Process(RENDERBUF*dest, U32 clipdepth);
400
401 double matrixsize(MATRIX*m)
402 {
403     double l1 = sqrt((m->sx /65536.0) * (m->sx /65536.0) + (m->r0 /65536.0) * (m->r0/65536.0) );
404     double l2 = sqrt((m->r1 /65536.0) * (m->r1 /65536.0) + (m->sy /65536.0) * (m->sy/65536.0) );
405     return sqrt(l1*l2);
406 }
407
408 void swf_RenderShape(RENDERBUF*dest, SHAPE2*shape, MATRIX*m, CXFORM*c, U16 _depth,U16 _clipdepth)
409 {
410     renderbuf_internal*i = (renderbuf_internal*)dest->internal;
411     
412     SHAPELINE*line;
413     int x=0,y=0;
414     MATRIX mat = *m;
415     SHAPE2* s2 = 0;
416     SHAPE2* lshape = 0;
417     renderpoint_t p, lp;
418     U32 clipdepth;
419     double widthmultiply = matrixsize(m);
420
421     memset(&p, 0, sizeof(renderpoint_t));
422     memset(&lp, 0, sizeof(renderpoint_t));
423     
424     clipdepth = _clipdepth? _clipdepth << 16 | 0xffff : 0;
425     p.depth = _depth << 16;
426
427     mat.tx -= dest->posx*20;
428     mat.ty -= dest->posy*20;
429
430     s2 = swf_Shape2Clone(shape);
431     line = s2->lines;
432     if(shape->numfillstyles) {
433         int t;
434         p.s = s2;
435         /* multiply fillstyles matrices with placement matrix-
436            important for texture and gradient fill */
437         for(t=0;t<s2->numfillstyles;t++) {
438             MATRIX nm;
439             swf_MatrixJoin(&nm, &mat, &s2->fillstyles[t].m);
440             /*nm.sx *= i->multiply;
441             nm.sy *= i->multiply;
442             nm.r0 *= i->multiply;
443             nm.r1 *= i->multiply;
444             nm.tx *= i->multiply;
445             nm.ty *= i->multiply;*/
446             s2->fillstyles[t].m = nm;
447         }
448     }
449
450     if(shape->numlinestyles) {
451         lshape = linestyle2fillstyle(shape);
452         lp.s = lshape;
453         lp.depth = (_depth << 16)+1;
454     }
455
456
457     while(line)
458     {
459         int x1,y1,x2,y2,x3,y3;
460
461         if(line->type == moveTo) {
462         } else if(line->type == lineTo) {
463             transform_point(&mat, x, y, &x1, &y1);
464             transform_point(&mat, line->x, line->y, &x3, &y3);
465             
466             if(line->linestyle && ! clipdepth) {
467                 lp.shapeline = &lshape->lines[line->linestyle-1];
468                 add_solidline(dest, x1, y1, x3, y3, shape->linestyles[line->linestyle-1].width * widthmultiply, &lp);
469                 lp.depth++;
470             }
471             if(line->fillstyle0 || line->fillstyle1) {
472                 assert(shape->numfillstyles);
473                 p.shapeline = line;
474                 add_line(dest, x1, y1, x3, y3, &p);
475             }
476         } else if(line->type == splineTo) {
477             int c,t,parts,qparts;
478             double xx,yy;
479             
480             transform_point(&mat, x, y, &x1, &y1);
481             transform_point(&mat, line->sx, line->sy, &x2, &y2);
482             transform_point(&mat, line->x, line->y, &x3, &y3);
483             
484             c = abs(x3-2*x2+x1) + abs(y3-2*y2+y1);
485             xx=x1;
486             yy=y1;
487
488             parts = (int)(sqrt((float)c)/3);
489             if(!parts) parts = 1;
490
491             for(t=1;t<=parts;t++) {
492                 double nx = (double)(t*t*x3 + 2*t*(parts-t)*x2 + (parts-t)*(parts-t)*x1)/(double)(parts*parts);
493                 double ny = (double)(t*t*y3 + 2*t*(parts-t)*y2 + (parts-t)*(parts-t)*y1)/(double)(parts*parts);
494                 
495                 if(line->linestyle && ! clipdepth) {
496                     lp.shapeline = &lshape->lines[line->linestyle-1];
497                     add_solidline(dest, xx, yy, nx, ny, shape->linestyles[line->linestyle-1].width * widthmultiply, &lp);
498                     lp.depth++;
499                 }
500                 if(line->fillstyle0 || line->fillstyle1) {
501                     assert(shape->numfillstyles);
502                     p.shapeline = line;
503                     add_line(dest, xx, yy, nx, ny, &p);
504                 }
505
506                 xx = nx;
507                 yy = ny;
508             }
509         }
510         x = line->x;
511         y = line->y;
512         line = line->next;
513     }
514     
515     swf_Process(dest, clipdepth);
516     
517     if(s2) {
518         swf_Shape2Free(s2);rfx_free(s2);s2=0;
519     }
520     if(lshape) {
521         swf_Shape2Free(lshape);rfx_free(lshape);lshape=0;
522     }
523
524 }
525
526 static RGBA color_red = {255,255,0,0};
527 static RGBA color_white = {255,255,255,255};
528 static RGBA color_black = {255,0,0,0};
529
530 static void fill_clip(RGBA*line, int*z, int y, int x1, int x2, U32 depth)
531 {
532     int x = x1;
533     if(x1>=x2)
534         return;
535     do {
536         if(depth > z[x]) {
537             z[x] = depth;
538         }
539     } while(++x<x2);
540 }
541
542
543 static void fill_solid(RGBA*line, int*z, int y, int x1, int x2, RGBA col, U32 depth)
544 {
545     int x = x1;
546
547     if(col.a!=255) {
548         int ainv = 255-col.a;
549         col.r = (col.r*col.a)>>8;
550         col.g = (col.g*col.a)>>8;
551         col.b = (col.b*col.a)>>8;
552         col.a = 255;
553         do {
554             if(depth >= z[x]) {
555                 line[x].r = ((line[x].r*ainv)>>8)+col.r;
556                 line[x].g = ((line[x].g*ainv)>>8)+col.g;
557                 line[x].b = ((line[x].b*ainv)>>8)+col.b;
558                 line[x].a = 255;
559                 z[x] = depth;
560             }
561         } while(++x<x2);
562     } else {
563         do {
564             if(depth >= z[x]) {
565                 line[x] = col;
566                 z[x] = depth;
567             }
568         } while(++x<x2);
569     }
570 }
571
572 static int inline clamp(int v)
573 {
574     if(v>255) return 255;
575     else return v;
576 }
577
578 static void fill_bitmap(RGBA*line, int*z, int y, int x1, int x2, MATRIX*m, bitmap_t*b, int clipbitmap, U32 depth, double fmultiply)
579 {
580     int x = x1;
581     
582     double m11= m->sx*fmultiply/65536.0, m21= m->r1*fmultiply/65536.0;
583     double m12= m->r0*fmultiply/65536.0, m22= m->sy*fmultiply/65536.0;
584     double rx = m->tx*fmultiply/20.0;
585     double ry = m->ty*fmultiply/20.0;
586
587     double det = m11*m22 - m12*m21;
588     if(fabs(det) < 0.0005) { 
589         /* x direction equals y direction- the image is invisible */
590         return;
591     }
592     det = 20.0/det;
593
594     if(!b->width || !b->height) {
595         fill_solid(line, z, y, x1, x2, color_red, depth);
596         return;
597     }
598
599     do {
600         if(depth >= z[x]) {
601             RGBA col;
602             int xx = (int)((  (x - rx) * m22 - (y - ry) * m21)*det);
603             int yy = (int)((- (x - rx) * m12 + (y - ry) * m11)*det);
604             int ainv;
605
606             if(clipbitmap) {
607                 if(xx<0) xx=0;
608                 if(xx>=b->width) xx = b->width-1;
609                 if(yy<0) yy=0;
610                 if(yy>=b->height) yy = b->height-1;
611             } else {
612                 xx %= b->width;
613                 yy %= b->height;
614                 if(xx<0) xx += b->width;
615                 if(yy<0) yy += b->height;
616             }
617
618             col = b->data[yy*b->width+xx];
619             ainv = 255-col.a;
620
621             line[x].r = clamp(((line[x].r*ainv)>>8)+col.r);
622             line[x].g = clamp(((line[x].g*ainv)>>8)+col.g);
623             line[x].b = clamp(((line[x].b*ainv)>>8)+col.b);
624             line[x].a = 255;
625             
626             z[x] = depth;
627         }
628     } while(++x<x2);
629 }
630
631 static void fill_gradient(RGBA*line, int*z, int y, int x1, int x2, MATRIX*m, GRADIENT*g, int type, U32 depth, double fmultiply)
632 {
633     int x = x1;
634     
635     double m11= m->sx*fmultiply/80, m21= m->r1*fmultiply/80;
636     double m12= m->r0*fmultiply/80, m22= m->sy*fmultiply/80;
637     double rx = m->tx*fmultiply/20.0;
638     double ry = m->ty*fmultiply/20.0;
639
640     double det = m11*m22 - m12*m21;
641     if(fabs(det) < 0.0005) { 
642         /* x direction equals y direction- the image is invisible */
643         return;
644     }
645     det = 1.0/det;
646
647     RGBA palette[512];
648     RGBA oldcol = g->rgba[0];
649     int r0 = g->ratios[0]*2;
650     int t;
651     for(t=0;t<r0;t++) 
652         palette[t] = oldcol;
653     for(t=1;t<g->num;t++) {
654         int r1 = g->ratios[t]*2;
655         RGBA newcol = g->rgba[t];
656         if(r0 == r1)
657             continue;
658         //printf("%d %d->%d %02x%02x%02x%02x->%02x%02x%02x%02x\n", 
659         //      t, r0, r1, oldcol.r,oldcol.g,oldcol.b,oldcol.a,
660         //      newcol.r,newcol.g,newcol.b,newcol.a);
661         double f = 1.0 / (r1-r0);
662         double p0 = 1;
663         double p1 = 0;
664         int s;
665         for(;r0<=r1;r0++) {
666             palette[r0].r = oldcol.r*p0 + newcol.r*p1;
667             palette[r0].g = oldcol.g*p0 + newcol.g*p1;
668             palette[r0].b = oldcol.b*p0 + newcol.b*p1;
669             palette[r0].a = oldcol.a*p0 + newcol.a*p1;
670             p0 -= f;
671             p1 += f;
672         }
673         oldcol = newcol;
674     }
675     for(t=r0;t<512;t++) 
676         palette[t] = oldcol;
677
678     do {
679         if(depth >= z[x]) {
680             RGBA col;
681             double xx = (  (x - rx) * m22 - (y - ry) * m21)*det;
682             double yy = (- (x - rx) * m12 + (y - ry) * m11)*det;
683             int ainv;
684             ainv = 255-col.a;
685
686             if(type == FILL_LINEAR) {
687                 int xr = xx*256;
688                 if(xr<-256)
689                     xr = -256;
690                 if(xr>255)
691                     xr = 255;
692                 col = palette[xr+256];
693             } else {
694                 int xr = sqrt(xx*xx+yy*yy)*511;
695                 if(xr<0)
696                     xr = 0;
697                 if(xr>511)
698                     xr = 511;
699                 col = palette[xr];
700             }
701
702             line[x].r = clamp(((line[x].r*ainv)>>8)+col.r);
703             line[x].g = clamp(((line[x].g*ainv)>>8)+col.g);
704             line[x].b = clamp(((line[x].b*ainv)>>8)+col.b);
705             line[x].a = 255;
706             
707             z[x] = depth;
708         }
709     } while(++x<x2);
710 }
711
712 typedef struct _layer {
713     int fillid;
714     renderpoint_t*p;
715     struct _layer*next;
716     struct _layer*prev;
717 } layer_t;
718
719 typedef struct {
720     layer_t*layers;
721 } state_t;
722
723
724 static void fill(RENDERBUF*dest, RGBA*line, int*zline, int y, int x1, int x2, state_t*fillstate, U32 clipdepth)
725 {
726     renderbuf_internal*i = (renderbuf_internal*)dest->internal;
727     int clip=1;
728
729     layer_t*l = fillstate->layers;
730
731     if(x1>=x2) //zero width? nothing to do.
732         return;
733     
734     while(l) {
735         if(l->fillid == 0) {
736             /* not filled. TODO: we should never add those in the first place */
737             if(DEBUG&2)
738                 printf("(not filled)");
739         } else if(l->fillid > l->p->s->numfillstyles) {
740             fprintf(stderr, "Fill style out of bounds (%d>%d)", l->fillid, l->p->s->numlinestyles);
741         } else if(clipdepth) {
742             /* filled region- not used for clipping */
743             clip = 0;
744         } else {
745             FILLSTYLE*f;
746             if(DEBUG&2) 
747                 printf("(%d -> %d style %d)", x1, x2, l->fillid);
748
749             f = &l->p->s->fillstyles[l->fillid-1];
750
751             if(f->type == FILL_SOLID) {
752                 /* plain color fill */
753                 fill_solid(line, zline, y, x1, x2, f->color, l->p->depth);
754             } else if(f->type == FILL_TILED || f->type == FILL_CLIPPED || f->type == (FILL_TILED|2) || f->type == (FILL_CLIPPED|2)) {
755                 /* TODO: optimize (do this in add_pixel()?) */
756                 bitmap_t* b = i->bitmaps;
757                 while(b && b->id != f->id_bitmap) {
758                     b = b->next;
759                 }
760                 if(!b) {
761                     fprintf(stderr, "Shape references unknown bitmap %d\n", f->id_bitmap);
762                     fill_solid(line, zline, y, x1, x2, color_red, l->p->depth);
763                 } else {
764                     fill_bitmap(line, zline, y, x1, x2, &f->m, b, /*clipped?*/f->type&1, l->p->depth, i->multiply);
765                 }
766             } else if(f->type == FILL_LINEAR || f->type == FILL_RADIAL) {
767                 fill_gradient(line, zline, y, x1, x2, &f->m, &f->gradient, f->type, l->p->depth, i->multiply);
768             } else {
769                 fprintf(stderr, "Undefined fillmode: %02x\n", f->type);
770             }
771         }
772         l = l->next;
773     }
774     if(clip && clipdepth) {
775         fill_clip(line, zline, y, x1, x2, clipdepth);
776     }
777 }
778
779 static void search_layer(state_t*state, int depth, layer_t**before, layer_t**self, layer_t**after)
780 {
781     layer_t*last=0,*l = state->layers;
782     while(l && l->p->depth < depth) {
783         last = l;
784         l = l->next;
785     }
786     *before = last;
787     if(l && l->p->depth == depth)
788         *self = l;
789     else
790         *after = l;
791 }
792 static void delete_layer(state_t*state, layer_t*todel)
793 {
794     layer_t*before=todel->prev;
795     layer_t*next = todel->next;
796     rfx_free(todel);
797     if(!before) {
798         state->layers = next;
799         if(next)
800             next->prev = 0;
801     } else {
802         before->next = next;
803         if(before->next)
804             before->next->prev = before;
805     }
806 }
807 static void add_layer(state_t*state, layer_t*before, layer_t*toadd)
808 {
809     if(!before) {
810         toadd->next = state->layers;
811         toadd->prev = 0;
812         state->layers=toadd;
813     } else {
814         toadd->next = before->next;
815         toadd->prev = before;
816         before->next = toadd;
817     }
818     if(toadd->next)
819         toadd->next->prev = toadd;
820 }
821 static void free_layers(state_t* state)
822 {
823     layer_t*l = state->layers;
824     while(l) {
825         layer_t*next = l->next;
826         rfx_free(l);
827         l = next;
828     }
829 }
830
831 static void change_state(int y, state_t* state, renderpoint_t*p)
832 {
833     layer_t*before=0, *self=0, *after=0;
834
835     if(DEBUG&2) { 
836         printf("[(%d,%d)/%d/%d-%d]", p->x, y, p->depth, p->shapeline->fillstyle0, p->shapeline->fillstyle1);
837     }
838
839     search_layer(state, p->depth, &before, &self, &after);
840
841     if(self) {
842         /* shape update */
843         if(self->fillid<0/*??*/ || !p->shapeline->fillstyle0 || !p->shapeline->fillstyle1) {
844             /* filling ends */
845             if(DEBUG&2) printf("<D>");
846             
847             delete_layer(state, self);
848         } else { 
849             /*both fill0 and fill1 are set- exchange the two, updating the layer */
850             if(self->fillid == p->shapeline->fillstyle0) {
851                 self->fillid = p->shapeline->fillstyle1;
852                 self->p = p;
853                 if(DEBUG&2) printf("<X>");
854             } else if(self->fillid == p->shapeline->fillstyle1) {
855                 self->fillid = p->shapeline->fillstyle0;
856                 self->p = p;
857                 if(DEBUG&2) printf("<X>");
858             } else {
859                 /* buggy shape. keep everything as-is. */
860                 if(DEBUG&2) printf("<!>");
861                 //fprintf(stderr, "<line %d: bad swap>\n", y);
862             }
863         }
864         return;
865     } else {
866         layer_t* n = 0;
867         if(p->shapeline && p->shapeline->fillstyle0 && p->shapeline->fillstyle1) {
868             /* this is a hack- a better way would be to make sure that
869                we always get (0,32), (32, 33), (33, 0) in the right order if
870                they happen to fall on the same pixel.
871                (not: (0,32), (33, 0), (32, 33))
872                Notice: Weird fill styles appear if linestyles are involved, too.
873             */
874             fprintf(stderr, "<line %d: both fillstyles set while not inside shape>\n", y);
875             return;
876         }
877         
878         n = (layer_t*)rfx_calloc(sizeof(layer_t));
879
880         if(DEBUG&2) printf("<+>");
881
882         n->fillid = p->shapeline->fillstyle0 ? p->shapeline->fillstyle0 : p->shapeline->fillstyle1;
883         n->p = p;
884
885         add_layer(state, before, n);
886     }
887 }
888
889 void swf_Process(RENDERBUF*dest, U32 clipdepth)
890 {
891     renderbuf_internal*i = (renderbuf_internal*)dest->internal;
892     int y;
893     
894     if(i->ymax < i->ymin) {
895         /* shape is empty. return. 
896            only, if it's a clipshape, remember the clipdepth */
897         if(clipdepth) {
898             for(y=0;y<i->height2;y++) {
899                 if(clipdepth > i->lines[y].pending_clipdepth)
900                     i->lines[y].pending_clipdepth = clipdepth;
901             }
902         }
903         return; //nothing (else) to do
904     }
905
906     if(clipdepth) {
907         /* lines outside the clip shape are not filled
908            immediately, only the highest clipdepth so far is
909            stored there. They will be clipfilled once there's
910            actually something about to happen in that line */
911         for(y=0;y<i->ymin;y++) {
912             if(clipdepth > i->lines[y].pending_clipdepth)
913                 i->lines[y].pending_clipdepth = clipdepth;
914         }
915         for(y=i->ymax+1;y<i->height2;y++) {
916             if(clipdepth > i->lines[y].pending_clipdepth)
917                 i->lines[y].pending_clipdepth = clipdepth;
918         }
919     }
920     
921     for(y=i->ymin;y<=i->ymax;y++) {
922         int n;
923         TAG*tag = i->lines[y].points;
924         int num = i->lines[y].num;
925         renderpoint_t*points = (renderpoint_t*)tag->data;
926         RGBA*line = &i->img[i->width2*y];
927         int*zline = &i->zbuf[i->width2*y];
928         int lastx = 0;
929         state_t fillstate;
930         memset(&fillstate, 0, sizeof(state_t));
931         qsort(points, num, sizeof(renderpoint_t), compare_renderpoints);
932         /* resort points */
933         /*if(y==884) {
934             for(n=0;n<num;n++) {
935                 printf("%f (%d/%d) %d\n", points[n].x, 
936                         points[n].shapeline->fillstyle0,
937                         points[n].shapeline->fillstyle1,
938                         points[n].shapeline->linestyle);
939             }
940         }*/
941
942         if(i->lines[y].pending_clipdepth && !clipdepth) {
943             fill_clip(line, zline, y, 0, i->width2, i->lines[y].pending_clipdepth);
944             i->lines[y].pending_clipdepth=0;
945         }
946
947         for(n=0;n<num;n++) {
948             renderpoint_t*p = &points[n];
949             renderpoint_t*next= n<num-1?&points[n+1]:0;
950             int startx = (int)p->x;
951             int endx = (int)(next?next->x:i->width2);
952             if(endx > i->width2)
953                 endx = i->width2;
954             if(startx < 0)
955                 startx = 0;
956             if(endx < 0)
957                 endx = 0;
958
959             if(clipdepth) {
960                 /* for clipping, the inverse is filled 
961                    TODO: lastx!=startx only at the start of the loop, 
962                          so this might be moved up
963                  */
964                 fill_clip(line, zline, y, lastx, startx, clipdepth);
965             }
966             change_state(y, &fillstate, p);
967
968             fill(dest, line, zline, y, startx, endx, &fillstate, clipdepth);
969 /*          if(y == 0 && startx == 232 && endx == 418) {
970                 printf("ymin=%d ymax=%d\n", i->ymin, i->ymax);
971                 for(n=0;n<num;n++) {
972                     renderpoint_t*p = &points[n];
973                     printf("x=%f depth=%08x\n", p->x, p->depth);
974                 }
975             }*/
976
977             lastx = endx;
978             if(endx == i->width2)
979                 break;
980         }
981         if(clipdepth) {
982             /* TODO: is lastx *ever* != i->width2 here? */
983             fill_clip(line, zline, y, lastx, i->width2, clipdepth);
984         }
985         free_layers(&fillstate);
986         
987         i->lines[y].num = 0;
988         swf_ClearTag(i->lines[y].points);
989     }
990     i->ymin = 0x7fffffff;
991     i->ymax = -0x80000000;
992 }
993
994 RGBA* swf_Render(RENDERBUF*dest)
995 {
996     renderbuf_internal*i = (renderbuf_internal*)dest->internal;
997     RGBA* img = (RGBA*)rfx_alloc(sizeof(RGBA)*dest->width*dest->height);
998     int y;
999     int antialize = i->antialize;
1000    
1001     if(antialize <= 1) /* no antializing */ {
1002         for(y=0;y<i->height2;y++) {
1003             RGBA*line = &i->img[y*i->width2];
1004             memcpy(&img[y*dest->width], line, sizeof(RGBA)*dest->width);
1005         }
1006     } else {
1007         RGBA**lines = (RGBA**)rfx_calloc(sizeof(RGBA*)*antialize);
1008         int q = antialize*antialize;
1009         int ypos = 0;
1010         for(y=0;y<i->height2;y++) {
1011             int n;
1012             ypos = y % antialize;
1013             lines[ypos] = &i->img[y*i->width2];
1014             if(ypos == antialize-1) {
1015                 RGBA*out = &img[(y / antialize)*dest->width];
1016                 int x;
1017                 int r,g,b,a;
1018                 for(x=0;x<dest->width;x++) {
1019                     int xpos = x*antialize;
1020                     int yp;
1021                     U32 r=0,g=0,b=0,a=0;
1022                     for(yp=0;yp<antialize;yp++) {
1023                         RGBA*lp = &lines[yp][xpos];
1024                         int xp;
1025                         for(xp=0;xp<antialize;xp++) {
1026                             RGBA*p = &lp[xp];
1027                             r += p->r;
1028                             g += p->g;
1029                             b += p->b;
1030                             a += p->a;
1031                         }
1032                     }
1033                     out[x].r = r / q;
1034                     out[x].g = g / q;
1035                     out[x].b = b / q;
1036                     out[x].a = a / q;
1037                 }
1038             }
1039         }
1040         rfx_free(lines);
1041     }
1042     return img;
1043 }
1044
1045 typedef struct
1046 {
1047     int numchars;
1048     SHAPE2**glyphs;
1049 } font_t;
1050
1051 enum CHARACTER_TYPE {none_type, shape_type, image_type, text_type, edittext_type, font_type, sprite_type};
1052 typedef struct
1053 {
1054     TAG*tag;
1055     SRECT*bbox;
1056     enum CHARACTER_TYPE type;
1057     union {
1058         SHAPE2*shape;
1059         font_t*font;
1060     } obj;
1061 } character_t;
1062
1063 int compare_placements(const void *v1, const void *v2)
1064 {
1065     SWFPLACEOBJECT*p1 = (SWFPLACEOBJECT*)v1;
1066     SWFPLACEOBJECT*p2 = (SWFPLACEOBJECT*)v2;
1067     if(p1->depth != p2->depth)
1068         return (int)p1->depth - (int)p2->depth;
1069     else 
1070         if(p2->clipdepth)
1071             return 1; // do the clip first
1072         else
1073             return -1;
1074
1075 /*    if(!p1->clipdepth) {
1076         if(!p2->clipdepth) {
1077             // !p1->clipdepth && !p2->clipdepth
1078             return (int)p1->depth - (int)p2->depth;
1079         } else {
1080             // !p1->clipdepth && p2->clipdepth
1081             if(p1->depth != p2->clipdepth)
1082                 return (int)p1->depth - (int)p2->clipdepth;
1083             else
1084                 return 1; // do the clip first
1085         }
1086     } else {
1087         if(!p2->clipdepth) {
1088             // p1->clipdepth && !p2->clipdepth
1089             if(p1->clipdepth != p2->depth)
1090                 return (int)p1->clipdepth - (int)p2->depth;
1091             else
1092                 return -1;// do the clip first
1093         } else {
1094             if(p1->clipdepth != p2->clipdepth)
1095                 return (int)p1->clipdepth - (int)p2->clipdepth;
1096             else
1097                 return (int)p1->depth - (int)p2->depth;
1098         }
1099     }*/
1100 }
1101
1102 typedef struct textcallbackblock
1103 {
1104     character_t*idtable;
1105     U16 depth;
1106     U16 clipdepth;
1107     CXFORM* cxform;
1108     MATRIX m;
1109     RENDERBUF*buf;
1110 } textcallbackblock_t;
1111
1112 static void textcallback(void*self, int*chars, int*xpos, int nr, int fontid, int fontsize, 
1113                     int xstart, int ystart, RGBA* color)
1114 {
1115     textcallbackblock_t * info = (textcallbackblock_t*)self;
1116     font_t*font = 0;
1117     int t;
1118     if(info->idtable[fontid].type != font_type) {
1119         fprintf(stderr, "ID %d is not a font\n", fontid);
1120         return;
1121     } else if(!info->idtable[fontid].obj.font) {
1122         fprintf(stderr, "Font %d unknown\n", fontid);
1123         return;
1124     } else {
1125         font  = info->idtable[fontid].obj.font;
1126     }
1127     for(t=0;t<nr;t++) {
1128         int x = xstart + xpos[t];
1129         int y = ystart;
1130         MATRIX m = info->m;
1131         SPOINT p;
1132         
1133         p.x = x; p.y = y; 
1134         p = swf_TurnPoint(p, &m);
1135         
1136         m.sx = (m.sx * fontsize) / 1024;
1137         m.sy = (m.sy * fontsize) / 1024;
1138         m.r0 = (m.r0 * fontsize) / 1024;
1139         m.r1 = (m.r1 * fontsize) / 1024;
1140         m.tx = p.x;
1141         m.ty = p.y;
1142
1143         if(chars[t]<0 || chars[t]>= font->numchars) {
1144             fprintf(stderr, "Character out of range: %d\n", chars[t]);
1145         } else {
1146             SHAPE2*shape = font->glyphs[chars[t]];
1147             shape->fillstyles[0].color = *color; //q&d
1148             /*printf("Rendering char %d (size %d, x:%d, y:%d) color:%02x%02x%02x%02x\n", chars[t], fontsize, x, y,
1149                     color->a, color->r, color->g, color->b);
1150             swf_DumpMatrix(stdout, &m);
1151             swf_DumpShape(shape);*/
1152             swf_RenderShape(info->buf, shape, &m, info->cxform, info->depth, info->clipdepth);
1153         }
1154     }
1155 }
1156
1157 static void renderFromTag(RENDERBUF*buf, character_t*idtable, TAG*firstTag, MATRIX*m)
1158 {
1159     TAG*tag = 0;
1160     int numplacements = 0;
1161     SWFPLACEOBJECT* placements;
1162
1163     tag = firstTag;
1164     numplacements = 0;
1165     while(tag) {
1166         if(tag->id == ST_PLACEOBJECT || 
1167            tag->id == ST_PLACEOBJECT2) {
1168             numplacements++;
1169         }
1170         if(tag->id == ST_SHOWFRAME || tag->id == ST_END)
1171             break;
1172         tag = tag->next;
1173     }
1174     placements = (SWFPLACEOBJECT*)rfx_calloc(sizeof(SWFPLACEOBJECT)*numplacements);
1175     numplacements = 0;
1176
1177     tag = firstTag;
1178     while(tag) {
1179         if(swf_isPlaceTag(tag)) {
1180             SWFPLACEOBJECT p;
1181             swf_GetPlaceObject(tag, &p);
1182             /* TODO: add move and deletion */
1183             placements[numplacements++] = p;
1184             swf_PlaceObjectFree(&p); //dirty! but it only frees fields we don't use
1185         }
1186         if(tag->id == ST_SHOWFRAME || tag->id == ST_END)
1187             break;
1188         tag = tag->next;
1189     }
1190
1191     qsort(placements, numplacements, sizeof(SWFPLACEOBJECT), compare_placements);
1192      
1193     int t;
1194     for(t=0;t<numplacements;t++) {
1195         SWFPLACEOBJECT*p = &placements[t];
1196         int id = p->id;
1197         MATRIX m2;
1198         swf_MatrixJoin(&m2, m, &p->matrix);
1199             
1200         if(!idtable[id].tag) { 
1201             fprintf(stderr, "rfxswf: Id %d is unknown\n", id);
1202             continue;
1203         }
1204
1205         if(idtable[id].type == shape_type) {
1206             //SRECT sbbox = swf_TurnRect(*idtable[id].bbox, &p->matrix);
1207             swf_RenderShape(buf, idtable[id].obj.shape, &m2, &p->cxform, p->depth, p->clipdepth);
1208         } else if(idtable[id].type == sprite_type) {
1209             swf_UnFoldSprite(idtable[id].tag);
1210             renderFromTag(buf, idtable, idtable[id].tag->next, &m2);
1211             swf_FoldSprite(idtable[id].tag);
1212         } else if(idtable[id].type == text_type) {
1213             TAG* tag = idtable[id].tag;
1214             textcallbackblock_t info;
1215             MATRIX mt;
1216
1217             swf_SetTagPos(tag, 0);
1218             swf_GetU16(tag);
1219             swf_GetRect(tag,0);
1220             swf_GetMatrix(tag,&mt);
1221             swf_MatrixJoin(&info.m, &m2, &mt);
1222             /*printf("Text matrix:\n");
1223             swf_DumpMatrix(stdout, &m);
1224             printf("Placement matrix:\n");
1225             swf_DumpMatrix(stdout, &p->matrix);
1226             printf("Final matrix:\n");
1227             swf_DumpMatrix(stdout, &info.m);*/
1228
1229             info.idtable = idtable;
1230             info.depth = p->depth;
1231             info.cxform = &p->cxform;
1232             info.clipdepth = p->clipdepth;
1233             info.buf = buf;
1234             
1235             swf_ParseDefineText(tag, textcallback, &info);
1236         } else if(idtable[id].type == edittext_type) {
1237             TAG* tag = idtable[id].tag;
1238             U16 flags = swf_GetBits(tag, 16);
1239             if(flags & ET_HASTEXT) {
1240                 fprintf(stderr, "edittext not supported yet (id %d)\n", id);
1241             }
1242         } else {
1243             fprintf(stderr, "Unknown/Unsupported Object Type for id %d: %s\n", id, swf_TagGetName(idtable[id].tag));
1244         }
1245     }
1246
1247     free(placements);
1248 }
1249
1250 void swf_RenderSWF(RENDERBUF*buf, SWF*swf)
1251 {
1252     TAG*tag;
1253     int t;
1254     RGBA color;
1255
1256     swf_FoldAll(swf);
1257     
1258     character_t* idtable = (character_t*)rfx_calloc(sizeof(character_t)*65536);            // id to character mapping
1259     
1260     /* set background color */
1261     color = swf_GetSWFBackgroundColor(swf);
1262     swf_Render_SetBackgroundColor(buf, color);
1263
1264     /* parse definitions */
1265     tag = swf->firstTag;
1266     while(tag) {
1267         if(swf_isDefiningTag(tag)) {
1268             int id = swf_GetDefineID(tag);
1269             idtable[id].tag = tag;
1270             idtable[id].bbox = (SRECT*)rfx_alloc(sizeof(SRECT));
1271             *idtable[id].bbox = swf_GetDefineBBox(tag);
1272
1273             if(swf_isShapeTag(tag)) {
1274                 SHAPE2* shape = (SHAPE2*)rfx_calloc(sizeof(SHAPE2));
1275                 swf_ParseDefineShape(tag, shape);
1276                 idtable[id].type = shape_type;
1277                 idtable[id].obj.shape = shape;
1278             } else if(swf_isImageTag(tag)) {
1279                 int width,height;
1280                 RGBA*data = swf_ExtractImage(tag, &width, &height);
1281                 idtable[id].type = image_type;
1282                 swf_Render_AddImage(buf, id, data, width, height);
1283                 free(data);
1284             } else if(tag->id == ST_DEFINEFONT ||
1285                       tag->id == ST_DEFINEFONT2) {
1286                 int t;
1287                 SWFFONT*swffont;
1288                 font_t*font = (font_t*)rfx_calloc(sizeof(font_t));
1289                 idtable[id].obj.font = font;
1290                 swf_FontExtract(swf,id,&swffont);
1291                 font->numchars = swffont->numchars;
1292                 font->glyphs = (SHAPE2**)rfx_calloc(sizeof(SHAPE2*)*font->numchars);
1293                 for(t=0;t<font->numchars;t++) {
1294                     if(!swffont->glyph[t].shape->fillstyle.n) {
1295                         /* the actual fill color will be overwritten while rendering */
1296                         swf_ShapeAddSolidFillStyle(swffont->glyph[t].shape, &color_white);
1297                     }
1298                     font->glyphs[t] = swf_ShapeToShape2(swffont->glyph[t].shape);
1299                 }
1300                 swf_FontFree(swffont);
1301                 idtable[id].type = font_type;
1302
1303             } else if(tag->id == ST_DEFINEFONTINFO ||
1304                       tag->id == ST_DEFINEFONTINFO2) {
1305                 idtable[id].type = font_type;
1306             } else if(tag->id == ST_DEFINETEXT ||
1307                       tag->id == ST_DEFINETEXT2) {
1308                 idtable[id].type = text_type;
1309             } else if(tag->id == ST_DEFINESPRITE) {
1310                 idtable[id].type = sprite_type;
1311             } else if(tag->id == ST_DEFINEEDITTEXT) {
1312                 idtable[id].type = edittext_type;
1313             }
1314         }
1315         tag = tag->next;
1316     }
1317     MATRIX m;
1318     swf_GetMatrix(0, &m);
1319     renderFromTag(buf, idtable, swf->firstTag, &m);
1320     
1321     /* free id and depth tables again */
1322     for(t=0;t<65536;t++) {
1323         if(idtable[t].bbox) {
1324             free(idtable[t].bbox);
1325             idtable[t].bbox=0;
1326         }
1327         if(idtable[t].type == shape_type) {
1328             SHAPE2* shape = idtable[t].obj.shape;
1329             if(shape) {
1330                 swf_Shape2Free(shape); // FIXME
1331                 free(idtable[t].obj.shape);idtable[t].obj.shape = 0;
1332             }
1333         } else if(idtable[t].type == font_type) {
1334             font_t* font = idtable[t].obj.font;
1335             if(font) {
1336                 if(font->glyphs) {
1337                     int t;
1338                     for(t=0;t<font->numchars;t++) {
1339                         swf_Shape2Free(font->glyphs[t]);
1340                         free(font->glyphs[t]); font->glyphs[t] = 0;
1341                     }
1342                     free(font->glyphs);
1343                     font->glyphs = 0;
1344                 }
1345                 free(idtable[t].obj.font); idtable[t].obj.font = 0;
1346                 font = 0;
1347             }
1348         }
1349     }
1350     free(idtable);
1351 }
1352