2969e750abfb8eaec01139ef2c611d83df9447a1
[swftools.git] / lib / devices / swf.c
1 /* gfxdevice_swf.c
2
3    Part of the swftools package.
4
5    Copyright (c) 2001,2002,2003,2004,2005 Matthias Kramm <kramm@quiss.org> 
6
7    Swftools is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    Swftools is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with swftools; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include "../../config.h"
25 #include <fcntl.h>
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif
29 #ifdef HAVE_IO_H
30 #include <io.h>
31 #endif
32 #ifdef HAVE_ASSERT_H
33 #include <assert.h>
34 #else
35 #define assert(a)
36 #endif
37 #include <math.h>
38 #include "../mem.h"
39 #include "../log.h"
40 #include "../rfxswf.h"
41 #include "../gfxdevice.h"
42 #include "../gfxtools.h"
43 #include "swf.h"
44 #include "../gfxpoly.h"
45 #include "../png.h"
46
47 #define CHARDATAMAX 8192
48 #define CHARMIDX 0
49 #define CHARMIDY 0
50
51 typedef struct _chardata {
52     int charid;
53     int fontid; /* TODO: use a SWFFONT instead */
54     int x;
55     int y;
56     int size;
57     RGBA color;
58 } chardata_t;
59
60 typedef struct _fontlist
61 {
62     SWFFONT *swffont;
63     struct _fontlist*next;
64 } fontlist_t;
65
66 typedef long int twip;
67
68 typedef struct _swfmatrix {
69     double m11,m12,m21,m22,m31,m32;
70 } swfmatrix_t;
71
72 typedef struct _swfoutput_internal
73 {
74     gfxdevice_t*dev; // the gfxdevice object where this internal struct resides
75
76     double config_dumpfonts;
77     double config_ppmsubpixels;
78     double config_jpegsubpixels;
79     char hasbuttons;
80     int config_dots;
81     int config_simpleviewer;
82     int config_opennewwindow;
83     int config_ignoredraworder;
84     int config_drawonlyshapes;
85     int config_frameresets;
86     int config_linknameurl;
87     int config_jpegquality;
88     int config_storeallcharacters;
89     int config_enablezlib;
90     int config_insertstoptag;
91     int config_watermark;
92     int config_flashversion;
93     int config_reordertags;
94     int config_showclipshapes;
95     int config_splinemaxerror;
96     int config_fontsplinemaxerror;
97     int config_filloverlap;
98     int config_protect;
99     int config_bboxvars;
100     int config_disable_polygon_conversion;
101     int config_normalize_polygon_positions;
102     char config_disablelinks;
103     RGBA config_linkcolor;
104     float config_minlinewidth;
105     double config_caplinewidth;
106     char* config_linktarget;
107     char*config_internallinkfunction;
108     char*config_externallinkfunction;
109     char config_animate;
110     double config_framerate;
111
112     SWF* swf;
113
114     fontlist_t* fontlist;
115
116     char storefont;
117
118     MATRIX page_matrix;
119
120     TAG *tag;
121     int currentswfid;
122     int startids;
123     int depth;
124     int startdepth;
125     int linewidth;
126     
127     SHAPE* shape;
128     int shapeid;
129     int textid;
130
131     int watermarks;
132     
133     int fillstyleid;
134     int linestyleid;
135     int swflastx;
136     int swflasty;
137     int lastwasfill;
138     int shapeisempty;
139     char fill;
140     int min_x,max_x;
141     int min_y,max_y;
142     TAG* cliptags[128];
143     int clipshapes[128];
144     U32 clipdepths[128];
145     int clippos;
146
147     /* image cache */
148     /*
149     int pic_xids[1024];
150     int pic_yids[1024];
151     int pic_ids[1024];
152     int pic_width[1024];
153     int pic_height[1024];
154     int picpos;
155     */
156
157     int frameno;
158     int lastframeno;
159     
160     char fillstylechanged;
161
162     int jpeg; //next image type
163     
164     int bboxrectpos;
165     SRECT bboxrect;
166
167     SRECT pagebbox;
168
169     chardata_t chardata[CHARDATAMAX];
170     int chardatapos;
171     int firstpage;
172     char pagefinished;
173
174     char overflow;
175
176     int current_font_size;
177     MATRIX fontmatrix;
178     double lastfontm11,lastfontm12,lastfontm21,lastfontm22;
179     SWFFONT *swffont;
180     RGBA strokergb;
181     RGBA fillrgb;
182     int drawmode;
183
184     int shapeposx;
185     int shapeposy;
186
187     char* mark;
188
189 } swfoutput_internal;
190     
191 static void swf_fillbitmap(gfxdevice_t*driver, gfxline_t*line, gfximage_t*img, gfxmatrix_t*move, gfxcxform_t*cxform);
192 static int  swf_setparameter(gfxdevice_t*driver, const char*key, const char*value);
193 static void swf_drawstroke(gfxdevice_t*dev, gfxline_t*line, gfxcoord_t width, gfxcolor_t*color, gfx_capType cap_style, gfx_joinType joint_style, gfxcoord_t miterLimit);
194 static void swf_startclip(gfxdevice_t*dev, gfxline_t*line);
195 static void swf_endclip(gfxdevice_t*dev);
196 static void swf_stroke(gfxdevice_t*dev, gfxline_t*line, gfxcoord_t width, gfxcolor_t*color, gfx_capType cap_style, gfx_joinType joint_style, gfxcoord_t miterLimit);
197 static void swf_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color);
198 static void swf_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform);
199 static void swf_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix);
200 static void swf_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyph, gfxcolor_t*color, gfxmatrix_t*matrix);
201 static void swf_addfont(gfxdevice_t*dev, gfxfont_t*font);
202 static void swf_drawlink(gfxdevice_t*dev, gfxline_t*line, const char*action);
203 static void swf_startframe(gfxdevice_t*dev, int width, int height);
204 static void swf_endframe(gfxdevice_t*dev);
205 static gfxresult_t* swf_finish(gfxdevice_t*driver);
206
207 static swfoutput_internal* init_internal_struct()
208 {
209     swfoutput_internal*i = (swfoutput_internal*)malloc(sizeof(swfoutput_internal));
210     memset(i, 0, sizeof(swfoutput_internal));
211
212     i->storefont = 0;
213     i->currentswfid = 0;
214     i->depth = 0;
215     i->overflow = 0;
216     i->startdepth = 0;
217     i->linewidth = 0;
218     i->shapeid = -1;
219     i->textid = -1;
220     i->frameno = 0;
221     i->lastframeno = 0;
222
223     i->mark = 0;
224
225     i->fillstyleid;
226     i->linestyleid;
227     i->swflastx=0;
228     i->swflasty=0;
229     i->lastwasfill = 0;
230     i->shapeisempty = 1;
231     i->fill = 0;
232     i->clippos = 0;
233
234     i->fillstylechanged = 0;
235
236     i->bboxrectpos = -1;
237     i->chardatapos = 0;
238     i->firstpage = 1;
239     i->pagefinished = 1;
240
241     i->config_disablelinks=0;
242     i->config_dumpfonts=0;
243     i->config_ppmsubpixels=0;
244     i->config_jpegsubpixels=0;
245     i->config_opennewwindow=1;
246     i->config_ignoredraworder=0;
247     i->config_drawonlyshapes=0;
248     i->config_jpegquality=85;
249     i->config_storeallcharacters=0;
250     i->config_dots=1;
251     i->config_enablezlib=0;
252     i->config_insertstoptag=0;
253     i->config_flashversion=6;
254     i->config_framerate=0.25;
255     i->config_splinemaxerror=1;
256     i->config_fontsplinemaxerror=1;
257     i->config_filloverlap=0;
258     i->config_protect=0;
259     i->config_bboxvars=0;
260     i->config_showclipshapes=0;
261     i->config_minlinewidth=0.05;
262     i->config_caplinewidth=1;
263     i->config_linktarget=0;
264     i->config_internallinkfunction=0;
265     i->config_externallinkfunction=0;
266     i->config_reordertags=1;
267     i->config_linknameurl=0;
268
269     i->config_linkcolor.r = i->config_linkcolor.g = i->config_linkcolor.b = 255;
270     i->config_linkcolor.a = 0x40;
271
272     return i;
273 };
274
275 static int id_error = 0;
276
277 static U16 getNewID(gfxdevice_t* dev)
278 {
279     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
280     if(i->currentswfid == 65535) {
281         if(!id_error) {
282             msg("<error> ID Table overflow");
283             msg("<error> This file is too complex to render- SWF only supports 65536 shapes at once");
284         }
285         id_error=1;
286         i->overflow = 1;
287         exit(1);
288     }
289     return ++i->currentswfid;
290 }
291 static U16 getNewDepth(gfxdevice_t* dev)
292 {
293     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
294     if(i->depth == 65520) {
295         if(!id_error) {
296             msg("<error> Depth Table overflow");
297             msg("<error> This file is too complex to render- SWF only supports 65536 shapes at once");
298         }
299         id_error=1;
300         i->overflow = 1;
301         exit(1);
302     }
303     return ++i->depth;
304 }
305
306 static void startshape(gfxdevice_t* dev);
307 static void starttext(gfxdevice_t* dev);
308 static void endshape(gfxdevice_t* dev);
309 static void endtext(gfxdevice_t* dev);
310
311 typedef struct _plotxy
312 {
313     double x,y;
314 } plotxy_t;
315
316 // write a move-to command into the swf
317 static int movetoxy(gfxdevice_t*dev, TAG*tag, plotxy_t p0)
318 {
319     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
320     int rx = (int)(p0.x*20);
321     int ry = (int)(p0.y*20);
322     if(rx!=i->swflastx || ry!=i->swflasty || i->fillstylechanged) {
323       swf_ShapeSetMove (tag, i->shape, rx,ry);
324       i->fillstylechanged = 0;
325       i->swflastx=rx;
326       i->swflasty=ry;
327       return 1;
328     }
329     return 0;
330 }
331 static int moveto(gfxdevice_t*dev, TAG*tag, double  x, double y)
332 {
333     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
334     plotxy_t p;
335     p.x = x;
336     p.y = y;
337     return movetoxy(dev, tag, p);
338 }
339 static void addPointToBBox(gfxdevice_t*dev, int px, int py) 
340 {
341     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
342
343     SPOINT p;
344     p.x = px;
345     p.y = py;
346     if(i->fill) {
347         swf_ExpandRect(&i->bboxrect, p);
348     } else {
349         swf_ExpandRect3(&i->bboxrect, p, i->linewidth*3/2);
350     }
351 }
352
353 /*static void plot(gfxdevice_t*dev, int x, int y, TAG*tag)
354 {
355     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
356     int width = i->linewidth/4;
357     if(width > 5)
358         width = 5;
359     ////square
360     //swf_ShapeSetLine(tag, i->shape,-width,-width);
361     //swf_ShapeSetLine(tag, i->shape,width*2,0);
362     //swf_ShapeSetLine(tag, i->shape,0,width*2);
363     //swf_ShapeSetLine(tag, i->shape,-width*2,0);
364     //swf_ShapeSetLine(tag, i->shape,0,-width*2);
365     //swf_ShapeSetLine(tag, i->shape,width,width);
366    
367     // diamond
368     swf_ShapeSetLine(tag, i->shape,-width,0);
369     swf_ShapeSetLine(tag, i->shape,width,-width);
370     swf_ShapeSetLine(tag, i->shape,width,width);
371     swf_ShapeSetLine(tag, i->shape,-width,width);
372     swf_ShapeSetLine(tag, i->shape,-width,-width);
373     swf_ShapeSetLine(tag, i->shape,width,0);
374
375     addPointToBBox(dev, x-width ,y-width);
376     addPointToBBox(dev, x+width ,y+width);
377 }*/
378
379 // write a line-to command into the swf
380 static void linetoxy(gfxdevice_t*dev, TAG*tag, plotxy_t p0)
381 {
382     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
383     int px = (int)(p0.x*20);
384     int py = (int)(p0.y*20);
385     int rx = (px-i->swflastx);
386     int ry = (py-i->swflasty);
387     if(rx|ry) {
388         swf_ShapeSetLine (tag, i->shape, rx,ry);
389         addPointToBBox(dev, i->swflastx,i->swflasty);
390         addPointToBBox(dev, px,py);
391     } /* this is a nice idea, but doesn't work with current flash
392          players (the pixel will be invisible if they're not
393          precisely on a pixel boundary) 
394          Besides, we should only do this if this lineto itself
395          is again followed by a "move".
396          else if(!i->fill && i->config_dots) {
397        // treat lines of length 0 as plots, making them
398        // at least 1 twip wide so Flash will display them
399         //plot(dev, i->swflastx, i->swflasty, tag);
400         swf_ShapeSetLine (tag, i->shape, rx+1,ry);
401     }*/
402
403     i->shapeisempty = 0;
404     i->swflastx+=rx;
405     i->swflasty+=ry;
406 }
407 static void lineto(gfxdevice_t*dev, TAG*tag, double x, double y)
408 {
409     plotxy_t p;
410     p.x = x;
411     p.y = y;
412     linetoxy(dev,tag, p);
413 }
414
415 // write a spline-to command into the swf
416 static void splineto(gfxdevice_t*dev, TAG*tag, plotxy_t control,plotxy_t end)
417 {
418     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
419     int lastlastx = i->swflastx;
420     int lastlasty = i->swflasty;
421
422     int cx = ((int)(control.x*20)-i->swflastx);
423     int cy = ((int)(control.y*20)-i->swflasty);
424     i->swflastx += cx;
425     i->swflasty += cy;
426     int ex = ((int)(end.x*20)-i->swflastx);
427     int ey = ((int)(end.y*20)-i->swflasty);
428     i->swflastx += ex;
429     i->swflasty += ey;
430     
431     if((cx || cy) && (ex || ey)) {
432         swf_ShapeSetCurve(tag, i->shape, cx,cy,ex,ey);
433         addPointToBBox(dev, lastlastx   ,lastlasty   );
434         addPointToBBox(dev, lastlastx+cx,lastlasty+cy);
435         addPointToBBox(dev, lastlastx+cx+ex,lastlasty+cy+ey);
436     } else if(cx || cy || ex || ey) {
437         swf_ShapeSetLine(tag, i->shape, cx+ex,cy+ey);
438         addPointToBBox(dev, lastlastx   ,lastlasty   );
439         addPointToBBox(dev, lastlastx+cx,lastlasty+cy);
440         addPointToBBox(dev, lastlastx+cx+ex,lastlasty+cy+ey);
441     }
442
443     i->shapeisempty = 0;
444 }
445
446 /* write a line, given two points and the transformation
447    matrix. */
448 /*static void line(gfxdevice_t*dev, TAG*tag, plotxy_t p0, plotxy_t p1)
449 {
450     moveto(dev, tag, p0);
451     lineto(dev, tag, p1);
452 }*/
453
454 void resetdrawer(gfxdevice_t*dev)
455 {
456     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
457     i->swflastx = 0;
458     i->swflasty = 0;
459 }
460
461 static void stopFill(gfxdevice_t*dev)
462 {
463     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
464     if(i->lastwasfill!=0)
465     {
466         swf_ShapeSetStyle(i->tag,i->shape,i->linestyleid,0x8000,0);
467         i->fillstylechanged = 1;
468         i->lastwasfill = 0;
469     }
470 }
471 static void startFill(gfxdevice_t*dev)
472 {
473     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
474     if(i->lastwasfill!=1)
475     {
476         swf_ShapeSetStyle(i->tag,i->shape,0x8000,i->fillstyleid,0);
477         i->fillstylechanged = 1;
478         i->lastwasfill = 1;
479     }
480 }
481
482 static inline int colorcompare(gfxdevice_t*dev, RGBA*a,RGBA*b)
483 {
484
485     if(a->r!=b->r ||
486        a->g!=b->g ||
487        a->b!=b->b ||
488        a->a!=b->a) {
489         return 0;
490     }
491     return 1;
492 }
493
494 static SRECT getcharacterbbox(gfxdevice_t*dev, SWFFONT*font, MATRIX* m)
495 {
496     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
497     SRECT r;
498     char debug = 0;
499     memset(&r, 0, sizeof(r));
500
501     int t;
502     if(debug) printf("\n");
503     for(t=0;t<i->chardatapos;t++)
504     {
505         if(i->chardata[t].fontid != font->id) {
506             msg("<error> Internal error: fontid %d != fontid %d", i->chardata[t].fontid, font->id);
507             exit(1);
508         }
509         SRECT b = font->layout->bounds[i->chardata[t].charid];
510         b.xmin *= i->chardata[t].size;
511         b.ymin *= i->chardata[t].size;
512         b.xmax *= i->chardata[t].size;
513         b.ymax *= i->chardata[t].size;
514
515         /* divide by 1024, rounding xmax/ymax up */
516         b.xmax += 1023; b.ymax += 1023; b.xmin /= 1024; b.ymin /= 1024; b.xmax /= 1024; b.ymax /= 1024;
517
518         b.xmin += i->chardata[t].x;
519         b.ymin += i->chardata[t].y;
520         b.xmax += i->chardata[t].x;
521         b.ymax += i->chardata[t].y;
522
523         /* until we solve the INTERNAL_SCALING problem (see below)
524            make sure the bounding box is big enough */
525         b.xmin -= 20;
526         b.ymin -= 20;
527         b.xmax += 20;
528         b.ymax += 20;
529
530         b = swf_TurnRect(b, m);
531
532         if(debug) printf("(%f,%f,%f,%f) -> (%f,%f,%f,%f) [font %d/%d, char %d]\n",
533                 font->layout->bounds[i->chardata[t].charid].xmin/20.0,
534                 font->layout->bounds[i->chardata[t].charid].ymin/20.0,
535                 font->layout->bounds[i->chardata[t].charid].xmax/20.0,
536                 font->layout->bounds[i->chardata[t].charid].ymax/20.0,
537                 b.xmin/20.0,
538                 b.ymin/20.0,
539                 b.xmax/20.0,
540                 b.ymax/20.0,
541                 i->chardata[t].fontid,
542                 font->id,
543                 i->chardata[t].charid
544                 );
545         swf_ExpandRect2(&r, &b);
546     }
547     if(debug) printf("-----> (%f,%f,%f,%f)\n",
548             r.xmin/20.0,
549             r.ymin/20.0,
550             r.xmax/20.0,
551             r.ymax/20.0);
552     return r;
553 }
554
555 static void putcharacters(gfxdevice_t*dev, TAG*tag)
556 {
557     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
558     int t;
559     SWFFONT font;
560     RGBA color;
561     color.r = i->chardata[0].color.r^255;
562     color.g = 0;
563     color.b = 0;
564     color.a = 0;
565     int lastfontid;
566     int lastx;
567     int lasty;
568     int lastsize;
569     int charids[128];
570     int charadvance[128];
571     int charstorepos;
572     int pass;
573     int glyphbits=1; //TODO: can this be zero?
574     int advancebits=1;
575
576     if(tag->id != ST_DEFINETEXT &&
577         tag->id != ST_DEFINETEXT2) {
578         msg("<error> internal error: putcharacters needs an text tag, not %d\n",tag->id);
579         exit(1);
580     }
581     if(!i->chardatapos) {
582         msg("<warning> putcharacters called with zero characters");
583     }
584
585     for(pass = 0; pass < 2; pass++)
586     {
587         charstorepos = 0;
588         lastfontid = -1;
589         lastx = CHARMIDX;
590         lasty = CHARMIDY;
591         lastsize = -1;
592
593         if(pass==1)
594         {
595             advancebits++; // add sign bit
596             swf_SetU8(tag, glyphbits);
597             swf_SetU8(tag, advancebits);
598         }
599
600         for(t=0;t<=i->chardatapos;t++)
601         {
602             if(lastfontid != i->chardata[t].fontid || 
603                     lastx!=i->chardata[t].x ||
604                     lasty!=i->chardata[t].y ||
605                     !colorcompare(dev,&color, &i->chardata[t].color) ||
606                     charstorepos==127 ||
607                     lastsize != i->chardata[t].size ||
608                     t == i->chardatapos)
609             {
610                 if(charstorepos && pass==0)
611                 {
612                     int s;
613                     for(s=0;s<charstorepos;s++)
614                     {
615                         while(charids[s]>=(1<<glyphbits))
616                             glyphbits++;
617                         while(charadvance[s]>=(1<<advancebits))
618                             advancebits++;
619                     }
620                 }
621                 if(charstorepos && pass==1)
622                 {
623                     tag->writeBit = 0; // Q&D
624                     swf_SetBits(tag, 0, 1); // GLYPH Record
625                     swf_SetBits(tag, charstorepos, 7); // number of glyphs
626                     int s;
627                     for(s=0;s<charstorepos;s++)
628                     {
629                         swf_SetBits(tag, charids[s], glyphbits);
630                         swf_SetBits(tag, charadvance[s], advancebits);
631                     }
632                 }
633                 charstorepos = 0;
634
635                 if(pass == 1 && t<i->chardatapos)
636                 {
637                     RGBA*newcolor=0;
638                     SWFFONT*newfont=0;
639                     int newx = 0;
640                     int newy = 0;
641                     if(lastx != i->chardata[t].x ||
642                        lasty != i->chardata[t].y)
643                     {
644                         newx = i->chardata[t].x;
645                         newy = i->chardata[t].y;
646                         if(newx == 0)
647                             newx = SET_TO_ZERO;
648                         if(newy == 0)
649                             newy = SET_TO_ZERO;
650                     }
651                     if(!colorcompare(dev,&color, &i->chardata[t].color)) 
652                     {
653                         color = i->chardata[t].color;
654                         newcolor = &color;
655                     }
656                     font.id = i->chardata[t].fontid;
657                     if(lastfontid != i->chardata[t].fontid || lastsize != i->chardata[t].size)
658                         newfont = &font;
659
660                     tag->writeBit = 0; // Q&D
661                     swf_TextSetInfoRecord(tag, newfont, i->chardata[t].size, newcolor, newx,newy);
662                 }
663
664                 lastfontid = i->chardata[t].fontid;
665                 lastx = i->chardata[t].x;
666                 lasty = i->chardata[t].y;
667                 lastsize = i->chardata[t].size;
668             }
669
670             if(t==i->chardatapos)
671                     break;
672
673             int advance;
674             int nextt = t==i->chardatapos-1?t:t+1;
675             int rel = i->chardata[nextt].x-i->chardata[t].x;
676             if(rel>=0 && (rel<(1<<(advancebits-1)) || pass==0)) {
677                advance = rel;
678                lastx=i->chardata[nextt].x;
679             }
680             else {
681                advance = 0;
682                lastx=i->chardata[t].x;
683             }
684             charids[charstorepos] = i->chardata[t].charid;
685             charadvance[charstorepos] = advance;
686             charstorepos ++;
687         }
688     }
689     i->chardatapos = 0;
690 }
691
692 static void putcharacter(gfxdevice_t*dev, int fontid, int charid, int x,int y, int size, RGBA color)
693 {
694     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
695     if(i->chardatapos == CHARDATAMAX)
696     {
697         msg("<warning> Character buffer too small. SWF will be slightly bigger");
698         endtext(dev);
699         starttext(dev);
700     }
701     i->chardata[i->chardatapos].fontid = fontid;
702     i->chardata[i->chardatapos].charid = charid;
703     i->chardata[i->chardatapos].x = x;
704     i->chardata[i->chardatapos].y = y;
705     i->chardata[i->chardatapos].color = color;
706     i->chardata[i->chardatapos].size = size;
707     i->chardatapos++;
708 }
709
710 /* Notice: we can only put chars in the range -1639,1638 (-32768/20,32768/20).
711    So if we set this value to high, the char coordinates will overflow.
712    If we set it to low, however, the char positions will be inaccurate */
713 #define GLYPH_SCALE 1
714
715 static void endtext(gfxdevice_t*dev)
716 {
717     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
718     if(i->textid<0)
719         return;
720
721     i->tag = swf_InsertTag(i->tag,ST_DEFINETEXT2);
722     swf_SetU16(i->tag, i->textid);
723
724     SRECT r;
725     r = getcharacterbbox(dev, i->swffont, &i->fontmatrix);
726     r = swf_ClipRect(i->pagebbox, r);
727     swf_SetRect(i->tag,&r);
728
729     swf_SetMatrix(i->tag,&i->fontmatrix);
730
731     msg("<trace> Placing text (%d characters) as ID %d", i->chardatapos, i->textid);
732
733     putcharacters(dev, i->tag);
734     swf_SetU8(i->tag,0);
735
736     if(i->swf->fileVersion >= 8) {
737         i->tag = swf_InsertTag(i->tag, ST_CSMTEXTSETTINGS);
738         swf_SetU16(i->tag, i->textid);
739
740         //swf_SetU8(i->tag, /*subpixel grid*/(2<<3)|/*flashtype*/0x40);
741         swf_SetU8(i->tag, /*grid*/(1<<3)|/*flashtype*/0x40);
742         //swf_SetU8(i->tag, /*no grid*/(0<<3)|/*flashtype*/0x40);
743
744         swf_SetU32(i->tag, 0);//thickness
745         swf_SetU32(i->tag, 0);//sharpness
746         swf_SetU8(i->tag, 0);//reserved
747     }
748     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
749     
750     swf_ObjectPlace(i->tag,i->textid,getNewDepth(dev),&i->page_matrix,NULL,NULL);
751     i->textid = -1;
752 }
753
754 /* set's the matrix which is to be applied to characters drawn by swfoutput_drawchar() */
755 static void setfontscale(gfxdevice_t*dev,double m11,double m12, double m21,double m22,double x, double y, char force)
756 {
757     m11 *= 1024;
758     m12 *= 1024;
759     m21 *= 1024;
760     m22 *= 1024;
761     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
762     if(i->lastfontm11 == m11 &&
763        i->lastfontm12 == m12 &&
764        i->lastfontm21 == m21 &&
765        i->lastfontm22 == m22 && !force)
766         return;
767    if(i->textid>=0)
768         endtext(dev);
769     
770     i->lastfontm11 = m11;
771     i->lastfontm12 = m12;
772     i->lastfontm21 = m21;
773     i->lastfontm22 = m22;
774
775     double xsize = sqrt(m11*m11 + m12*m12);
776     double ysize = sqrt(m21*m21 + m22*m22);
777     i->current_font_size = (xsize>ysize?xsize:ysize)*1;
778     if(i->current_font_size < 1)
779         i->current_font_size = 1;
780     double ifs = 1.0 / (i->current_font_size*GLYPH_SCALE);
781
782     MATRIX m;
783     m.sx = (S32)((m11*ifs)*65536); m.r1 = (S32)((m21*ifs)*65536);
784     m.r0 = (S32)((m12*ifs)*65536); m.sy = (S32)((m22*ifs)*65536); 
785     /* this is the position of the first char to set a new fontmatrix-
786        we hope that it's close enough to all other characters using the
787        font, so we use its position as origin for the matrix */
788     m.tx = x*20;
789     m.ty = y*20;
790     i->fontmatrix = m;
791 }
792
793 static int watermark2_width=47;
794 static int watermark2_height=11;
795 static int watermark2[47] = {95,1989,71,0,2015,337,1678,0,2015,5,1921,320,1938,25,2006,1024,
796                              1042,21,13,960,1039,976,8,2000,1359,1088,31,1989,321,1728,0,1152,
797                              1344,832,0,1984,0,896,1088,1088,896,0,1984,128,256,512,1984};
798
799 static void draw_watermark(gfxdevice_t*dev, gfxbbox_t r, char drawall)
800 {
801     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
802     double wx = r.xmax / 5.0;
803     double tx = r.xmax*4.0 / 5.0;
804     double ty = r.ymax-wx*watermark2_height/watermark2_width;
805     double sx = (r.xmax - tx) / watermark2_width;
806     double sy = (r.ymax - ty) / watermark2_height;
807     double px = sx-0.5;
808     double py = sy-0.5;
809     if(ty > 0 && px > 1.0 && py > 1.0) {
810         int x,y;
811         for(y=0;y<watermark2_height;y++)
812         for(x=0;x<watermark2_width;x++) {
813             if(((watermark2[x]>>y)&1)) {
814                 if(!drawall && rand()%5)
815                     continue;
816                 unsigned int b = rand();
817                 moveto(dev, i->tag, x*sx+tx+((b>>1)&1)/20.0, y*sy+ty+((b>>3)&1)/20.0);
818                 lineto(dev, i->tag, x*sx+px+tx+((b>>2)&1)/20.0, y*sy+ty+((b>>3)&1)/20.0);
819                 lineto(dev, i->tag, x*sx+px+tx+((b>>2)&1)/20.0, y*sy+py+ty+((b>>4)&1)/20.0);
820                 lineto(dev, i->tag, x*sx+tx+((b>>1)&1)/20.0, y*sy+py+ty+((b>>4)&1)/20.0);
821                 lineto(dev, i->tag, x*sx+tx+((b>>1)&1)/20.0, y*sy+ty+((b>>3)&1)/20.0);
822             }
823         }
824     }
825 }
826
827 static void swfoutput_setfillcolor(gfxdevice_t* dev, U8 r, U8 g, U8 b, U8 a)
828 {
829     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
830     if(i->fillrgb.r == r &&
831        i->fillrgb.g == g &&
832        i->fillrgb.b == b &&
833        i->fillrgb.a == a) return;
834     if(i->shapeid>=0)
835      endshape(dev);
836
837     i->fillrgb.r = r;
838     i->fillrgb.g = g;
839     i->fillrgb.b = b;
840     i->fillrgb.a = a;
841 }
842 static void insert_watermark(gfxdevice_t*dev, char drawall)
843 {
844     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
845     if(!drawall && i->watermarks>20)
846         return;
847     endshape(dev);
848     endtext(dev);
849    
850     if(drawall) {
851         swfoutput_setfillcolor(dev, 0,0,255,192);
852     } else {
853         swfoutput_setfillcolor(dev, rand(),rand(),rand(),(rand()&127)+128);
854     }
855     startshape(dev);
856     startFill(dev);
857
858     gfxbbox_t r; r.xmin = r.ymin = 0;
859     r.xmax = i->max_x;
860     r.ymax = i->max_y;
861     draw_watermark(dev, r, drawall);
862     endshape(dev);
863     i->watermarks++;
864 }
865
866
867 static void endpage(gfxdevice_t*dev)
868 {
869     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
870     if(i->pagefinished)
871       return;
872     if(i->shapeid>=0)
873       endshape(dev);
874     if(i->textid>=0)
875       endtext(dev);
876     
877     while(i->clippos)
878         dev->endclip(dev);
879
880     if(i->config_watermark) {
881         insert_watermark(dev, 1);
882     }
883
884     i->pagefinished = 1;
885 }
886
887 static void addViewer(gfxdevice_t* dev)
888 {
889     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
890
891     SHAPE*s;
892     RGBA button_colors[3]= {{0xbf,0x00,0x00,0x80},{0xbf,0x20,0x20,0xc0}, {0xbf,0xc0,0xc0,0xff}};
893     int ids[6];
894     int button_sizex = 20;
895     int button_sizey = 20; 
896     int t;
897     RGBA black = {255,0,0,0};
898     for(t=0;t<6;t++) {
899         i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
900         swf_ShapeNew(&s);
901         int ls1 = swf_ShapeAddLineStyle(s,40,&black);
902         int fs1 = swf_ShapeAddSolidFillStyle(s,&button_colors[t/2]);
903         int shapeid = ids[t] = getNewID(dev);
904         swf_SetU16(i->tag,shapeid);
905         SRECT r;
906         r.xmin = -20*button_sizex;
907         r.xmax = 20*button_sizex; 
908         r.ymin = 0;
909         r.ymax = 40*button_sizey;
910         swf_SetRect(i->tag,&r);              // set shape bounds
911         swf_SetShapeHeader(i->tag,s);        // write all styles to tag
912         swf_ShapeSetAll(i->tag,s,0*button_sizex,0,ls1,fs1,0);
913         swf_ShapeSetLine(i->tag,s,(1-(t&1)*2)*20*button_sizex,20*button_sizey);
914         swf_ShapeSetLine(i->tag,s,-(1-(t&1)*2)*20*button_sizex,20*button_sizey);
915         swf_ShapeSetLine(i->tag,s,0,-40*button_sizey);
916         swf_ShapeSetEnd(i->tag);   // finish drawing
917         swf_ShapeFree(s);   // clean shape structure (which isn't needed anymore after writing the tag)
918     }
919     ActionTAG*a1=0,*a2=0,*a3=0;
920     a1 = action_NextFrame(a1);
921     a1 = action_Stop(a1);
922     a1 = action_End(a1);
923     
924     a2 = action_PreviousFrame(a2);
925     a2 = action_Stop(a2);
926     a2 = action_End(a2);
927     
928     a3 = action_Stop(a3);
929     a3 = action_End(a3);
930
931     i->tag = swf_InsertTag(i->tag, ST_DOACTION);
932     swf_ActionSet(i->tag,a3);
933
934     i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON);
935     int buttonid1 = getNewID(dev);
936     swf_SetU16(i->tag, buttonid1);
937     swf_ButtonSetRecord(i->tag,BS_UP|BS_HIT,ids[0],1,NULL,NULL);
938     swf_ButtonSetRecord(i->tag,BS_OVER,ids[2],1,NULL,NULL);
939     swf_ButtonSetRecord(i->tag,BS_DOWN,ids[4],1,NULL,NULL);
940     swf_SetU8(i->tag,0); // end of button records
941     swf_ActionSet(i->tag,a1);
942     
943     i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON);
944     int buttonid2 = getNewID(dev);
945     swf_SetU16(i->tag, buttonid2);
946     swf_ButtonSetRecord(i->tag,BS_UP|BS_HIT,ids[1],1,NULL,NULL);
947     swf_ButtonSetRecord(i->tag,BS_OVER,ids[3],1,NULL,NULL);
948     swf_ButtonSetRecord(i->tag,BS_DOWN,ids[5],1,NULL,NULL);
949     swf_SetU8(i->tag,0); // end of button records
950     swf_ActionSet(i->tag,a2);
951   
952     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
953     MATRIX m;
954     swf_GetMatrix(0, &m);
955     m.tx = button_sizex*20+200;
956     swf_ObjectPlace(i->tag, buttonid2, 65534,&m,0,0);
957     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
958     m.tx = button_sizex*20+200+200;
959     swf_ObjectPlace(i->tag, buttonid1, 65535,&m,0,0);
960 }
961
962
963 void swf_startframe(gfxdevice_t*dev, int width, int height)
964 {
965     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
966     if(i->firstpage) {
967         if(i->config_protect) {
968             i->tag = swf_InsertTag(i->tag, ST_PROTECT);
969             i->config_protect = 0;
970         }
971         if(i->config_simpleviewer) {
972             addViewer(dev);
973         }
974     }
975     
976     if(!i->firstpage && !i->pagefinished)
977         endpage(dev);
978
979     msg("<verbose> Starting new SWF page of size %dx%d", width, height);
980
981     swf_GetMatrix(0, &i->page_matrix);
982     i->page_matrix.tx = 0;
983     i->page_matrix.ty = 0;
984     i->min_x = 0;
985     i->min_y = 0;
986     i->max_x = width;
987     i->max_y = height;
988     i->watermarks = 0;
989
990     /* create a bbox structure with the page size. This is used
991        for clipping shape and text bounding boxes. As we don't want to
992        generate bounding boxes which extend beyond the movie size (in
993        order to not confuse Flash), we clip everything against i->pagebbox */
994     i->pagebbox.xmin = 0;
995     i->pagebbox.ymin = 0;
996     i->pagebbox.xmax = width*20;
997     i->pagebbox.ymax = height*20;
998
999     /* increase SWF's bounding box */
1000     swf_ExpandRect2(&i->swf->movieSize, &i->pagebbox);
1001
1002     i->lastframeno = i->frameno;
1003     i->firstpage = 0;
1004     i->pagefinished = 0;
1005 }
1006
1007 void swf_endframe(gfxdevice_t*dev)
1008 {
1009     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1010     
1011     if(!i->pagefinished)
1012         endpage(dev);
1013
1014     if( (i->swf->fileVersion <= 8) && (i->config_insertstoptag) ) {
1015         ActionTAG*atag=0;
1016         atag = action_Stop(atag);
1017         atag = action_End(atag);
1018         i->tag = swf_InsertTag(i->tag,ST_DOACTION);
1019         swf_ActionSet(i->tag,atag);
1020     }
1021     i->tag = swf_InsertTag(i->tag,ST_SHOWFRAME);
1022     i->frameno ++;
1023     
1024     for(i->depth;i->depth>i->startdepth;i->depth--) {
1025         i->tag = swf_InsertTag(i->tag,ST_REMOVEOBJECT2);
1026         swf_SetU16(i->tag,i->depth);
1027     }
1028     i->depth = i->startdepth;
1029
1030     if(i->config_frameresets) {
1031         for(i->currentswfid;i->currentswfid>i->startids;i->currentswfid--) {
1032             i->tag = swf_InsertTag(i->tag,ST_FREECHARACTER);
1033             swf_SetU16(i->tag,i->currentswfid);
1034         }
1035         i->currentswfid = i->startids;
1036     }
1037 }
1038
1039 static void setBackground(gfxdevice_t*dev, int x1, int y1, int x2, int y2)
1040 {
1041     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1042     RGBA rgb;
1043     rgb.a = rgb.r = rgb.g = rgb.b = 0xff;
1044     SRECT r;
1045     SHAPE* s;
1046     int ls1=0,fs1=0;
1047     int shapeid = getNewID(dev);
1048     r.xmin = x1;
1049     r.ymin = y1;
1050     r.xmax = x2;
1051     r.ymax = y2;
1052     i->tag = swf_InsertTag(i->tag, ST_DEFINESHAPE);
1053     swf_ShapeNew(&s);
1054     fs1 = swf_ShapeAddSolidFillStyle(s, &rgb);
1055     swf_SetU16(i->tag,shapeid);
1056     swf_SetRect(i->tag,&r);
1057     swf_SetShapeHeader(i->tag,s);
1058     swf_ShapeSetAll(i->tag,s,x1,y1,ls1,fs1,0);
1059     swf_ShapeSetLine(i->tag,s,(x2-x1),0);
1060     swf_ShapeSetLine(i->tag,s,0,(y2-y1));
1061     swf_ShapeSetLine(i->tag,s,(x1-x2),0);
1062     swf_ShapeSetLine(i->tag,s,0,(y1-y2));
1063     swf_ShapeSetEnd(i->tag);
1064     swf_ShapeFree(s);
1065     i->tag = swf_InsertTag(i->tag, ST_PLACEOBJECT2);
1066     swf_ObjectPlace(i->tag,shapeid,getNewDepth(dev),0,0,0);
1067     i->tag = swf_InsertTag(i->tag, ST_PLACEOBJECT2);
1068     swf_ObjectPlaceClip(i->tag,shapeid,getNewDepth(dev),0,0,0,65535);
1069 }
1070
1071 /* initialize the swf writer */
1072 void gfxdevice_swf_init(gfxdevice_t* dev)
1073 {
1074     memset(dev, 0, sizeof(gfxdevice_t));
1075     
1076     dev->name = "swf";
1077
1078     dev->internal = init_internal_struct(); // set config to default values
1079
1080     dev->startpage = swf_startframe;
1081     dev->endpage = swf_endframe;
1082     dev->finish = swf_finish;
1083     dev->fillbitmap = swf_fillbitmap;
1084     dev->setparameter = swf_setparameter;
1085     dev->stroke = swf_stroke;
1086     dev->startclip = swf_startclip;
1087     dev->endclip = swf_endclip;
1088     dev->fill = swf_fill;
1089     dev->fillbitmap = swf_fillbitmap;
1090     dev->fillgradient = swf_fillgradient;
1091     dev->addfont = swf_addfont;
1092     dev->drawchar = swf_drawchar;
1093     dev->drawlink = swf_drawlink;
1094
1095     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1096     i->dev = dev;
1097
1098     msg("<verbose> initializing swf output\n", i->max_x,i->max_y);
1099
1100     i->swffont = 0;
1101    
1102     i->swf = (SWF*)rfx_calloc(sizeof(SWF));
1103     i->swf->fileVersion    = 0;
1104     i->swf->frameRate      = 0x80;
1105     i->swf->movieSize.xmin = 0;
1106     i->swf->movieSize.ymin = 0;
1107     i->swf->movieSize.xmax = 0;
1108     i->swf->movieSize.ymax = 0;
1109     i->swf->fileAttributes = 9; // as3, local-with-network
1110     
1111     i->swf->firstTag = swf_InsertTag(NULL,ST_SETBACKGROUNDCOLOR);
1112     i->tag = i->swf->firstTag;
1113     RGBA rgb;
1114     rgb.a = rgb.r = rgb.g = rgb.b = 0xff;
1115     //rgb.r = 0;
1116     swf_SetRGB(i->tag,&rgb);
1117
1118     i->startdepth = i->depth = 0;
1119     i->startids = i->currentswfid = 0;
1120 }
1121
1122 static void startshape(gfxdevice_t*dev)
1123 {
1124     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1125     SRECT r;
1126
1127     if(i->shapeid>=0)
1128         return;
1129     //if(i->chardatapos && i->chardata[i->chardatapos-1].color.a)
1130     endtext(dev);
1131
1132     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1133
1134     swf_ShapeNew(&i->shape);
1135     i->linestyleid = swf_ShapeAddLineStyle(i->shape,i->linewidth,&i->strokergb);
1136     i->fillstyleid = swf_ShapeAddSolidFillStyle(i->shape,&i->fillrgb);
1137     if(i->mark) {
1138         RGBA markcol = {0,i->mark[0],i->mark[1],i->mark[2]};
1139         swf_ShapeAddSolidFillStyle(i->shape,&markcol);
1140     }
1141
1142     i->shapeid = getNewID(dev);
1143     
1144     msg("<debug> Using shape id %d", i->shapeid);
1145
1146     swf_SetU16(i->tag,i->shapeid);  // ID
1147
1148     i->bboxrectpos = i->tag->len;
1149     /* changed later */
1150     swf_SetRect(i->tag,&i->pagebbox);
1151    
1152     memset(&i->bboxrect, 0, sizeof(i->bboxrect));
1153
1154     swf_SetShapeStyles(i->tag,i->shape);
1155     swf_ShapeCountBits(i->shape,NULL,NULL);
1156     swf_SetShapeBits(i->tag,i->shape);
1157
1158     /* TODO: do we really need this? */
1159     //swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,i->linestyleid,0,0);
1160     //swf_ShapeSetAll(i->tag,i->shape,/*x*/UNDEFINED_COORD,/*y*/UNDEFINED_COORD,i->linestyleid,0,0);
1161     i->swflastx=i->swflasty=UNDEFINED_COORD;
1162     i->lastwasfill = -1;
1163     i->shapeisempty = 1;
1164 }
1165
1166 static void starttext(gfxdevice_t*dev)
1167 {
1168     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1169     if(i->shapeid>=0)
1170         endshape(dev);
1171
1172     if(i->config_watermark) {
1173         insert_watermark(dev, 0);
1174     }
1175     i->textid = getNewID(dev);
1176     i->swflastx=i->swflasty=0;
1177 }
1178             
1179
1180 /* TODO: move to ../lib/rfxswf */
1181 void changeRect(gfxdevice_t*dev, TAG*tag, int pos, SRECT*newrect)
1182 {
1183     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1184     /* determine length of old rect */
1185     tag->pos = pos;
1186     tag->readBit = 0;
1187     SRECT old;
1188     swf_GetRect(tag, &old);
1189     swf_ResetReadBits(tag);
1190     int pos_end = tag->pos;
1191
1192     int len = tag->len - pos_end;
1193     U8*data = (U8*)malloc(len);
1194     memcpy(data, &tag->data[pos_end], len);
1195     tag->writeBit = 0;
1196     tag->len = pos;
1197     swf_SetRect(tag, newrect);
1198     swf_SetBlock(tag, data, len);
1199     free(data);
1200     tag->pos = tag->readBit = 0;
1201 }
1202
1203 void cancelshape(gfxdevice_t*dev)
1204 {
1205     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1206     /* delete old shape tag */
1207     TAG*todel = i->tag;
1208     i->tag = i->tag->prev;
1209     swf_DeleteTag(0, todel);
1210     if(i->shape) {swf_ShapeFree(i->shape);i->shape=0;}
1211     i->shapeid = -1;
1212     i->bboxrectpos = -1;
1213
1214 //    i->currentswfid--; // doesn't work, for some reason
1215 }
1216
1217 void fixAreas(gfxdevice_t*dev)
1218 {
1219     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1220     if(!i->shapeisempty && i->fill &&
1221        (i->bboxrect.xmin == i->bboxrect.xmax ||
1222         i->bboxrect.ymin == i->bboxrect.ymax) &&
1223         i->config_minlinewidth >= 0.001
1224        ) {
1225         msg("<debug> Shape has size 0: width=%.2f height=%.2f",
1226                 (i->bboxrect.xmax-i->bboxrect.xmin)/20.0,
1227                 (i->bboxrect.ymax-i->bboxrect.ymin)/20.0
1228                 );
1229     
1230         SRECT r = i->bboxrect;
1231         
1232         if(r.xmin == r.xmax && r.ymin == r.ymax) {
1233             /* this thing comes down to a single dot- nothing to fix here */
1234             return;
1235         }
1236
1237         cancelshape(dev);
1238
1239         RGBA save_col = i->strokergb;
1240         int  save_width = i->linewidth;
1241
1242         i->strokergb = i->fillrgb;
1243         i->linewidth = (int)(i->config_minlinewidth*20);
1244         if(i->linewidth==0) i->linewidth = 1;
1245         
1246         startshape(dev);
1247         stopFill(dev);
1248
1249         moveto(dev, i->tag, r.xmin/20.0,r.ymin/20.0);
1250         lineto(dev, i->tag, r.xmax/20.0,r.ymax/20.0);
1251
1252         i->strokergb = save_col;
1253         i->linewidth = save_width;
1254     }
1255     
1256 }
1257
1258 static void endshape_noput(gfxdevice_t*dev)
1259 {
1260     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1261     if(i->shapeid<0) 
1262         return;
1263     //changeRect(dev, i->tag, i->bboxrectpos, &i->bboxrect);
1264     i->shapeid = -1;
1265     if(i->shape) {
1266         swf_ShapeFree(i->shape);
1267         i->shape=0;
1268     }
1269     i->fill=0;
1270     i->shapeposx=0;
1271     i->shapeposy=0;
1272 }
1273
1274 static void endshape(gfxdevice_t*dev)
1275 {
1276     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1277     if(i->shapeid<0) 
1278         return;
1279
1280     fixAreas(dev);
1281         
1282     if(i->shapeisempty ||
1283        /*bbox empty?*/
1284        (i->bboxrect.xmin == i->bboxrect.xmax && 
1285         i->bboxrect.ymin == i->bboxrect.ymax))
1286     {
1287         // delete the shape again, we didn't do anything
1288         msg("<debug> cancelling shape: bbox is (%f,%f,%f,%f)",
1289                 i->bboxrect.xmin /20.0,
1290                 i->bboxrect.ymin /20.0,
1291                 i->bboxrect.xmax /20.0,
1292                 i->bboxrect.ymax /20.0
1293                 );
1294         cancelshape(dev);
1295         return;
1296     }
1297     
1298     swf_ShapeSetEnd(i->tag);
1299
1300     SRECT r = swf_ClipRect(i->pagebbox, i->bboxrect);
1301     changeRect(dev, i->tag, i->bboxrectpos, &r);
1302
1303     msg("<trace> Placing shape ID %d", i->shapeid);
1304
1305     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
1306     MATRIX m = i->page_matrix;
1307     m.tx += i->shapeposx;
1308     m.ty += i->shapeposy;
1309     swf_ObjectPlace(i->tag,i->shapeid,getNewDepth(dev),&m,NULL,NULL);
1310
1311     if(i->config_animate) {
1312         i->tag = swf_InsertTag(i->tag,ST_SHOWFRAME);
1313     }
1314
1315     swf_ShapeFree(i->shape);
1316     i->shape = 0;
1317     i->shapeid = -1;
1318     i->bboxrectpos = -1;
1319
1320     i->fill=0;
1321     i->shapeposx=0;
1322     i->shapeposy=0;
1323 }
1324
1325 void wipeSWF(SWF*swf)
1326 {
1327     TAG*tag = swf->firstTag;
1328     while(tag) {
1329         TAG*next = tag->next;
1330         if(tag->id != ST_SETBACKGROUNDCOLOR &&
1331            tag->id != ST_END &&
1332            tag->id != ST_DOACTION &&
1333            tag->id != ST_SHOWFRAME) {
1334             swf_DeleteTag(swf, tag);
1335         }
1336         tag = next;
1337     }
1338 }
1339
1340 void swfoutput_finalize(gfxdevice_t*dev)
1341 {
1342     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1343
1344     if(i->tag && i->tag->id == ST_END)
1345         return; //already done
1346
1347     i->swf->fileVersion = i->config_flashversion;
1348     i->swf->frameRate = i->config_framerate*0x100;
1349
1350     if(i->config_bboxvars) {
1351         TAG* tag = swf_InsertTag(i->swf->firstTag, ST_DOACTION);
1352         ActionTAG*a = 0;
1353         a = action_PushString(a, "xmin");
1354         a = action_PushFloat(a, i->swf->movieSize.xmin / 20.0);
1355         a = action_SetVariable(a);
1356         a = action_PushString(a, "ymin");
1357         a = action_PushFloat(a, i->swf->movieSize.ymin / 20.0);
1358         a = action_SetVariable(a);
1359         a = action_PushString(a, "xmax");
1360         a = action_PushFloat(a, i->swf->movieSize.xmax / 20.0);
1361         a = action_SetVariable(a);
1362         a = action_PushString(a, "ymax");
1363         a = action_PushFloat(a, i->swf->movieSize.ymax / 20.0);
1364         a = action_SetVariable(a);
1365         a = action_PushString(a, "width");
1366         a = action_PushFloat(a, (i->swf->movieSize.xmax - i->swf->movieSize.xmin) / 20.0);
1367         a = action_SetVariable(a);
1368         a = action_PushString(a, "height");
1369         a = action_PushFloat(a, (i->swf->movieSize.ymax - i->swf->movieSize.ymin) / 20.0);
1370         a = action_SetVariable(a);
1371         a = action_End(a);
1372         swf_ActionSet(tag, a);
1373         swf_ActionFree(a);
1374     }
1375
1376     if(i->mark) {
1377         free(i->mark);i->mark = 0;
1378     }
1379
1380     endpage(dev);
1381     fontlist_t *iterator = i->fontlist;
1382     while(iterator) {
1383         TAG*mtag = i->swf->firstTag;
1384         if(iterator->swffont) {
1385             if(!i->config_storeallcharacters) {
1386                 msg("<debug> Reducing font %s", iterator->swffont->name);
1387                 swf_FontReduce(iterator->swffont);
1388             }
1389             int used = iterator->swffont->use && iterator->swffont->use->used_glyphs;
1390             if(used) {
1391                 mtag = swf_InsertTag(mtag, ST_DEFINEFONT2);
1392                 swf_FontSetDefine2(mtag, iterator->swffont);
1393             }
1394         }
1395
1396         iterator = iterator->next;
1397     }
1398         
1399     i->tag = swf_InsertTag(i->tag,ST_END);
1400     TAG* tag = i->tag->prev;
1401
1402     /* remove the removeobject2 tags between the last ST_SHOWFRAME
1403        and the ST_END- they confuse the flash player  */
1404     while(tag->id == ST_REMOVEOBJECT2) {
1405         TAG* prev = tag->prev;
1406         swf_DeleteTag(i->swf, tag);
1407         tag = prev;
1408     }
1409     
1410     if(i->overflow) {
1411         wipeSWF(i->swf);
1412     }
1413     if(i->config_enablezlib || i->config_flashversion>=6) {
1414         i->swf->compressed = 1;
1415     }
1416
1417     /* Add AVM2 actionscript */
1418     if(i->config_flashversion>=9 && 
1419             (i->config_insertstoptag || i->hasbuttons)) {
1420         swf_AddButtonLinks(i->swf, i->config_insertstoptag, 
1421                 i->config_internallinkfunction||i->config_externallinkfunction);
1422     }
1423 //    if(i->config_reordertags)
1424 //      swf_Optimize(i->swf);
1425 }
1426
1427 int swfresult_save(gfxresult_t*gfx, const char*filename)
1428 {
1429     SWF*swf = (SWF*)gfx->internal;
1430     int fi;
1431     if(filename)
1432      fi = open(filename, O_BINARY|O_CREAT|O_TRUNC|O_WRONLY, 0777);
1433     else
1434      fi = 1; // stdout
1435     
1436     if(fi<=0) {
1437         msg("<fatal> Could not create \"%s\". ", FIXNULL(filename));
1438         return -1;
1439     }
1440     
1441     if FAILED(swf_WriteSWF(fi,swf)) 
1442         msg("<error> WriteSWF() failed.\n");
1443
1444     if(filename)
1445      close(fi);
1446     return 0;
1447 }
1448 void* swfresult_get(gfxresult_t*gfx, const char*name)
1449 {
1450     SWF*swf = (SWF*)gfx->internal;
1451     if(!strcmp(name, "swf")) {
1452         return (void*)swf_CopySWF(swf);
1453     } else if(!strcmp(name, "xmin")) {
1454         return (void*)(swf->movieSize.xmin/20);
1455     } else if(!strcmp(name, "ymin")) {
1456         return (void*)(swf->movieSize.ymin/20);
1457     } else if(!strcmp(name, "xmax")) {
1458         return (void*)(swf->movieSize.xmax/20);
1459     } else if(!strcmp(name, "ymax")) {
1460         return (void*)(swf->movieSize.ymax/20);
1461     } else if(!strcmp(name, "width")) {
1462         return (void*)((swf->movieSize.xmax - swf->movieSize.xmin)/20);
1463     } else if(!strcmp(name, "height")) {
1464         return (void*)((swf->movieSize.ymax - swf->movieSize.ymin)/20);
1465     }
1466     return 0;
1467 }
1468 void swfresult_destroy(gfxresult_t*gfx)
1469 {
1470     if(gfx->internal) {
1471         swf_FreeTags((SWF*)gfx->internal);
1472         free(gfx->internal);
1473         gfx->internal = 0;
1474     }
1475     memset(gfx, 0, sizeof(gfxresult_t));
1476     free(gfx);
1477 }
1478
1479 static void swfoutput_destroy(gfxdevice_t* dev);
1480
1481 gfxresult_t* swf_finish(gfxdevice_t* dev)
1482 {
1483     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1484     gfxresult_t*result;
1485
1486     if(i->config_linktarget) {
1487         free(i->config_linktarget);
1488         i->config_linktarget = 0;
1489     }
1490
1491     swfoutput_finalize(dev);
1492     SWF* swf = i->swf;i->swf = 0;
1493     swfoutput_destroy(dev);
1494
1495     result = (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
1496     result->internal = swf;
1497     result->save = swfresult_save;
1498     result->write = 0;
1499     result->get = swfresult_get;
1500     result->destroy = swfresult_destroy;
1501     return result;
1502 }
1503
1504 /* Perform cleaning up */
1505 static void swfoutput_destroy(gfxdevice_t* dev) 
1506 {
1507     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1508     if(!i) {
1509         /* not initialized yet- nothing to destroy */
1510         return;
1511     }
1512
1513     fontlist_t *tmp,*iterator = i->fontlist;
1514     while(iterator) {
1515         if(iterator->swffont) {
1516             swf_FontFree(iterator->swffont);iterator->swffont=0;
1517         }
1518         tmp = iterator;
1519         iterator = iterator->next;
1520         free(tmp);
1521     }
1522     if(i->swf) {swf_FreeTags(i->swf);free(i->swf);i->swf = 0;}
1523
1524     free(i);i=0;
1525     memset(dev, 0, sizeof(gfxdevice_t));
1526 }
1527
1528 static void swfoutput_setstrokecolor(gfxdevice_t* dev, U8 r, U8 g, U8 b, U8 a)
1529 {
1530     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1531     if(i->strokergb.r == r &&
1532        i->strokergb.g == g &&
1533        i->strokergb.b == b &&
1534        i->strokergb.a == a) return;
1535
1536     if(i->shapeid>=0)
1537      endshape(dev);
1538     i->strokergb.r = r;
1539     i->strokergb.g = g;
1540     i->strokergb.b = b;
1541     i->strokergb.a = a;
1542 }
1543
1544 //#define ROUND_UP 19
1545 //#define ROUND_UP 10
1546
1547 static void swfoutput_setlinewidth(gfxdevice_t*dev, double _linewidth)
1548 {
1549     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1550     if(i->linewidth == (U16)(_linewidth*20+19.0/20.0))
1551         return;
1552     if(i->shapeid>=0)
1553         endshape(dev);
1554     i->linewidth = (U16)(_linewidth*20+19.0/20.0);
1555 }
1556
1557
1558 static void drawlink(gfxdevice_t*dev, ActionTAG*,ActionTAG*, gfxline_t*points, char mouseover, const char*url);
1559 static void swfoutput_namedlink(gfxdevice_t*dev, char*name, gfxline_t*points);
1560 static void swfoutput_linktopage(gfxdevice_t*dev, int page, gfxline_t*points);
1561 static void swfoutput_linktourl(gfxdevice_t*dev, const char*url, gfxline_t*points);
1562
1563 /*void swfoutput_drawlink(gfxdevice_t*dev, char*url, gfxline_t*points)
1564 {
1565     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1566     dev->drawlink(dev, points, url);
1567 }*/
1568
1569 void swf_drawlink(gfxdevice_t*dev, gfxline_t*points, const char*url)
1570 {
1571     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1572
1573     if(i->config_disablelinks)
1574         return;
1575
1576     if(!strncmp("http://pdf2swf:", url, 15)) {
1577         char*tmp = strdup(url);
1578         int l = strlen(tmp);
1579         if(tmp[l-1] == '/')
1580            tmp[l-1] = 0;
1581         swfoutput_namedlink(dev, tmp+15, points);
1582         free(tmp);
1583         return;
1584     } else if(!strncmp("page", url, 4)) {
1585         int t, nodigit=0;
1586         for(t=4;url[t];t++)
1587             if(url[t]<'0' || url[t]>'9')
1588                 nodigit = 1;
1589         if(!nodigit) {
1590             int page = atoi(&url[4]);
1591             if(page<0) page = 0;
1592             swfoutput_linktopage(dev, page, points);
1593         }
1594     } else {
1595         swfoutput_linktourl(dev, url, points);
1596     }
1597 }
1598 void swfoutput_linktourl(gfxdevice_t*dev, const char*url, gfxline_t*points)
1599 {
1600     ActionTAG* actions = 0;
1601     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1602     if(i->shapeid>=0)
1603         endshape(dev);
1604     if(i->textid>=0)
1605         endtext(dev);
1606
1607     /* TODO: escape special characters in url */
1608     
1609     if(i->config_externallinkfunction && i->config_flashversion<=8) {
1610         actions = action_PushString(actions, url); //parameter
1611         actions = action_PushInt(actions, 1); //number of parameters (1)
1612         actions = action_PushString(actions, i->config_externallinkfunction); //function name
1613         actions = action_CallFunction(actions);
1614     } else if(!i->config_linktarget) {
1615         if(!i->config_opennewwindow)
1616           actions = action_GetUrl(actions, url, "_parent");
1617         else
1618           actions = action_GetUrl(actions, url, "_this");
1619     } else {
1620         actions = action_GetUrl(actions, url, i->config_linktarget);
1621     }
1622     actions = action_End(actions);
1623    
1624     drawlink(dev, actions, 0, points, 0, url);
1625 }
1626 void swfoutput_linktopage(gfxdevice_t*dev, int page, gfxline_t*points)
1627 {
1628     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1629     ActionTAG* actions = 0;
1630
1631     if(i->shapeid>=0)
1632         endshape(dev);
1633     if(i->textid>=0)
1634         endtext(dev);
1635   
1636     if(!i->config_internallinkfunction || i->config_flashversion>=9) {
1637         actions = action_GotoFrame(actions, page-1);
1638         actions = action_End(actions);
1639     } else {
1640         actions = action_PushInt(actions, page); //parameter
1641         actions = action_PushInt(actions, 1); //number of parameters (1)
1642         actions = action_PushString(actions, i->config_internallinkfunction); //function name
1643         actions = action_CallFunction(actions);
1644         actions = action_End(actions);
1645     }
1646
1647     char name[80];
1648     sprintf(name, "page%d", page);
1649
1650     drawlink(dev, actions, 0, points, 0, name);
1651 }
1652
1653 /* Named Links (a.k.a. Acrobatmenu) are used to implement various gadgets
1654    of the viewer objects, like subtitles, index elements etc.
1655 */
1656 void swfoutput_namedlink(gfxdevice_t*dev, char*name, gfxline_t*points)
1657 {
1658     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1659     ActionTAG *actions1,*actions2;
1660     char*tmp = strdup(name);
1661     char mouseover = 1;
1662
1663     if(i->shapeid>=0)
1664         endshape(dev);
1665     if(i->textid>=0)
1666         endtext(dev);
1667
1668     if(!strncmp(tmp, "call:", 5))
1669     {
1670         char*x = strchr(&tmp[5], ':');
1671         if(!x) {
1672             actions1 = action_PushInt(0, 0); //number of parameters (0)
1673             actions1 = action_PushString(actions1, &tmp[5]); //function name
1674             actions1 = action_CallFunction(actions1);
1675             actions1 = action_End(actions1);
1676         } else {
1677             *x = 0;
1678             actions1 = action_PushString(0, x+1); //parameter
1679             actions1 = action_PushInt(actions1, 1); //number of parameters (1)
1680             actions1 = action_PushString(actions1, &tmp[5]); //function name
1681             actions1 = action_CallFunction(actions1);
1682             actions1 = action_End(actions1);
1683         }
1684         actions2 = action_End(0);
1685         mouseover = 0;
1686     }
1687     else
1688     {
1689         actions1 = action_PushString(0, "/:subtitle");
1690         actions1 = action_PushString(actions1, name);
1691         actions1 = action_SetVariable(actions1);
1692         actions1 = action_End(actions1);
1693
1694         actions2 = action_PushString(0, "/:subtitle");
1695         actions2 = action_PushString(actions2, "");
1696         actions2 = action_SetVariable(actions2);
1697         actions2 = action_End(actions2);
1698     }
1699
1700     drawlink(dev, actions1, actions2, points, mouseover, name);
1701
1702     swf_ActionFree(actions1);
1703     swf_ActionFree(actions2);
1704     free(tmp);
1705 }
1706
1707 static void drawgfxline(gfxdevice_t*dev, gfxline_t*line, int fill)
1708 {
1709     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1710     gfxcoord_t lastx=0,lasty=0,px=0,py=0;
1711     char lastwasmoveto;
1712     int lines= 0, splines=0;
1713
1714     i->fill = fill;
1715
1716     while(1) {
1717         if(!line)
1718             break;
1719         /* check whether the next segment is zero */
1720         if(line->type == gfx_moveTo) {
1721             moveto(dev, i->tag, line->x, line->y);
1722             px = lastx = line->x;
1723             py = lasty = line->y;
1724             lastwasmoveto = 1;
1725         } if(line->type == gfx_lineTo) {
1726             lineto(dev, i->tag, line->x, line->y);
1727             px = line->x;
1728             py = line->y;
1729             lastwasmoveto = 0;
1730             lines++;
1731         } else if(line->type == gfx_splineTo) {
1732             plotxy_t s,p;
1733             s.x = line->sx;p.x = line->x;
1734             s.y = line->sy;p.y = line->y;
1735             splineto(dev, i->tag, s, p);
1736             px = line->x;
1737             py = line->y;
1738             lastwasmoveto = 0;
1739             splines++;
1740         }
1741         line = line->next;
1742     }
1743     msg("<trace> drawgfxline, %d lines, %d splines", lines, splines);
1744 }
1745
1746
1747 static void drawlink(gfxdevice_t*dev, ActionTAG*actions1, ActionTAG*actions2, gfxline_t*points, char mouseover, const char*url)
1748 {
1749     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1750     RGBA rgb;
1751     SRECT r;
1752     int lsid=0;
1753     int fsid;
1754     int myshapeid;
1755     int myshapeid2;
1756     double posx = 0;
1757     double posy = 0;
1758     int buttonid = getNewID(dev);
1759     gfxbbox_t bbox = gfxline_getbbox(points);
1760     
1761     i->hasbuttons = 1;
1762
1763     /* shape */
1764     myshapeid = getNewID(dev);
1765     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1766     swf_ShapeNew(&i->shape);
1767     rgb.r = rgb.b = rgb.a = rgb.g = 0; 
1768     fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
1769     swf_SetU16(i->tag, myshapeid);
1770     r.xmin = (int)(bbox.xmin*20);
1771     r.ymin = (int)(bbox.ymin*20);
1772     r.xmax = (int)(bbox.xmax*20);
1773     r.ymax = (int)(bbox.ymax*20);
1774     r = swf_ClipRect(i->pagebbox, r);
1775     swf_SetRect(i->tag,&r);
1776     swf_SetShapeStyles(i->tag,i->shape);
1777     swf_ShapeCountBits(i->shape,NULL,NULL);
1778     swf_SetShapeBits(i->tag,i->shape);
1779     swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
1780     i->swflastx = i->swflasty = 0;
1781     drawgfxline(dev, points, 1);
1782     swf_ShapeSetEnd(i->tag);
1783     swf_ShapeFree(i->shape);
1784
1785     /* shape2 */
1786     myshapeid2 = getNewID(dev);
1787     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1788     swf_ShapeNew(&i->shape);
1789     
1790     rgb = i->config_linkcolor;
1791
1792     fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
1793     swf_SetU16(i->tag, myshapeid2);
1794     r.xmin = (int)(bbox.xmin*20);
1795     r.ymin = (int)(bbox.ymin*20);
1796     r.xmax = (int)(bbox.xmax*20);
1797     r.ymax = (int)(bbox.ymax*20);
1798     r = swf_ClipRect(i->pagebbox, r);
1799     swf_SetRect(i->tag,&r);
1800     swf_SetShapeStyles(i->tag,i->shape);
1801     swf_ShapeCountBits(i->shape,NULL,NULL);
1802     swf_SetShapeBits(i->tag,i->shape);
1803     swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
1804     i->swflastx = i->swflasty = 0;
1805     drawgfxline(dev, points, 1);
1806     swf_ShapeSetEnd(i->tag);
1807     swf_ShapeFree(i->shape);
1808
1809     if(!mouseover)
1810     {
1811         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON);
1812         swf_SetU16(i->tag,buttonid); //id
1813         swf_ButtonSetFlags(i->tag, 0); //menu=no
1814         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1815         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1816         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1817         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1818         swf_SetU8(i->tag,0);
1819         swf_ActionSet(i->tag,actions1);
1820         swf_SetU8(i->tag,0);
1821     }
1822     else
1823     {
1824         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON2);
1825         swf_SetU16(i->tag,buttonid); //id
1826         swf_ButtonSetFlags(i->tag, 0); //menu=no
1827         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1828         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1829         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1830         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1831         swf_SetU8(i->tag,0); // end of button records
1832         swf_ButtonSetCondition(i->tag, BC_IDLE_OVERUP);
1833         swf_ActionSet(i->tag,actions1);
1834         if(actions2) {
1835             swf_ButtonSetCondition(i->tag, BC_OVERUP_IDLE);
1836             swf_ActionSet(i->tag,actions2);
1837             swf_SetU8(i->tag,0);
1838             swf_ButtonPostProcess(i->tag, 2);
1839         } else {
1840             swf_SetU8(i->tag,0);
1841             swf_ButtonPostProcess(i->tag, 1);
1842         }
1843     }
1844     char buf[80];
1845     const char* name = 0;
1846     if(i->config_linknameurl) {
1847         name = url;
1848     } else {
1849         name = buf;
1850         sprintf(buf, "button%d", buttonid);
1851     }
1852     
1853     msg("<trace> Placing link ID %d", buttonid);
1854     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
1855
1856     if(posx!=0 || posy!=0) {
1857         SPOINT p;
1858         p.x = (int)(posx*20);
1859         p.y = (int)(posy*20);
1860         p = swf_TurnPoint(p, &i->page_matrix);
1861         MATRIX m;
1862         m = i->page_matrix;
1863         m.tx = p.x;
1864         m.ty = p.y;
1865         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&m,0,name);
1866     } else {
1867         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&i->page_matrix,0,name);
1868     }
1869 }
1870
1871       
1872 ///////////
1873 /*
1874 for(t=0;t<picpos;t++)
1875       {
1876           if(pic_xids[t] == xid &&
1877              pic_yids[t] == yid) {
1878               width = pic_width[t];
1879               height = pic_height[t];
1880               found = t;break;
1881           }
1882       }
1883           pic_ids[picpos] = swfoutput_drawimagelosslessN(&output, pic, pal, width, height, x1,y1,x2,y2,x3,y3,x4,y4, numpalette);
1884           pic_xids[picpos] = xid;
1885           pic_yids[picpos] = yid;
1886           pic_width[picpos] = width;
1887           pic_height[picpos] = height;
1888           if(picpos<1024)
1889               picpos++;
1890             pic[width*y+x] = buf[0];
1891             xid+=x*buf[0]+1;
1892             yid+=y*buf[0]*3+1;
1893       
1894             xid += pal[1].r*3 + pal[1].g*11 + pal[1].b*17;
1895       yid += pal[1].r*7 + pal[1].g*5 + pal[1].b*23;
1896       
1897       int xid = 0;
1898       int yid = 0;
1899           xid += x*r+x*b*3+x*g*7+x*a*11;
1900           yid += y*r*3+y*b*17+y*g*19+y*a*11;
1901       int t,found = -1;
1902       for(t=0;t<picpos;t++)
1903       {
1904           if(pic_xids[t] == xid &&
1905              pic_yids[t] == yid) {
1906               found = t;break;
1907           }
1908       }
1909       if(found<0) {
1910 */
1911 ///////////
1912
1913
1914 int swf_setparameter(gfxdevice_t*dev, const char*name, const char*value)
1915 {
1916     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1917
1918     msg("<trace> swfdevice: %s=%s", name, value);
1919     if(!strcmp(name, "jpegsubpixels")) {
1920         i->config_jpegsubpixels = atof(value);
1921     } else if(!strcmp(name, "ppmsubpixels")) {
1922         i->config_ppmsubpixels = atof(value);
1923     } else if(!strcmp(name, "subpixels")) {
1924         i->config_ppmsubpixels = i->config_jpegsubpixels = atof(value);
1925     } else if(!strcmp(name, "drawonlyshapes")) {
1926         i->config_drawonlyshapes = atoi(value);
1927     } else if(!strcmp(name, "ignoredraworder")) {
1928         i->config_ignoredraworder = atoi(value);
1929     } else if(!strcmp(name, "mark")) {
1930         if(!value || !value[0]) {
1931             if(i->mark) free(i->mark);
1932             i->mark = 0;
1933         } else {
1934             int t;
1935             i->mark = strdup("...");
1936             for(t=0;t<3;t++) if(value[t]) i->mark[t] = value[t];
1937         }
1938     } else if(!strcmp(name, "filloverlap")) {
1939         i->config_filloverlap = atoi(value);
1940     } else if(!strcmp(name, "linksopennewwindow")) {
1941         i->config_opennewwindow = atoi(value);
1942     } else if(!strcmp(name, "opennewwindow")) {
1943         i->config_opennewwindow = atoi(value);
1944     } else if(!strcmp(name, "storeallcharacters")) {
1945         i->config_storeallcharacters = atoi(value);
1946     } else if(!strcmp(name, "enablezlib")) {
1947         i->config_enablezlib = atoi(value);
1948     } else if(!strcmp(name, "bboxvars")) {
1949         i->config_bboxvars = atoi(value);
1950     } else if(!strcmp(name, "dots")) {
1951         i->config_dots = atoi(value);
1952     } else if(!strcmp(name, "frameresets")) {
1953         i->config_frameresets = atoi(value);
1954     } else if(!strcmp(name, "showclipshapes")) {
1955         i->config_showclipshapes = atoi(value);
1956     } else if(!strcmp(name, "reordertags")) {
1957         i->config_reordertags = atoi(value);
1958     } else if(!strcmp(name, "internallinkfunction")) {
1959         i->config_internallinkfunction = strdup(value);
1960     } else if(!strcmp(name, "externallinkfunction")) {
1961         i->config_externallinkfunction = strdup(value);
1962     } else if(!strcmp(name, "linkfunction")) { //sets both internallinkfunction and externallinkfunction
1963         i->config_internallinkfunction = strdup(value);
1964         i->config_externallinkfunction = strdup(value);
1965     } else if(!strcmp(name, "disable_polygon_conversion")) {
1966         i->config_disable_polygon_conversion = atoi(value);
1967     } else if(!strcmp(name, "normalize_polygon_positions")) {
1968         i->config_normalize_polygon_positions = atoi(value);
1969     } else if(!strcmp(name, "wxwindowparams")) {
1970         i->config_watermark = atoi(value);
1971     } else if(!strcmp(name, "insertstop")) {
1972         i->config_insertstoptag = atoi(value);
1973     } else if(!strcmp(name, "protect")) {
1974         i->config_protect = atoi(value);
1975         if(i->config_protect && i->tag) {
1976             i->tag = swf_InsertTag(i->tag, ST_PROTECT);
1977         }
1978     } else if(!strcmp(name, "flashversion")) {
1979         i->config_flashversion = atoi(value);
1980         if(i->swf) {
1981             i->swf->fileVersion = i->config_flashversion;
1982         }
1983     } else if(!strcmp(name, "framerate")) {
1984         i->config_framerate = atof(value);
1985         if(i->swf) {
1986             i->swf->frameRate = i->config_framerate*0x100;
1987         }
1988     } else if(!strcmp(name, "minlinewidth")) {
1989         i->config_minlinewidth = atof(value);
1990     } else if(!strcmp(name, "caplinewidth")) {
1991         i->config_caplinewidth = atof(value);
1992     } else if(!strcmp(name, "linktarget")) {
1993         i->config_linktarget = strdup(value);
1994     } else if(!strcmp(name, "dumpfonts")) {
1995         i->config_dumpfonts = atoi(value);
1996     } else if(!strcmp(name, "animate")) {
1997         i->config_animate = atoi(value);
1998     } else if(!strcmp(name, "disablelinks")) {
1999         i->config_disablelinks = atoi(value);
2000     } else if(!strcmp(name, "simpleviewer")) {
2001         i->config_simpleviewer = atoi(value);
2002     } else if(!strcmp(name, "next_bitmap_is_jpeg")) {
2003         i->jpeg = 1;
2004     } else if(!strcmp(name, "jpegquality")) {
2005         int val = atoi(value);
2006         if(val<0) val=0;
2007         if(val>101) val=101;
2008         i->config_jpegquality = val;
2009     } else if(!strcmp(name, "splinequality")) {
2010         int v = atoi(value);
2011         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
2012         if(v<1) v = 1;
2013         i->config_splinemaxerror = v;
2014     } else if(!strcmp(name, "fontquality")) {
2015         int v = atoi(value);
2016         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
2017         if(v<1) v = 1;
2018         i->config_fontsplinemaxerror = v;
2019     } else if(!strcmp(name, "linkcolor")) {
2020         if(strlen(value)!=8) {
2021             fprintf(stderr, "Unknown format for option 'linkcolor'. (%s <-> RRGGBBAA)\n", value);
2022             return 1;
2023         }
2024 #       define NIBBLE(s) (((s)>='0' && (s)<='9')?((s)-'0'):((s)&0x0f)+9)
2025         i->config_linkcolor.r = NIBBLE(value[0])<<4 | NIBBLE(value[1]);
2026         i->config_linkcolor.g = NIBBLE(value[2])<<4 | NIBBLE(value[3]);
2027         i->config_linkcolor.b = NIBBLE(value[4])<<4 | NIBBLE(value[5]);
2028         i->config_linkcolor.a = NIBBLE(value[6])<<4 | NIBBLE(value[7]);
2029     } else if(!strcmp(name, "help")) {
2030         printf("\nSWF layer options:\n");
2031         printf("jpegsubpixels=<pixels>      resolution adjustment for jpeg images (same as jpegdpi, but in pixels)\n");
2032         printf("ppmsubpixels=<pixels        resolution adjustment for  lossless images (same as ppmdpi, but in pixels)\n");
2033         printf("subpixels=<pixels>          shortcut for setting both jpegsubpixels and ppmsubpixels\n");
2034         printf("drawonlyshapes              convert everything to shapes (currently broken)\n");
2035         printf("ignoredraworder             allow to perform a few optimizations for creating smaller SWFs\n");
2036         printf("linksopennewwindow          make links open a new browser window\n");
2037         printf("linktarget                  target window name of new links\n");
2038         printf("linkcolor=<color)           color of links (format: RRGGBBAA)\n");
2039         printf("linknameurl                 Link buttons will be named like the URL they refer to (handy for iterating through links with actionscript)\n");
2040         printf("storeallcharacters          don't reduce the fonts to used characters in the output file\n");
2041         printf("enablezlib                  switch on zlib compression (also done if flashversion>=7)\n");
2042         printf("bboxvars                    store the bounding box of the SWF file in actionscript variables\n");
2043         printf("dots                        Take care to handle dots correctly\n");
2044         printf("reordertags=0/1             (default: 1) perform some tag optimizations\n");
2045         printf("internallinkfunction=<name> when the user clicks a internal link (to a different page) in the converted file, this actionscript function is called\n");
2046         printf("externallinkfunction=<name> when the user clicks an external link (e.g. http://www.foo.bar/) on the converted file, this actionscript function is called\n");
2047         printf("disable_polygon_conversion  never convert strokes to polygons (will remove capstyles and joint styles)\n");
2048         printf("caplinewidth=<width>        the minimum thichness a line needs to have so that capstyles become visible (and are converted)\n");
2049         printf("insertstop                  put an ActionScript \"STOP\" tag in every frame\n");
2050         printf("protect                     add a \"protect\" tag to the file, to prevent loading in the Flash editor\n");
2051         printf("flashversion=<version>      the SWF fileversion (6)\n");
2052         printf("framerate=<fps>             SWF framerate\n");
2053         printf("minlinewidth=<width>        convert horizontal/vertical boxes smaller than this width to lines (0.05) \n");
2054         printf("simpleviewer                Add next/previous buttons to the SWF\n");
2055         printf("animate                     insert a showframe tag after each placeobject (animate draw order of PDF files)\n");
2056         printf("jpegquality=<quality>       set compression quality of jpeg images\n");
2057         printf("splinequality=<value>       Set the quality of spline convertion to value (0-100, default: 100).\n");
2058         printf("disablelinks                Disable links.\n");
2059     } else {
2060         return 0;
2061     }
2062     return 1;
2063 }
2064
2065 // --------------------------------------------------------------------
2066
2067 static CXFORM gfxcxform_to_cxform(gfxcxform_t* c)
2068 {
2069     CXFORM cx;
2070     swf_GetCXForm(0, &cx, 1);
2071     if(!c)
2072         return cx;
2073     if(c->rg!=0 || c->rb!=0 || c->ra!=0 ||
2074        c->gr!=0 || c->gb!=0 || c->ga!=0 ||
2075        c->br!=0 || c->bg!=0 || c->ba!=0 ||
2076        c->ar!=0 || c->ag!=0 || c->ab!=0)
2077         msg("<warning> CXForm not SWF-compatible");
2078
2079     cx.a0 = (S16)(c->aa*256);
2080     cx.r0 = (S16)(c->rr*256);
2081     cx.g0 = (S16)(c->gg*256);
2082     cx.b0 = (S16)(c->bb*256);
2083     cx.a1 = c->ta;
2084     cx.r1 = c->tr;
2085     cx.g1 = c->tg;
2086     cx.b1 = c->tb;
2087     return cx;
2088 }
2089
2090 /* TODO */
2091 static int imageInCache(gfxdevice_t*dev, void*data, int width, int height)
2092 {
2093     return -1;
2094 }
2095 static void addImageToCache(gfxdevice_t*dev, void*data, int width, int height)
2096 {
2097 }
2098     
2099 static int add_image(swfoutput_internal*i, gfximage_t*img, int targetwidth, int targetheight, int* newwidth, int* newheight)
2100 {
2101     gfxdevice_t*dev = i->dev;
2102     RGBA*newpic = 0;
2103     RGBA*mem = (RGBA*)img->data;
2104     
2105     int sizex = img->width;
2106     int sizey = img->height;
2107     int is_jpeg = i->jpeg;
2108     i->jpeg = 0;
2109
2110     int newsizex=sizex, newsizey=sizey;
2111
2112     /// {
2113     if(is_jpeg && i->config_jpegsubpixels) {
2114         newsizex = (int)(targetwidth*i->config_jpegsubpixels + 0.5);
2115         newsizey = (int)(targetheight*i->config_jpegsubpixels + 0.5);
2116     } else if(!is_jpeg && i->config_ppmsubpixels) {
2117         newsizex = (int)(targetwidth*i->config_ppmsubpixels + 0.5);
2118         newsizey = (int)(targetheight*i->config_ppmsubpixels + 0.5);
2119     }
2120     /// }
2121
2122     if(sizex<=0 || sizey<=0)
2123         return -1;
2124     if(newsizex<=0)
2125         newsizex = 1;
2126     if(newsizey<=0)
2127         newsizey = 1;
2128
2129     /* TODO: cache images */
2130     
2131     if(newsizex<sizex || newsizey<sizey) {
2132         msg("<verbose> Scaling %dx%d image to %dx%d", sizex, sizey, newsizex, newsizey);
2133         newpic = swf_ImageScale(mem, sizex, sizey, newsizex, newsizey);
2134         *newwidth = sizex = newsizex;
2135         *newheight  = sizey = newsizey;
2136         mem = newpic;
2137     } else {
2138         *newwidth = newsizex = sizex;
2139         *newheight = newsizey  = sizey;
2140     }
2141
2142     int num_colors = swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,0);
2143     int has_alpha = swf_ImageHasAlpha(mem,sizex,sizey);
2144     
2145     msg("<verbose> Drawing %dx%d %s%simage (id %d) at size %dx%d (%dx%d), %s%d colors",
2146             sizex, sizey, 
2147             has_alpha?(has_alpha==2?"semi-transparent ":"transparent "):"", 
2148             is_jpeg?"jpeg-":"", i->currentswfid+1,
2149             newsizex, newsizey,
2150             targetwidth, targetheight,
2151             /*newsizex, newsizey,*/
2152             num_colors>256?">":"", num_colors>256?256:num_colors);
2153
2154     /*RGBA* pal = (RGBA*)rfx_alloc(sizeof(RGBA)*num_colors);
2155     swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,pal);
2156     int t;
2157     for(t=0;t<num_colors;t++) {
2158         printf("%02x%02x%02x%02x ",
2159                 pal[t].r, pal[t].g, pal[t].b, pal[t].a);
2160         if((t&7)==7)
2161             printf("\n");
2162     }
2163     printf("\n");*/
2164
2165     int bitid = -1;
2166     int cacheid = imageInCache(dev, mem, sizex, sizey);
2167
2168     if(cacheid<=0) {
2169         bitid = getNewID(dev);
2170
2171         i->tag = swf_AddImage(i->tag, bitid, mem, sizex, sizey, i->config_jpegquality);
2172         addImageToCache(dev, mem, sizex, sizey);
2173     } else {
2174         bitid = cacheid;
2175     }
2176
2177     if(newpic)
2178         free(newpic);
2179     return bitid;
2180 }
2181
2182 static SRECT gfxline_getSWFbbox(gfxline_t*line)
2183 {
2184     gfxbbox_t bbox = gfxline_getbbox(line);
2185     SRECT r;
2186     r.xmin = (int)(bbox.xmin*20);
2187     r.ymin = (int)(bbox.ymin*20);
2188     r.xmax = (int)(bbox.xmax*20);
2189     r.ymax = (int)(bbox.ymax*20);
2190     return r;
2191 }
2192
2193 int line_is_empty(gfxline_t*line)
2194 {
2195     while(line) {
2196         if(line->type != gfx_moveTo)
2197             return 0;
2198         line = line->next;
2199     }
2200     return 1;
2201 }
2202
2203 static void swf_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
2204 {
2205     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2206     
2207     if(line_is_empty(line))
2208         return;
2209
2210     endshape(dev);
2211     endtext(dev);
2212
2213     int targetx = (int)(sqrt(matrix->m00*matrix->m00 + matrix->m01*matrix->m01)*img->width);
2214     int targety = (int)(sqrt(matrix->m10*matrix->m10 + matrix->m11*matrix->m11)*img->height);
2215
2216     int newwidth=0,newheight=0;
2217     int bitid = add_image(i, img, targetx, targety, &newwidth, &newheight);
2218     if(bitid<0)
2219         return;
2220     double fx = (double)img->width / (double)newwidth;
2221     double fy = (double)img->height / (double)newheight;
2222
2223     MATRIX m;
2224     m.sx = (int)(65536*20*matrix->m00*fx); m.r1 = (int)(65536*20*matrix->m10*fy);
2225     m.r0 = (int)(65536*20*matrix->m01*fx); m.sy = (int)(65536*20*matrix->m11*fy);
2226     m.tx = (int)(matrix->tx*20);
2227     m.ty = (int)(matrix->ty*20);
2228   
2229     /* shape */
2230     int myshapeid = getNewID(dev);
2231     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE);
2232     SHAPE*shape;
2233     swf_ShapeNew(&shape);
2234     int fsid = swf_ShapeAddBitmapFillStyle(shape,&m,bitid,1);
2235     swf_SetU16(i->tag, myshapeid);
2236     SRECT r = gfxline_getSWFbbox(line);
2237     r = swf_ClipRect(i->pagebbox, r);
2238     swf_SetRect(i->tag,&r);
2239     swf_SetShapeStyles(i->tag,shape);
2240     swf_ShapeCountBits(shape,NULL,NULL);
2241     swf_SetShapeBits(i->tag,shape);
2242     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2243     i->swflastx = i->swflasty = UNDEFINED_COORD;
2244     drawgfxline(dev, line, 1);
2245     swf_ShapeSetEnd(i->tag);
2246     swf_ShapeFree(shape);
2247
2248     msg("<trace> Placing image, shape ID %d, bitmap ID %d", myshapeid, bitid);
2249     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2250     CXFORM cxform2 = gfxcxform_to_cxform(cxform);
2251     swf_ObjectPlace(i->tag,myshapeid,getNewDepth(dev),&i->page_matrix,&cxform2,NULL);
2252 }
2253
2254 static RGBA col_black = {255,0,0,0};
2255
2256 static void drawoutline(gfxdevice_t*dev, gfxline_t*line)
2257 {
2258     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2259
2260     int myshapeid = getNewID(dev);
2261     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2262
2263     SHAPE*shape;
2264     swf_ShapeNew(&shape);
2265     int lsid = swf_ShapeAddLineStyle(shape,1,&col_black);
2266
2267     swf_SetU16(i->tag,myshapeid);
2268     SRECT r = gfxline_getSWFbbox(line);
2269     r = swf_ClipRect(i->pagebbox, r);
2270     swf_SetRect(i->tag,&r);
2271     swf_SetShapeStyles(i->tag,shape);
2272     swf_ShapeCountBits(shape,NULL,NULL);
2273     swf_SetShapeBits(i->tag,shape);
2274     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,lsid,0,0);
2275     drawgfxline(dev, line, 1);
2276     swf_ShapeSetEnd(i->tag);
2277     swf_ShapeFree(shape);
2278         
2279     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2280     swf_ObjectPlace(i->tag, myshapeid, getNewDepth(dev), 0,0,0);
2281 }
2282
2283 static void swf_startclip(gfxdevice_t*dev, gfxline_t*line)
2284 {
2285     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2286
2287     endtext(dev);
2288     endshape(dev);
2289
2290     if(i->clippos >= 127)
2291     {
2292         msg("<warning> Too many clip levels.");
2293         i->clippos --;
2294     } 
2295
2296     if(i->config_showclipshapes)
2297         drawoutline(dev, line);
2298
2299     int myshapeid = getNewID(dev);
2300     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2301     RGBA col;
2302     memset(&col, 0, sizeof(RGBA));
2303     col.a = 255;
2304     SHAPE*shape;
2305     swf_ShapeNew(&shape);
2306     int fsid = swf_ShapeAddSolidFillStyle(shape,&col);
2307     if(i->mark) {
2308         RGBA markcol = {0,i->mark[0],i->mark[1],i->mark[2]};
2309         swf_ShapeAddSolidFillStyle(shape,&markcol);
2310     }
2311     swf_SetU16(i->tag,myshapeid);
2312     SRECT r = gfxline_getSWFbbox(line);
2313     r = swf_ClipRect(i->pagebbox, r);
2314     swf_SetRect(i->tag,&r);
2315     swf_SetShapeStyles(i->tag,shape);
2316     swf_ShapeCountBits(shape,NULL,NULL);
2317     swf_SetShapeBits(i->tag,shape);
2318     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2319     i->swflastx = i->swflasty = UNDEFINED_COORD;
2320     i->shapeisempty = 1;
2321     drawgfxline(dev, line, 1);
2322     if(i->shapeisempty) {
2323         /* an empty clip shape is equivalent to a shape with no area */
2324         int x = line?line->x:0;
2325         int y = line?line->y:0;
2326         moveto(dev, i->tag, x,y);
2327         lineto(dev, i->tag, x,y);
2328         lineto(dev, i->tag, x,y);
2329     }
2330     if(!i->shapeisempty && i->currentswfid==1 && r.xmin==0 && r.ymin==0 && r.xmax==(int)(i->max_x*20) && r.ymax==(int)(i->max_y*20)) {
2331         if(i->config_watermark) {
2332             gfxbbox_t r; r.xmin = r.ymin = 0;r.xmax = i->max_x;r.ymax = i->max_y;
2333             draw_watermark(dev, r, 1);
2334         }
2335     }
2336     swf_ShapeSetEnd(i->tag);
2337     swf_ShapeFree(shape);
2338
2339     /* TODO: remember the bbox, and check all shapes against it */
2340     
2341     msg("<trace> Placing clip ID %d", myshapeid);
2342     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2343     i->cliptags[i->clippos] = i->tag;
2344     i->clipshapes[i->clippos] = myshapeid;
2345     i->clipdepths[i->clippos] = getNewDepth(dev);
2346     i->clippos++;
2347 }
2348
2349 static void swf_endclip(gfxdevice_t*dev)
2350 {
2351     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2352     if(i->textid>=0)
2353         endtext(dev);
2354     if(i->shapeid>=0)
2355         endshape(dev);
2356
2357     if(!i->clippos) {
2358         msg("<error> Invalid end of clipping region");
2359         return;
2360     }
2361     i->clippos--;
2362     /*swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,
2363             / * clip to depth: * / i->depth <= i->clipdepths[i->clippos]? i->depth : i->depth - 1);
2364     i->depth ++;*/
2365     swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,i->depth);
2366 }
2367 static int gfxline_type(gfxline_t*line)
2368 {
2369     int tmplines=0;
2370     int tmpsplines=0;
2371     int lines=0;
2372     int splines=0;
2373     int haszerosegments=0;
2374     int length=0;
2375     while(line) {
2376         if(line->type == gfx_moveTo) {
2377             tmplines=0;
2378             tmpsplines=0;
2379         } else if(line->type == gfx_lineTo) {
2380             tmplines++;
2381             if(tmplines>lines)
2382                 lines=tmplines;
2383         } else if(line->type == gfx_splineTo) {
2384             tmpsplines++;
2385             if(tmpsplines>lines)
2386                 splines=tmpsplines;
2387         }
2388         length++;
2389         line = line->next;
2390     }
2391     if(length>400)
2392         return 5;
2393     if(lines==0 && splines==0) return 0;
2394     else if(lines==1 && splines==0) return 1;
2395     else if(lines==0 && splines==1) return 2;
2396     else if(splines==0) return 3;
2397     else return 4;
2398 }
2399
2400 static int gfxline_has_dots(gfxline_t*line)
2401 {
2402     int tmplines=0;
2403     double x=0,y=0;
2404     double dist = 0;
2405     int isline = 0;
2406     int short_gap = 0;
2407     while(line) {
2408         if(line->type == gfx_moveTo) {
2409             /* test the length of the preceding line, and assume it is a dot if
2410                it's length is less than 1.0. But *only* if there's a noticable 
2411                gap between the previous line and the next moveTo. (I've come
2412                across a PDF where thousands of "dots" were stringed together,
2413                forming a line) */
2414             int last_short_gap = short_gap;
2415             if((fabs(line->x - x) + fabs(line->y - y)) < 1.0) {
2416                 short_gap = 1;
2417             } else {
2418                 short_gap = 0;
2419             }
2420             if(isline && dist < 1 && !short_gap && !last_short_gap) {
2421                 return 1;
2422             }
2423             dist = 0;
2424             isline = 0;
2425         } else if(line->type == gfx_lineTo) {
2426             dist += fabs(line->x - x) + fabs(line->y - y);
2427             isline = 1;
2428         } else if(line->type == gfx_splineTo) {
2429             dist += fabs(line->sx - x) + fabs(line->sy - y) + 
2430                     fabs(line->x - line->sx) + fabs(line->y - line->sy);
2431             isline = 1;
2432         }
2433         x = line->x;
2434         y = line->y;
2435         line = line->next;
2436     }
2437     if(isline && dist < 1 && !short_gap) {
2438         return 1;
2439     }
2440     return 0;
2441 }
2442
2443 static int gfxline_fix_short_edges(gfxline_t*line)
2444 {
2445     double x,y;
2446     while(line) {
2447         if(line->type == gfx_lineTo) {
2448             if(fabs(line->x - x) + fabs(line->y - y) < 0.01) {
2449                 line->x += 0.01;
2450             }
2451         } else if(line->type == gfx_splineTo) {
2452             if(fabs(line->sx - x) + fabs(line->sy - y) + 
2453                fabs(line->x - line->sx) + fabs(line->y - line->sy) < 0.01) {
2454                 line->x += 0.01;
2455             }
2456         }
2457         x = line->x;
2458         y = line->y;
2459         line = line->next;
2460     }
2461     return 0;
2462 }
2463
2464 static char is_inside_page(gfxdevice_t*dev, gfxcoord_t x, gfxcoord_t y)
2465 {
2466     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2467     if(x<i->min_x || x>i->max_x) return 0;
2468     if(y<i->min_y || y>i->max_y) return 0;
2469     return 1;
2470 }
2471
2472 gfxline_t* gfxline_move(gfxline_t*line, double x, double y)
2473 {
2474     gfxline_t*l = line = gfxline_clone(line);
2475
2476     while(l) {
2477         l->x += x;
2478         l->y += y;
2479         l->sx += x;
2480         l->sy += y;
2481         l = l->next;
2482     }
2483     return line;
2484 }
2485
2486 //#define NORMALIZE_POLYGON_POSITIONS
2487
2488 static void swf_stroke(gfxdevice_t*dev, gfxline_t*line, gfxcoord_t width, gfxcolor_t*color, gfx_capType cap_style, gfx_joinType joint_style, gfxcoord_t miterLimit)
2489 {
2490     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2491     if(line_is_empty(line))
2492         return;
2493     int type = gfxline_type(line);
2494     int has_dots = gfxline_has_dots(line);
2495     gfxbbox_t r = gfxline_getbbox(line);
2496     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2497
2498     /* TODO: * split line into segments, and perform this check for all segments */
2499
2500     if(i->config_disable_polygon_conversion || /*type>=5 ||*/
2501        (!has_dots &&
2502         (width <= i->config_caplinewidth 
2503         || (cap_style == gfx_capRound && joint_style == gfx_joinRound)
2504         || (cap_style == gfx_capRound && type<=2)))) 
2505     {
2506         // ...
2507     } else {
2508         /* convert line to polygon */
2509         msg("<trace> draw as polygon, type=%d dots=%d", type, has_dots);
2510         if(has_dots)
2511             gfxline_fix_short_edges(line);
2512         /* we need to convert the line into a polygon */
2513         gfxpoly_t* poly = gfxpoly_strokeToPoly(line, width, cap_style, joint_style, miterLimit);
2514         gfxline_t*gfxline = gfxpoly_to_gfxline(poly);
2515         dev->fill(dev, gfxline, color);
2516         gfxline_free(gfxline);
2517         gfxpoly_free(poly);
2518         return;
2519     }
2520
2521     msg("<trace> draw as stroke, type=%d dots=%d", type, has_dots);
2522     endtext(dev);
2523
2524     if(i->config_normalize_polygon_positions) {
2525         endshape(dev);
2526         double startx = 0, starty = 0;
2527         if(line && line->type == gfx_moveTo) {
2528             startx = line->x;
2529             starty = line->y;
2530         }
2531         line = gfxline_move(line, -startx, -starty);
2532         i->shapeposx = (int)(startx*20);
2533         i->shapeposy = (int)(starty*20);
2534     }
2535
2536     swfoutput_setstrokecolor(dev, color->r, color->g, color->b, color->a);
2537     swfoutput_setlinewidth(dev, width);
2538     startshape(dev);
2539     stopFill(dev);
2540     drawgfxline(dev, line, 0);
2541
2542     if(i->config_normalize_polygon_positions) {
2543         free(line); //account for _move
2544     }
2545
2546 }
2547
2548 static void swf_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color)
2549 {
2550     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2551     if(line_is_empty(line))
2552         return;
2553     if(!color->a)
2554         return;
2555     gfxbbox_t r = gfxline_getbbox(line);
2556     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2557
2558     //if(i->chardatapos && i->chardata[i->chardatapos-1].color.a) {
2559     endtext(dev);
2560
2561     if(!i->config_ignoredraworder)
2562         endshape(dev);
2563
2564     if(i->config_normalize_polygon_positions) {
2565         endshape(dev);
2566         double startx = 0, starty = 0;
2567         if(line && line->type == gfx_moveTo) {
2568             startx = line->x;
2569             starty = line->y;
2570         }
2571         line = gfxline_move(line, -startx, -starty);
2572         i->shapeposx = (int)(startx*20);
2573         i->shapeposy = (int)(starty*20);
2574     }
2575
2576     swfoutput_setfillcolor(dev, color->r, color->g, color->b, color->a);
2577     startshape(dev);
2578     startFill(dev);
2579     drawgfxline(dev, line, 1);
2580     
2581     if(i->currentswfid==2 && r.xmin==0 && r.ymin==0 && r.xmax==i->max_x && r.ymax==i->max_y) {
2582         if(i->config_watermark) {
2583             draw_watermark(dev, r, 1);
2584         }
2585     }
2586
2587     msg("<trace> end of swf_fill (shapeid=%d)", i->shapeid);
2588
2589     if(i->config_normalize_polygon_positions) {
2590         free(line); //account for _move
2591     }
2592 }
2593
2594 static GRADIENT* gfxgradient_to_GRADIENT(gfxgradient_t*gradient)
2595 {
2596     int num = 0;
2597     gfxgradient_t*g = gradient;
2598     while(g) {
2599         num++;
2600         g = g->next;
2601     }
2602     GRADIENT* swfgradient = malloc(sizeof(GRADIENT));
2603     swfgradient->num = num;
2604     swfgradient->rgba = malloc(sizeof(swfgradient->rgba[0])*num);
2605     swfgradient->ratios = malloc(sizeof(swfgradient->ratios[0])*num);
2606
2607     g = gradient;
2608     num = 0;
2609     while(g) {
2610         swfgradient->ratios[num] = g->pos*255;
2611         swfgradient->rgba[num] = *(RGBA*)&g->color;
2612         num++;
2613         g = g->next;
2614     }
2615     return swfgradient;
2616 }
2617
2618 static void swf_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
2619 {
2620     if(line_is_empty(line))
2621         return;
2622     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2623     
2624     if(line_is_empty(line))
2625         return;
2626
2627     GRADIENT* swfgradient = gfxgradient_to_GRADIENT(gradient);
2628     if(!swfgradient)
2629         return;
2630   
2631     endshape(dev);
2632     endtext(dev);
2633
2634     double f = type==gfxgradient_radial?4:4;
2635     MATRIX m;
2636     m.sx = (int)(matrix->m00*20*f); m.r1 = (int)(matrix->m10*20*f);
2637     m.r0 = (int)(matrix->m01*20*f); m.sy = (int)(matrix->m11*20*f);
2638     m.tx = (int)(matrix->tx*20);
2639     m.ty = (int)(matrix->ty*20);
2640
2641     /* shape */
2642     int myshapeid = getNewID(dev);
2643     i->tag = swf_InsertTag(i->tag, ST_DEFINESHAPE2);
2644     SHAPE*shape;
2645     swf_ShapeNew(&shape);
2646     int fsid = swf_ShapeAddGradientFillStyle(shape,&m,swfgradient,type==gfxgradient_radial);
2647     swf_SetU16(i->tag, myshapeid);
2648     SRECT r = gfxline_getSWFbbox(line);
2649     r = swf_ClipRect(i->pagebbox, r);
2650     swf_SetRect(i->tag,&r);
2651     swf_SetShapeStyles(i->tag,shape);
2652     swf_ShapeCountBits(shape,NULL,NULL);
2653     swf_SetShapeBits(i->tag,shape);
2654     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2655     i->swflastx = i->swflasty = UNDEFINED_COORD;
2656     drawgfxline(dev, line, 1);
2657     swf_ShapeSetEnd(i->tag);
2658     swf_ShapeFree(shape);
2659
2660     int depth = getNewDepth(dev);
2661     msg("<trace> Placing gradient, shape ID %d, depth %d", myshapeid, depth);
2662     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2663     swf_ObjectPlace(i->tag,myshapeid,depth,&i->page_matrix,NULL,NULL);
2664
2665     swf_FreeGradient(swfgradient);free(swfgradient);
2666 }
2667
2668 static SWFFONT* gfxfont_to_swffont(gfxfont_t*font, const char* id)
2669 {
2670     SWFFONT*swffont = (SWFFONT*)rfx_calloc(sizeof(SWFFONT));
2671     int t;
2672     SRECT bounds = {0,0,0,0};
2673     swffont->id = -1;
2674     swffont->version = 2;
2675     swffont->name = (U8*)strdup(id);
2676     swffont->layout = (SWFLAYOUT*)rfx_calloc(sizeof(SWFLAYOUT));
2677     swffont->layout->ascent = 0;
2678     swffont->layout->descent = 0;
2679     swffont->layout->leading = 0;
2680     swffont->layout->bounds = (SRECT*)rfx_calloc(sizeof(SRECT)*font->num_glyphs);
2681     swffont->encoding = FONT_ENCODING_UNICODE;
2682     swffont->numchars = font->num_glyphs;
2683     swffont->maxascii = font->max_unicode;
2684     swffont->ascii2glyph = (int*)rfx_calloc(sizeof(int)*swffont->maxascii);
2685     swffont->glyph2ascii = (U16*)rfx_calloc(sizeof(U16)*swffont->numchars);
2686     swffont->glyph = (SWFGLYPH*)rfx_calloc(sizeof(SWFGLYPH)*swffont->numchars);
2687     swffont->glyphnames = (char**)rfx_calloc(sizeof(char*)*swffont->numchars);
2688     for(t=0;t<font->max_unicode;t++) {
2689         swffont->ascii2glyph[t] = font->unicode2glyph[t];
2690     }
2691     SRECT max = {0,0,0,0};
2692     for(t=0;t<font->num_glyphs;t++) {
2693         drawer_t draw;
2694         gfxline_t*line;
2695         double advance = 0;
2696         swffont->glyph2ascii[t] = font->glyphs[t].unicode;
2697         if(swffont->glyph2ascii[t] == 0xffff || swffont->glyph2ascii[t] == 0x0000) {
2698             /* flash 8 flashtype requires unique unicode IDs for each character.
2699                We use the Unicode private user area to assign characters, hoping that
2700                the font doesn't contain more than 2048 glyphs */
2701             swffont->glyph2ascii[t] = 0xe000 + (t&0x1fff);
2702         }
2703
2704         if(font->glyphs[t].name) {
2705             swffont->glyphnames[t] = strdup(font->glyphs[t].name);
2706         } else {
2707             swffont->glyphnames[t] = 0;
2708         }
2709         advance = font->glyphs[t].advance;
2710
2711         swf_Shape01DrawerInit(&draw, 0);
2712         line = font->glyphs[t].line;
2713         while(line) {
2714             FPOINT c,to;
2715             c.x = line->sx * GLYPH_SCALE; c.y = line->sy * GLYPH_SCALE;
2716             to.x = line->x * GLYPH_SCALE; to.y = line->y * GLYPH_SCALE;
2717             if(line->type == gfx_moveTo) {
2718                 draw.moveTo(&draw, &to);
2719             } else if(line->type == gfx_lineTo) {
2720                 draw.lineTo(&draw, &to);
2721             } else if(line->type == gfx_splineTo) {
2722                 draw.splineTo(&draw, &c, &to);
2723             }
2724             line = line->next;
2725         }
2726         draw.finish(&draw);
2727         swffont->glyph[t].shape = swf_ShapeDrawerToShape(&draw);
2728
2729         SRECT bbox = swf_ShapeDrawerGetBBox(&draw);
2730         swf_ExpandRect2(&max, &bbox);
2731
2732         swffont->layout->bounds[t] = bbox;
2733             
2734         if(advance<32768.0/20) {
2735             swffont->glyph[t].advance = (int)(advance*20);
2736         } else {
2737             //msg("<warning> Advance value overflow in glyph %d", t);
2738             swffont->glyph[t].advance = 32767;
2739         }
2740
2741         draw.dealloc(&draw);
2742
2743         swf_ExpandRect2(&bounds, &swffont->layout->bounds[t]);
2744     }
2745     for(t=0;t<font->num_glyphs;t++) {
2746         SRECT bbox = swffont->layout->bounds[t];
2747
2748         /* if the glyph doesn't have a bounding box, use the
2749            combined bounding box (necessary e.g. for space characters) */
2750         if(!(bbox.xmin|bbox.ymin|bbox.xmax|bbox.ymax)) {
2751             swffont->layout->bounds[t] = bbox = max;
2752         }
2753         
2754         /* check that the advance value is reasonable, by comparing it
2755            with the bounding box */
2756         if(bbox.xmax>0 && (bbox.xmax*10 < swffont->glyph[t].advance || !swffont->glyph[t].advance)) {
2757             if(swffont->glyph[t].advance)
2758                 msg("<warning> fix bad advance value for char %d: bbox=%.2f, advance=%.2f\n", t, bbox.xmax/20.0, swffont->glyph[t].advance/20.0);
2759             swffont->glyph[t].advance = bbox.xmax;
2760         }
2761         //swffont->glyph[t].advance = bbox.xmax - bbox.xmin;
2762     }
2763
2764
2765     /* Flash player will use the advance value from the char, and the ascent/descent values
2766        from the layout for text selection.
2767        ascent will extend the char into negative y direction, from the baseline, while descent
2768        will extend in positive y direction, also from the baseline.
2769        The baseline is defined as the y-position zero 
2770      */
2771
2772     swffont->layout->ascent = -bounds.ymin;
2773     if(swffont->layout->ascent < 0)
2774         swffont->layout->ascent = 0;
2775     swffont->layout->descent = bounds.ymax;
2776     if(swffont->layout->descent < 0)
2777         swffont->layout->descent = 0;
2778     swffont->layout->leading = bounds.ymax - bounds.ymin;
2779
2780     /* if the font has proper ascent/descent values (>0) and those define
2781        greater line spacing that what we estimated from the bounding boxes,
2782        use the font's parameters */
2783     if(font->ascent*20 > swffont->layout->ascent)
2784         swffont->layout->ascent = font->ascent*20;
2785     if(font->descent*20 > swffont->layout->descent)
2786         swffont->layout->descent = font->descent*20;
2787
2788     return swffont;
2789 }
2790
2791 static void swf_addfont(gfxdevice_t*dev, gfxfont_t*font)
2792 {
2793     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2794
2795     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,font->id))
2796         return; // the requested font is the current font
2797     
2798     fontlist_t*last=0,*l = i->fontlist;
2799     while(l) {
2800         last = l;
2801         if(!strcmp((char*)l->swffont->name, font->id)) {
2802             return; // we already know this font
2803         }
2804         l = l->next;
2805     }
2806     l = (fontlist_t*)rfx_calloc(sizeof(fontlist_t));
2807     l->swffont = gfxfont_to_swffont(font, font->id);
2808     l->next = 0;
2809     if(last) {
2810         last->next = l;
2811     } else {
2812         i->fontlist = l;
2813     }
2814     swf_FontSetID(l->swffont, getNewID(i->dev));
2815
2816     if(getScreenLogLevel() >= LOGLEVEL_DEBUG)  {
2817         int iii;
2818         // print font information
2819         msg("<debug> Font %s",font->id);
2820         msg("<debug> |   ID: %d", l->swffont->id);
2821         msg("<debug> |   Version: %d", l->swffont->version);
2822         msg("<debug> |   Name: %s", l->swffont->name);
2823         msg("<debug> |   Numchars: %d", l->swffont->numchars);
2824         msg("<debug> |   Maxascii: %d", l->swffont->maxascii);
2825         msg("<debug> |   Style: %d", l->swffont->style);
2826         msg("<debug> |   Encoding: %d", l->swffont->encoding);
2827         for(iii=0; iii<l->swffont->numchars;iii++) {
2828             msg("<debug> |   Glyph %d) name=%s, unicode=%d size=%d bbox=(%.2f,%.2f,%.2f,%.2f)\n", iii, l->swffont->glyphnames?l->swffont->glyphnames[iii]:"<nonames>", l->swffont->glyph2ascii[iii], l->swffont->glyph[iii].shape->bitlen, 
2829                     l->swffont->layout->bounds[iii].xmin/20.0,
2830                     l->swffont->layout->bounds[iii].ymin/20.0,
2831                     l->swffont->layout->bounds[iii].xmax/20.0,
2832                     l->swffont->layout->bounds[iii].ymax/20.0
2833                     );
2834             int t;
2835             for(t=0;t<l->swffont->maxascii;t++) {
2836                 if(l->swffont->ascii2glyph[t] == iii)
2837                     msg("<debug> | - maps to %d",t);
2838             }
2839         }
2840     }
2841 }
2842
2843 static void swf_switchfont(gfxdevice_t*dev, const char*fontid)
2844 {
2845     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2846
2847     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,fontid))
2848         return; // the requested font is the current font
2849     
2850     fontlist_t*l = i->fontlist;
2851     while(l) {
2852         if(!strcmp((char*)l->swffont->name, fontid)) {
2853             i->swffont = l->swffont;
2854             return; //done!
2855         }
2856         l = l->next;
2857     }
2858     msg("<error> Unknown font id: %s", fontid);
2859     return;
2860 }
2861
2862 static void swf_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyph, gfxcolor_t*color, gfxmatrix_t*matrix)
2863 {
2864     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2865     if(!font) {
2866         msg("<error> swf_drawchar called (glyph %d) without font", glyph);
2867         return;
2868     }
2869
2870     if(i->config_drawonlyshapes) {
2871         gfxglyph_t*g = &font->glyphs[glyph];
2872         gfxline_t*line2 = gfxline_clone(g->line);
2873         gfxline_transform(line2, matrix);
2874         dev->fill(dev, line2, color);
2875         gfxline_free(line2);
2876         return;
2877     }
2878
2879     if(!i->swffont || !i->swffont->name || strcmp((char*)i->swffont->name,font->id)) // not equal to current font
2880     {
2881         /* TODO: remove the need for this (enhance getcharacterbbox so that it can cope
2882                  with multiple fonts */
2883         endtext(dev);
2884         swf_switchfont(dev, font->id); // set the current font
2885     }
2886     if(!i->swffont) {
2887         msg("<warning> swf_drawchar: Font is NULL");
2888         return;
2889     }
2890     if(glyph<0 || glyph>=i->swffont->numchars) {
2891         msg("<warning> No character %d in font %s (%d chars)", glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2892         return;
2893     }
2894     
2895     setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 0);
2896     
2897     double det = i->fontmatrix.sx/65536.0 * i->fontmatrix.sy/65536.0 - 
2898                  i->fontmatrix.r0/65536.0 * i->fontmatrix.r1/65536.0;
2899     if(fabs(det) < 0.0005) { 
2900         /* x direction equals y direction- the text is invisible */
2901         msg("<verbose> Not drawing invisible character character %d (det=%f, m=[%f %f;%f %f]\n", glyph, 
2902                 det,
2903                 i->fontmatrix.sx/65536.0, i->fontmatrix.r1/65536.0, 
2904                 i->fontmatrix.r0/65536.0, i->fontmatrix.sy/65536.0);
2905         return;
2906     }
2907
2908     /*if(i->swffont->glyph[glyph].shape->bitlen <= 16) {
2909         msg("<warning> Glyph %d in current charset (%s, %d characters) is empty", 
2910                 glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2911         return 1;
2912     }*/
2913
2914     /* calculate character position with respect to the current font matrix */
2915     double s = 20 * GLYPH_SCALE / det;
2916     double px = matrix->tx - i->fontmatrix.tx/20.0;
2917     double py = matrix->ty - i->fontmatrix.ty/20.0;
2918     int x = (SCOORD)((  px * i->fontmatrix.sy/65536.0 - py * i->fontmatrix.r1/65536.0)*s);
2919     int y = (SCOORD)((- px * i->fontmatrix.r0/65536.0 + py * i->fontmatrix.sx/65536.0)*s);
2920     if(x>32767 || x<-32768 || y>32767 || y<-32768) {
2921         msg("<verbose> Moving character origin to %f %f\n", matrix->tx, matrix->ty);
2922         endtext(dev);
2923         setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 1);
2924         /* since we just moved the char origin to the current char's position, 
2925            it now has the relative position (0,0) */
2926         x = y = 0;
2927     }
2928     
2929     if(i->shapeid>=0)
2930         endshape(dev);
2931     if(i->textid<0)
2932         starttext(dev);
2933
2934     msg("<trace> Drawing char %d in font %d at %d,%d in color %02x%02x%02x%02x", 
2935             glyph, i->swffont->id, x, y, color->r, color->g, color->b, color->a);
2936
2937     putcharacter(dev, i->swffont->id, glyph, x, y, i->current_font_size, *(RGBA*)color);
2938     swf_FontUseGlyph(i->swffont, glyph);
2939     return;
2940 }