SWF are local-with-network by default, now
[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
1784     /* shape2 */
1785     myshapeid2 = getNewID(dev);
1786     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1787     swf_ShapeNew(&i->shape);
1788     
1789     rgb = i->config_linkcolor;
1790
1791     fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
1792     swf_SetU16(i->tag, myshapeid2);
1793     r.xmin = (int)(bbox.xmin*20);
1794     r.ymin = (int)(bbox.ymin*20);
1795     r.xmax = (int)(bbox.xmax*20);
1796     r.ymax = (int)(bbox.ymax*20);
1797     r = swf_ClipRect(i->pagebbox, r);
1798     swf_SetRect(i->tag,&r);
1799     swf_SetShapeStyles(i->tag,i->shape);
1800     swf_ShapeCountBits(i->shape,NULL,NULL);
1801     swf_SetShapeBits(i->tag,i->shape);
1802     swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
1803     i->swflastx = i->swflasty = 0;
1804     drawgfxline(dev, points, 1);
1805     swf_ShapeSetEnd(i->tag);
1806
1807     if(!mouseover)
1808     {
1809         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON);
1810         swf_SetU16(i->tag,buttonid); //id
1811         swf_ButtonSetFlags(i->tag, 0); //menu=no
1812         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1813         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1814         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1815         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1816         swf_SetU8(i->tag,0);
1817         swf_ActionSet(i->tag,actions1);
1818         swf_SetU8(i->tag,0);
1819     }
1820     else
1821     {
1822         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON2);
1823         swf_SetU16(i->tag,buttonid); //id
1824         swf_ButtonSetFlags(i->tag, 0); //menu=no
1825         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1826         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1827         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1828         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1829         swf_SetU8(i->tag,0); // end of button records
1830         swf_ButtonSetCondition(i->tag, BC_IDLE_OVERUP);
1831         swf_ActionSet(i->tag,actions1);
1832         if(actions2) {
1833             swf_ButtonSetCondition(i->tag, BC_OVERUP_IDLE);
1834             swf_ActionSet(i->tag,actions2);
1835             swf_SetU8(i->tag,0);
1836             swf_ButtonPostProcess(i->tag, 2);
1837         } else {
1838             swf_SetU8(i->tag,0);
1839             swf_ButtonPostProcess(i->tag, 1);
1840         }
1841     }
1842     char buf[80];
1843     const char* name = 0;
1844     if(i->config_linknameurl) {
1845         name = url;
1846     } else {
1847         name = buf;
1848         sprintf(buf, "button%d", buttonid);
1849     }
1850     
1851     msg("<trace> Placing link ID %d", buttonid);
1852     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
1853
1854     if(posx!=0 || posy!=0) {
1855         SPOINT p;
1856         p.x = (int)(posx*20);
1857         p.y = (int)(posy*20);
1858         p = swf_TurnPoint(p, &i->page_matrix);
1859         MATRIX m;
1860         m = i->page_matrix;
1861         m.tx = p.x;
1862         m.ty = p.y;
1863         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&m,0,name);
1864     } else {
1865         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&i->page_matrix,0,name);
1866     }
1867 }
1868
1869       
1870 ///////////
1871 /*
1872 for(t=0;t<picpos;t++)
1873       {
1874           if(pic_xids[t] == xid &&
1875              pic_yids[t] == yid) {
1876               width = pic_width[t];
1877               height = pic_height[t];
1878               found = t;break;
1879           }
1880       }
1881           pic_ids[picpos] = swfoutput_drawimagelosslessN(&output, pic, pal, width, height, x1,y1,x2,y2,x3,y3,x4,y4, numpalette);
1882           pic_xids[picpos] = xid;
1883           pic_yids[picpos] = yid;
1884           pic_width[picpos] = width;
1885           pic_height[picpos] = height;
1886           if(picpos<1024)
1887               picpos++;
1888             pic[width*y+x] = buf[0];
1889             xid+=x*buf[0]+1;
1890             yid+=y*buf[0]*3+1;
1891       
1892             xid += pal[1].r*3 + pal[1].g*11 + pal[1].b*17;
1893       yid += pal[1].r*7 + pal[1].g*5 + pal[1].b*23;
1894       
1895       int xid = 0;
1896       int yid = 0;
1897           xid += x*r+x*b*3+x*g*7+x*a*11;
1898           yid += y*r*3+y*b*17+y*g*19+y*a*11;
1899       int t,found = -1;
1900       for(t=0;t<picpos;t++)
1901       {
1902           if(pic_xids[t] == xid &&
1903              pic_yids[t] == yid) {
1904               found = t;break;
1905           }
1906       }
1907       if(found<0) {
1908 */
1909 ///////////
1910
1911
1912 int swf_setparameter(gfxdevice_t*dev, const char*name, const char*value)
1913 {
1914     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1915
1916     msg("<trace> swfdevice: %s=%s", name, value);
1917     if(!strcmp(name, "jpegsubpixels")) {
1918         i->config_jpegsubpixels = atof(value);
1919     } else if(!strcmp(name, "ppmsubpixels")) {
1920         i->config_ppmsubpixels = atof(value);
1921     } else if(!strcmp(name, "subpixels")) {
1922         i->config_ppmsubpixels = i->config_jpegsubpixels = atof(value);
1923     } else if(!strcmp(name, "drawonlyshapes")) {
1924         i->config_drawonlyshapes = atoi(value);
1925     } else if(!strcmp(name, "ignoredraworder")) {
1926         i->config_ignoredraworder = atoi(value);
1927     } else if(!strcmp(name, "mark")) {
1928         if(!value || !value[0]) {
1929             if(i->mark) free(i->mark);
1930             i->mark = 0;
1931         } else {
1932             int t;
1933             i->mark = strdup("...");
1934             for(t=0;t<3;t++) if(value[t]) i->mark[t] = value[t];
1935         }
1936     } else if(!strcmp(name, "filloverlap")) {
1937         i->config_filloverlap = atoi(value);
1938     } else if(!strcmp(name, "linksopennewwindow")) {
1939         i->config_opennewwindow = atoi(value);
1940     } else if(!strcmp(name, "opennewwindow")) {
1941         i->config_opennewwindow = atoi(value);
1942     } else if(!strcmp(name, "storeallcharacters")) {
1943         i->config_storeallcharacters = atoi(value);
1944     } else if(!strcmp(name, "enablezlib")) {
1945         i->config_enablezlib = atoi(value);
1946     } else if(!strcmp(name, "bboxvars")) {
1947         i->config_bboxvars = atoi(value);
1948     } else if(!strcmp(name, "dots")) {
1949         i->config_dots = atoi(value);
1950     } else if(!strcmp(name, "frameresets")) {
1951         i->config_frameresets = atoi(value);
1952     } else if(!strcmp(name, "showclipshapes")) {
1953         i->config_showclipshapes = atoi(value);
1954     } else if(!strcmp(name, "reordertags")) {
1955         i->config_reordertags = atoi(value);
1956     } else if(!strcmp(name, "internallinkfunction")) {
1957         i->config_internallinkfunction = strdup(value);
1958     } else if(!strcmp(name, "externallinkfunction")) {
1959         i->config_externallinkfunction = strdup(value);
1960     } else if(!strcmp(name, "linkfunction")) { //sets both internallinkfunction and externallinkfunction
1961         i->config_internallinkfunction = strdup(value);
1962         i->config_externallinkfunction = strdup(value);
1963     } else if(!strcmp(name, "disable_polygon_conversion")) {
1964         i->config_disable_polygon_conversion = atoi(value);
1965     } else if(!strcmp(name, "normalize_polygon_positions")) {
1966         i->config_normalize_polygon_positions = atoi(value);
1967     } else if(!strcmp(name, "wxwindowparams")) {
1968         i->config_watermark = atoi(value);
1969     } else if(!strcmp(name, "insertstop")) {
1970         i->config_insertstoptag = atoi(value);
1971     } else if(!strcmp(name, "protect")) {
1972         i->config_protect = atoi(value);
1973         if(i->config_protect && i->tag) {
1974             i->tag = swf_InsertTag(i->tag, ST_PROTECT);
1975         }
1976     } else if(!strcmp(name, "flashversion")) {
1977         i->config_flashversion = atoi(value);
1978         if(i->swf) {
1979             i->swf->fileVersion = i->config_flashversion;
1980         }
1981     } else if(!strcmp(name, "framerate")) {
1982         i->config_framerate = atof(value);
1983         if(i->swf) {
1984             i->swf->frameRate = i->config_framerate*0x100;
1985         }
1986     } else if(!strcmp(name, "minlinewidth")) {
1987         i->config_minlinewidth = atof(value);
1988     } else if(!strcmp(name, "caplinewidth")) {
1989         i->config_caplinewidth = atof(value);
1990     } else if(!strcmp(name, "linktarget")) {
1991         i->config_linktarget = strdup(value);
1992     } else if(!strcmp(name, "dumpfonts")) {
1993         i->config_dumpfonts = atoi(value);
1994     } else if(!strcmp(name, "animate")) {
1995         i->config_animate = atoi(value);
1996     } else if(!strcmp(name, "disablelinks")) {
1997         i->config_disablelinks = atoi(value);
1998     } else if(!strcmp(name, "simpleviewer")) {
1999         i->config_simpleviewer = atoi(value);
2000     } else if(!strcmp(name, "next_bitmap_is_jpeg")) {
2001         i->jpeg = 1;
2002     } else if(!strcmp(name, "jpegquality")) {
2003         int val = atoi(value);
2004         if(val<0) val=0;
2005         if(val>101) val=101;
2006         i->config_jpegquality = val;
2007     } else if(!strcmp(name, "splinequality")) {
2008         int v = atoi(value);
2009         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
2010         if(v<1) v = 1;
2011         i->config_splinemaxerror = v;
2012     } else if(!strcmp(name, "fontquality")) {
2013         int v = atoi(value);
2014         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
2015         if(v<1) v = 1;
2016         i->config_fontsplinemaxerror = v;
2017     } else if(!strcmp(name, "linkcolor")) {
2018         if(strlen(value)!=8) {
2019             fprintf(stderr, "Unknown format for option 'linkcolor'. (%s <-> RRGGBBAA)\n", value);
2020             return 1;
2021         }
2022 #       define NIBBLE(s) (((s)>='0' && (s)<='9')?((s)-'0'):((s)&0x0f)+9)
2023         i->config_linkcolor.r = NIBBLE(value[0])<<4 | NIBBLE(value[1]);
2024         i->config_linkcolor.g = NIBBLE(value[2])<<4 | NIBBLE(value[3]);
2025         i->config_linkcolor.b = NIBBLE(value[4])<<4 | NIBBLE(value[5]);
2026         i->config_linkcolor.a = NIBBLE(value[6])<<4 | NIBBLE(value[7]);
2027     } else if(!strcmp(name, "help")) {
2028         printf("\nSWF layer options:\n");
2029         printf("jpegsubpixels=<pixels>      resolution adjustment for jpeg images (same as jpegdpi, but in pixels)\n");
2030         printf("ppmsubpixels=<pixels        resolution adjustment for  lossless images (same as ppmdpi, but in pixels)\n");
2031         printf("subpixels=<pixels>          shortcut for setting both jpegsubpixels and ppmsubpixels\n");
2032         printf("drawonlyshapes              convert everything to shapes (currently broken)\n");
2033         printf("ignoredraworder             allow to perform a few optimizations for creating smaller SWFs\n");
2034         printf("linksopennewwindow          make links open a new browser window\n");
2035         printf("linktarget                  target window name of new links\n");
2036         printf("linkcolor=<color)           color of links (format: RRGGBBAA)\n");
2037         printf("linknameurl                 Link buttons will be named like the URL they refer to (handy for iterating through links with actionscript)\n");
2038         printf("storeallcharacters          don't reduce the fonts to used characters in the output file\n");
2039         printf("enablezlib                  switch on zlib compression (also done if flashversion>=7)\n");
2040         printf("bboxvars                    store the bounding box of the SWF file in actionscript variables\n");
2041         printf("dots                        Take care to handle dots correctly\n");
2042         printf("reordertags=0/1             (default: 1) perform some tag optimizations\n");
2043         printf("internallinkfunction=<name> when the user clicks a internal link (to a different page) in the converted file, this actionscript function is called\n");
2044         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");
2045         printf("disable_polygon_conversion  never convert strokes to polygons (will remove capstyles and joint styles)\n");
2046         printf("caplinewidth=<width>        the minimum thichness a line needs to have so that capstyles become visible (and are converted)\n");
2047         printf("insertstop                  put an ActionScript \"STOP\" tag in every frame\n");
2048         printf("protect                     add a \"protect\" tag to the file, to prevent loading in the Flash editor\n");
2049         printf("flashversion=<version>      the SWF fileversion (6)\n");
2050         printf("framerate=<fps>             SWF framerate\n");
2051         printf("minlinewidth=<width>        convert horizontal/vertical boxes smaller than this width to lines (0.05) \n");
2052         printf("simpleviewer                Add next/previous buttons to the SWF\n");
2053         printf("animate                     insert a showframe tag after each placeobject (animate draw order of PDF files)\n");
2054         printf("jpegquality=<quality>       set compression quality of jpeg images\n");
2055         printf("splinequality=<value>       Set the quality of spline convertion to value (0-100, default: 100).\n");
2056         printf("disablelinks                Disable links.\n");
2057     } else {
2058         return 0;
2059     }
2060     return 1;
2061 }
2062
2063 // --------------------------------------------------------------------
2064
2065 static CXFORM gfxcxform_to_cxform(gfxcxform_t* c)
2066 {
2067     CXFORM cx;
2068     swf_GetCXForm(0, &cx, 1);
2069     if(!c)
2070         return cx;
2071     if(c->rg!=0 || c->rb!=0 || c->ra!=0 ||
2072        c->gr!=0 || c->gb!=0 || c->ga!=0 ||
2073        c->br!=0 || c->bg!=0 || c->ba!=0 ||
2074        c->ar!=0 || c->ag!=0 || c->ab!=0)
2075         msg("<warning> CXForm not SWF-compatible");
2076
2077     cx.a0 = (S16)(c->aa*256);
2078     cx.r0 = (S16)(c->rr*256);
2079     cx.g0 = (S16)(c->gg*256);
2080     cx.b0 = (S16)(c->bb*256);
2081     cx.a1 = c->ta;
2082     cx.r1 = c->tr;
2083     cx.g1 = c->tg;
2084     cx.b1 = c->tb;
2085     return cx;
2086 }
2087
2088 /* TODO */
2089 static int imageInCache(gfxdevice_t*dev, void*data, int width, int height)
2090 {
2091     return -1;
2092 }
2093 static void addImageToCache(gfxdevice_t*dev, void*data, int width, int height)
2094 {
2095 }
2096     
2097 static int add_image(swfoutput_internal*i, gfximage_t*img, int targetwidth, int targetheight, int* newwidth, int* newheight)
2098 {
2099     gfxdevice_t*dev = i->dev;
2100     RGBA*newpic = 0;
2101     RGBA*mem = (RGBA*)img->data;
2102     
2103     int sizex = img->width;
2104     int sizey = img->height;
2105     int is_jpeg = i->jpeg;
2106     i->jpeg = 0;
2107
2108     int newsizex=sizex, newsizey=sizey;
2109
2110     /// {
2111     if(is_jpeg && i->config_jpegsubpixels) {
2112         newsizex = (int)(targetwidth*i->config_jpegsubpixels + 0.5);
2113         newsizey = (int)(targetheight*i->config_jpegsubpixels + 0.5);
2114     } else if(!is_jpeg && i->config_ppmsubpixels) {
2115         newsizex = (int)(targetwidth*i->config_ppmsubpixels + 0.5);
2116         newsizey = (int)(targetheight*i->config_ppmsubpixels + 0.5);
2117     }
2118     /// }
2119
2120     if(sizex<=0 || sizey<=0)
2121         return -1;
2122     if(newsizex<=0)
2123         newsizex = 1;
2124     if(newsizey<=0)
2125         newsizey = 1;
2126
2127     /* TODO: cache images */
2128     
2129     if(newsizex<sizex || newsizey<sizey) {
2130         msg("<verbose> Scaling %dx%d image to %dx%d", sizex, sizey, newsizex, newsizey);
2131         newpic = swf_ImageScale(mem, sizex, sizey, newsizex, newsizey);
2132         *newwidth = sizex = newsizex;
2133         *newheight  = sizey = newsizey;
2134         mem = newpic;
2135     } else {
2136         *newwidth = newsizex = sizex;
2137         *newheight = newsizey  = sizey;
2138     }
2139
2140     int num_colors = swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,0);
2141     int has_alpha = swf_ImageHasAlpha(mem,sizex,sizey);
2142     
2143     msg("<verbose> Drawing %dx%d %s%simage (id %d) at size %dx%d (%dx%d), %s%d colors",
2144             sizex, sizey, 
2145             has_alpha?(has_alpha==2?"semi-transparent ":"transparent "):"", 
2146             is_jpeg?"jpeg-":"", i->currentswfid+1,
2147             newsizex, newsizey,
2148             targetwidth, targetheight,
2149             /*newsizex, newsizey,*/
2150             num_colors>256?">":"", num_colors>256?256:num_colors);
2151
2152     /*RGBA* pal = (RGBA*)rfx_alloc(sizeof(RGBA)*num_colors);
2153     swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,pal);
2154     int t;
2155     for(t=0;t<num_colors;t++) {
2156         printf("%02x%02x%02x%02x ",
2157                 pal[t].r, pal[t].g, pal[t].b, pal[t].a);
2158         if((t&7)==7)
2159             printf("\n");
2160     }
2161     printf("\n");*/
2162
2163     int bitid = -1;
2164     int cacheid = imageInCache(dev, mem, sizex, sizey);
2165
2166     if(cacheid<=0) {
2167         bitid = getNewID(dev);
2168
2169         i->tag = swf_AddImage(i->tag, bitid, mem, sizex, sizey, i->config_jpegquality);
2170         addImageToCache(dev, mem, sizex, sizey);
2171     } else {
2172         bitid = cacheid;
2173     }
2174
2175     if(newpic)
2176         free(newpic);
2177     return bitid;
2178 }
2179
2180 static SRECT gfxline_getSWFbbox(gfxline_t*line)
2181 {
2182     gfxbbox_t bbox = gfxline_getbbox(line);
2183     SRECT r;
2184     r.xmin = (int)(bbox.xmin*20);
2185     r.ymin = (int)(bbox.ymin*20);
2186     r.xmax = (int)(bbox.xmax*20);
2187     r.ymax = (int)(bbox.ymax*20);
2188     return r;
2189 }
2190
2191 int line_is_empty(gfxline_t*line)
2192 {
2193     while(line) {
2194         if(line->type != gfx_moveTo)
2195             return 0;
2196         line = line->next;
2197     }
2198     return 1;
2199 }
2200
2201 static void swf_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
2202 {
2203     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2204     
2205     if(line_is_empty(line))
2206         return;
2207
2208     endshape(dev);
2209     endtext(dev);
2210
2211     int targetx = (int)(sqrt(matrix->m00*matrix->m00 + matrix->m01*matrix->m01)*img->width);
2212     int targety = (int)(sqrt(matrix->m10*matrix->m10 + matrix->m11*matrix->m11)*img->height);
2213
2214     int newwidth=0,newheight=0;
2215     int bitid = add_image(i, img, targetx, targety, &newwidth, &newheight);
2216     if(bitid<0)
2217         return;
2218     double fx = (double)img->width / (double)newwidth;
2219     double fy = (double)img->height / (double)newheight;
2220
2221     MATRIX m;
2222     m.sx = (int)(65536*20*matrix->m00*fx); m.r1 = (int)(65536*20*matrix->m10*fy);
2223     m.r0 = (int)(65536*20*matrix->m01*fx); m.sy = (int)(65536*20*matrix->m11*fy);
2224     m.tx = (int)(matrix->tx*20);
2225     m.ty = (int)(matrix->ty*20);
2226   
2227     /* shape */
2228     int myshapeid = getNewID(dev);
2229     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE);
2230     SHAPE*shape;
2231     swf_ShapeNew(&shape);
2232     int fsid = swf_ShapeAddBitmapFillStyle(shape,&m,bitid,1);
2233     swf_SetU16(i->tag, myshapeid);
2234     SRECT r = gfxline_getSWFbbox(line);
2235     r = swf_ClipRect(i->pagebbox, r);
2236     swf_SetRect(i->tag,&r);
2237     swf_SetShapeStyles(i->tag,shape);
2238     swf_ShapeCountBits(shape,NULL,NULL);
2239     swf_SetShapeBits(i->tag,shape);
2240     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2241     i->swflastx = i->swflasty = UNDEFINED_COORD;
2242     drawgfxline(dev, line, 1);
2243     swf_ShapeSetEnd(i->tag);
2244     swf_ShapeFree(shape);
2245
2246     msg("<trace> Placing image, shape ID %d, bitmap ID %d", myshapeid, bitid);
2247     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2248     CXFORM cxform2 = gfxcxform_to_cxform(cxform);
2249     swf_ObjectPlace(i->tag,myshapeid,getNewDepth(dev),&i->page_matrix,&cxform2,NULL);
2250 }
2251
2252 static RGBA col_black = {255,0,0,0};
2253
2254 static void drawoutline(gfxdevice_t*dev, gfxline_t*line)
2255 {
2256     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2257
2258     int myshapeid = getNewID(dev);
2259     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2260
2261     SHAPE*shape;
2262     swf_ShapeNew(&shape);
2263     int lsid = swf_ShapeAddLineStyle(shape,1,&col_black);
2264
2265     swf_SetU16(i->tag,myshapeid);
2266     SRECT r = gfxline_getSWFbbox(line);
2267     r = swf_ClipRect(i->pagebbox, r);
2268     swf_SetRect(i->tag,&r);
2269     swf_SetShapeStyles(i->tag,shape);
2270     swf_ShapeCountBits(shape,NULL,NULL);
2271     swf_SetShapeBits(i->tag,shape);
2272     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,lsid,0,0);
2273     drawgfxline(dev, line, 1);
2274     swf_ShapeSetEnd(i->tag);
2275     swf_ShapeFree(shape);
2276         
2277     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2278     swf_ObjectPlace(i->tag, myshapeid, getNewDepth(dev), 0,0,0);
2279 }
2280
2281 static void swf_startclip(gfxdevice_t*dev, gfxline_t*line)
2282 {
2283     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2284
2285     endtext(dev);
2286     endshape(dev);
2287
2288     if(i->clippos >= 127)
2289     {
2290         msg("<warning> Too many clip levels.");
2291         i->clippos --;
2292     } 
2293
2294     if(i->config_showclipshapes)
2295         drawoutline(dev, line);
2296
2297     int myshapeid = getNewID(dev);
2298     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2299     RGBA col;
2300     memset(&col, 0, sizeof(RGBA));
2301     col.a = 255;
2302     SHAPE*shape;
2303     swf_ShapeNew(&shape);
2304     int fsid = swf_ShapeAddSolidFillStyle(shape,&col);
2305     if(i->mark) {
2306         RGBA markcol = {0,i->mark[0],i->mark[1],i->mark[2]};
2307         swf_ShapeAddSolidFillStyle(shape,&markcol);
2308     }
2309     swf_SetU16(i->tag,myshapeid);
2310     SRECT r = gfxline_getSWFbbox(line);
2311     r = swf_ClipRect(i->pagebbox, r);
2312     swf_SetRect(i->tag,&r);
2313     swf_SetShapeStyles(i->tag,shape);
2314     swf_ShapeCountBits(shape,NULL,NULL);
2315     swf_SetShapeBits(i->tag,shape);
2316     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2317     i->swflastx = i->swflasty = UNDEFINED_COORD;
2318     i->shapeisempty = 1;
2319     drawgfxline(dev, line, 1);
2320     if(i->shapeisempty) {
2321         /* an empty clip shape is equivalent to a shape with no area */
2322         int x = line?line->x:0;
2323         int y = line?line->y:0;
2324         moveto(dev, i->tag, x,y);
2325         lineto(dev, i->tag, x,y);
2326         lineto(dev, i->tag, x,y);
2327     }
2328     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)) {
2329         if(i->config_watermark) {
2330             gfxbbox_t r; r.xmin = r.ymin = 0;r.xmax = i->max_x;r.ymax = i->max_y;
2331             draw_watermark(dev, r, 1);
2332         }
2333     }
2334     swf_ShapeSetEnd(i->tag);
2335     swf_ShapeFree(shape);
2336
2337     /* TODO: remember the bbox, and check all shapes against it */
2338     
2339     msg("<trace> Placing clip ID %d", myshapeid);
2340     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2341     i->cliptags[i->clippos] = i->tag;
2342     i->clipshapes[i->clippos] = myshapeid;
2343     i->clipdepths[i->clippos] = getNewDepth(dev);
2344     i->clippos++;
2345 }
2346
2347 static void swf_endclip(gfxdevice_t*dev)
2348 {
2349     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2350     if(i->textid>=0)
2351         endtext(dev);
2352     if(i->shapeid>=0)
2353         endshape(dev);
2354
2355     if(!i->clippos) {
2356         msg("<error> Invalid end of clipping region");
2357         return;
2358     }
2359     i->clippos--;
2360     /*swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,
2361             / * clip to depth: * / i->depth <= i->clipdepths[i->clippos]? i->depth : i->depth - 1);
2362     i->depth ++;*/
2363     swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,i->depth);
2364 }
2365 static int gfxline_type(gfxline_t*line)
2366 {
2367     int tmplines=0;
2368     int tmpsplines=0;
2369     int lines=0;
2370     int splines=0;
2371     int haszerosegments=0;
2372     int length=0;
2373     while(line) {
2374         if(line->type == gfx_moveTo) {
2375             tmplines=0;
2376             tmpsplines=0;
2377         } else if(line->type == gfx_lineTo) {
2378             tmplines++;
2379             if(tmplines>lines)
2380                 lines=tmplines;
2381         } else if(line->type == gfx_splineTo) {
2382             tmpsplines++;
2383             if(tmpsplines>lines)
2384                 splines=tmpsplines;
2385         }
2386         length++;
2387         line = line->next;
2388     }
2389     if(length>400)
2390         return 5;
2391     if(lines==0 && splines==0) return 0;
2392     else if(lines==1 && splines==0) return 1;
2393     else if(lines==0 && splines==1) return 2;
2394     else if(splines==0) return 3;
2395     else return 4;
2396 }
2397
2398 static int gfxline_has_dots(gfxline_t*line)
2399 {
2400     int tmplines=0;
2401     double x=0,y=0;
2402     double dist = 0;
2403     int isline = 0;
2404     int short_gap = 0;
2405     while(line) {
2406         if(line->type == gfx_moveTo) {
2407             /* test the length of the preceding line, and assume it is a dot if
2408                it's length is less than 1.0. But *only* if there's a noticable 
2409                gap between the previous line and the next moveTo. (I've come
2410                across a PDF where thousands of "dots" were stringed together,
2411                forming a line) */
2412             int last_short_gap = short_gap;
2413             if((fabs(line->x - x) + fabs(line->y - y)) < 1.0) {
2414                 short_gap = 1;
2415             } else {
2416                 short_gap = 0;
2417             }
2418             if(isline && dist < 1 && !short_gap && !last_short_gap) {
2419                 return 1;
2420             }
2421             dist = 0;
2422             isline = 0;
2423         } else if(line->type == gfx_lineTo) {
2424             dist += fabs(line->x - x) + fabs(line->y - y);
2425             isline = 1;
2426         } else if(line->type == gfx_splineTo) {
2427             dist += fabs(line->sx - x) + fabs(line->sy - y) + 
2428                     fabs(line->x - line->sx) + fabs(line->y - line->sy);
2429             isline = 1;
2430         }
2431         x = line->x;
2432         y = line->y;
2433         line = line->next;
2434     }
2435     if(isline && dist < 1 && !short_gap) {
2436         return 1;
2437     }
2438     return 0;
2439 }
2440
2441 static int gfxline_fix_short_edges(gfxline_t*line)
2442 {
2443     double x,y;
2444     while(line) {
2445         if(line->type == gfx_lineTo) {
2446             if(fabs(line->x - x) + fabs(line->y - y) < 0.01) {
2447                 line->x += 0.01;
2448             }
2449         } else if(line->type == gfx_splineTo) {
2450             if(fabs(line->sx - x) + fabs(line->sy - y) + 
2451                fabs(line->x - line->sx) + fabs(line->y - line->sy) < 0.01) {
2452                 line->x += 0.01;
2453             }
2454         }
2455         x = line->x;
2456         y = line->y;
2457         line = line->next;
2458     }
2459     return 0;
2460 }
2461
2462 static char is_inside_page(gfxdevice_t*dev, gfxcoord_t x, gfxcoord_t y)
2463 {
2464     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2465     if(x<i->min_x || x>i->max_x) return 0;
2466     if(y<i->min_y || y>i->max_y) return 0;
2467     return 1;
2468 }
2469
2470 gfxline_t* gfxline_move(gfxline_t*line, double x, double y)
2471 {
2472     gfxline_t*l = line = gfxline_clone(line);
2473
2474     while(l) {
2475         l->x += x;
2476         l->y += y;
2477         l->sx += x;
2478         l->sy += y;
2479         l = l->next;
2480     }
2481     return line;
2482 }
2483
2484 //#define NORMALIZE_POLYGON_POSITIONS
2485
2486 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)
2487 {
2488     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2489     if(line_is_empty(line))
2490         return;
2491     int type = gfxline_type(line);
2492     int has_dots = gfxline_has_dots(line);
2493     gfxbbox_t r = gfxline_getbbox(line);
2494     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2495
2496     /* TODO: * split line into segments, and perform this check for all segments */
2497
2498     if(i->config_disable_polygon_conversion || /*type>=5 ||*/
2499        (!has_dots &&
2500         (width <= i->config_caplinewidth 
2501         || (cap_style == gfx_capRound && joint_style == gfx_joinRound)
2502         || (cap_style == gfx_capRound && type<=2)))) 
2503     {
2504         // ...
2505     } else {
2506         /* convert line to polygon */
2507         msg("<trace> draw as polygon, type=%d dots=%d", type, has_dots);
2508         if(has_dots)
2509             gfxline_fix_short_edges(line);
2510         /* we need to convert the line into a polygon */
2511         gfxpoly_t* poly = gfxpoly_strokeToPoly(line, width, cap_style, joint_style, miterLimit);
2512         gfxline_t*gfxline = gfxpoly_to_gfxline(poly);
2513         dev->fill(dev, gfxline, color);
2514         gfxline_free(gfxline);
2515         gfxpoly_free(poly);
2516         return;
2517     }
2518
2519     msg("<trace> draw as stroke, type=%d dots=%d", type, has_dots);
2520     endtext(dev);
2521
2522     if(i->config_normalize_polygon_positions) {
2523         endshape(dev);
2524         double startx = 0, starty = 0;
2525         if(line && line->type == gfx_moveTo) {
2526             startx = line->x;
2527             starty = line->y;
2528         }
2529         line = gfxline_move(line, -startx, -starty);
2530         i->shapeposx = (int)(startx*20);
2531         i->shapeposy = (int)(starty*20);
2532     }
2533
2534     swfoutput_setstrokecolor(dev, color->r, color->g, color->b, color->a);
2535     swfoutput_setlinewidth(dev, width);
2536     startshape(dev);
2537     stopFill(dev);
2538     drawgfxline(dev, line, 0);
2539
2540     if(i->config_normalize_polygon_positions) {
2541         free(line); //account for _move
2542     }
2543
2544 }
2545
2546 static void swf_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color)
2547 {
2548     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2549     if(line_is_empty(line))
2550         return;
2551     if(!color->a)
2552         return;
2553     gfxbbox_t r = gfxline_getbbox(line);
2554     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2555
2556     //if(i->chardatapos && i->chardata[i->chardatapos-1].color.a) {
2557     endtext(dev);
2558
2559     if(!i->config_ignoredraworder)
2560         endshape(dev);
2561
2562     if(i->config_normalize_polygon_positions) {
2563         endshape(dev);
2564         double startx = 0, starty = 0;
2565         if(line && line->type == gfx_moveTo) {
2566             startx = line->x;
2567             starty = line->y;
2568         }
2569         line = gfxline_move(line, -startx, -starty);
2570         i->shapeposx = (int)(startx*20);
2571         i->shapeposy = (int)(starty*20);
2572     }
2573
2574     swfoutput_setfillcolor(dev, color->r, color->g, color->b, color->a);
2575     startshape(dev);
2576     startFill(dev);
2577     drawgfxline(dev, line, 1);
2578     
2579     if(i->currentswfid==2 && r.xmin==0 && r.ymin==0 && r.xmax==i->max_x && r.ymax==i->max_y) {
2580         if(i->config_watermark) {
2581             draw_watermark(dev, r, 1);
2582         }
2583     }
2584
2585     msg("<trace> end of swf_fill (shapeid=%d)", i->shapeid);
2586
2587     if(i->config_normalize_polygon_positions) {
2588         free(line); //account for _move
2589     }
2590 }
2591
2592 static GRADIENT* gfxgradient_to_GRADIENT(gfxgradient_t*gradient)
2593 {
2594     int num = 0;
2595     gfxgradient_t*g = gradient;
2596     while(g) {
2597         num++;
2598         g = g->next;
2599     }
2600     GRADIENT* swfgradient = malloc(sizeof(GRADIENT));
2601     swfgradient->num = num;
2602     swfgradient->rgba = malloc(sizeof(swfgradient->rgba[0])*num);
2603     swfgradient->ratios = malloc(sizeof(swfgradient->ratios[0])*num);
2604
2605     g = gradient;
2606     num = 0;
2607     while(g) {
2608         swfgradient->ratios[num] = g->pos*255;
2609         swfgradient->rgba[num] = *(RGBA*)&g->color;
2610         num++;
2611         g = g->next;
2612     }
2613     return swfgradient;
2614 }
2615
2616 static void swf_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
2617 {
2618     if(line_is_empty(line))
2619         return;
2620     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2621     
2622     if(line_is_empty(line))
2623         return;
2624
2625     GRADIENT* swfgradient = gfxgradient_to_GRADIENT(gradient);
2626     if(!swfgradient)
2627         return;
2628   
2629     endshape(dev);
2630     endtext(dev);
2631
2632     double f = type==gfxgradient_radial?4:4;
2633     MATRIX m;
2634     m.sx = (int)(matrix->m00*20*f); m.r1 = (int)(matrix->m10*20*f);
2635     m.r0 = (int)(matrix->m01*20*f); m.sy = (int)(matrix->m11*20*f);
2636     m.tx = (int)(matrix->tx*20);
2637     m.ty = (int)(matrix->ty*20);
2638
2639     /* shape */
2640     int myshapeid = getNewID(dev);
2641     i->tag = swf_InsertTag(i->tag, ST_DEFINESHAPE2);
2642     SHAPE*shape;
2643     swf_ShapeNew(&shape);
2644     int fsid = swf_ShapeAddGradientFillStyle(shape,&m,swfgradient,type==gfxgradient_radial);
2645     swf_SetU16(i->tag, myshapeid);
2646     SRECT r = gfxline_getSWFbbox(line);
2647     r = swf_ClipRect(i->pagebbox, r);
2648     swf_SetRect(i->tag,&r);
2649     swf_SetShapeStyles(i->tag,shape);
2650     swf_ShapeCountBits(shape,NULL,NULL);
2651     swf_SetShapeBits(i->tag,shape);
2652     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2653     i->swflastx = i->swflasty = UNDEFINED_COORD;
2654     drawgfxline(dev, line, 1);
2655     swf_ShapeSetEnd(i->tag);
2656     swf_ShapeFree(shape);
2657
2658     int depth = getNewDepth(dev);
2659     msg("<trace> Placing gradient, shape ID %d, depth %d", myshapeid, depth);
2660     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2661     swf_ObjectPlace(i->tag,myshapeid,depth,&i->page_matrix,NULL,NULL);
2662
2663     swf_FreeGradient(swfgradient);free(swfgradient);
2664 }
2665
2666 static SWFFONT* gfxfont_to_swffont(gfxfont_t*font, const char* id)
2667 {
2668     SWFFONT*swffont = (SWFFONT*)rfx_calloc(sizeof(SWFFONT));
2669     int t;
2670     SRECT bounds = {0,0,0,0};
2671     swffont->id = -1;
2672     swffont->version = 2;
2673     swffont->name = (U8*)strdup(id);
2674     swffont->layout = (SWFLAYOUT*)rfx_calloc(sizeof(SWFLAYOUT));
2675     swffont->layout->ascent = 0;
2676     swffont->layout->descent = 0;
2677     swffont->layout->leading = 0;
2678     swffont->layout->bounds = (SRECT*)rfx_calloc(sizeof(SRECT)*font->num_glyphs);
2679     swffont->encoding = FONT_ENCODING_UNICODE;
2680     swffont->numchars = font->num_glyphs;
2681     swffont->maxascii = font->max_unicode;
2682     swffont->ascii2glyph = (int*)rfx_calloc(sizeof(int)*swffont->maxascii);
2683     swffont->glyph2ascii = (U16*)rfx_calloc(sizeof(U16)*swffont->numchars);
2684     swffont->glyph = (SWFGLYPH*)rfx_calloc(sizeof(SWFGLYPH)*swffont->numchars);
2685     swffont->glyphnames = (char**)rfx_calloc(sizeof(char*)*swffont->numchars);
2686     for(t=0;t<font->max_unicode;t++) {
2687         swffont->ascii2glyph[t] = font->unicode2glyph[t];
2688     }
2689     SRECT max = {0,0,0,0};
2690     for(t=0;t<font->num_glyphs;t++) {
2691         drawer_t draw;
2692         gfxline_t*line;
2693         double advance = 0;
2694         swffont->glyph2ascii[t] = font->glyphs[t].unicode;
2695         if(swffont->glyph2ascii[t] == 0xffff || swffont->glyph2ascii[t] == 0x0000) {
2696             /* flash 8 flashtype requires unique unicode IDs for each character.
2697                We use the Unicode private user area to assign characters, hoping that
2698                the font doesn't contain more than 2048 glyphs */
2699             swffont->glyph2ascii[t] = 0xe000 + (t&0x1fff);
2700         }
2701
2702         if(font->glyphs[t].name) {
2703             swffont->glyphnames[t] = strdup(font->glyphs[t].name);
2704         } else {
2705             swffont->glyphnames[t] = 0;
2706         }
2707         advance = font->glyphs[t].advance;
2708
2709         swf_Shape01DrawerInit(&draw, 0);
2710         line = font->glyphs[t].line;
2711         while(line) {
2712             FPOINT c,to;
2713             c.x = line->sx * GLYPH_SCALE; c.y = line->sy * GLYPH_SCALE;
2714             to.x = line->x * GLYPH_SCALE; to.y = line->y * GLYPH_SCALE;
2715             if(line->type == gfx_moveTo) {
2716                 draw.moveTo(&draw, &to);
2717             } else if(line->type == gfx_lineTo) {
2718                 draw.lineTo(&draw, &to);
2719             } else if(line->type == gfx_splineTo) {
2720                 draw.splineTo(&draw, &c, &to);
2721             }
2722             line = line->next;
2723         }
2724         draw.finish(&draw);
2725         swffont->glyph[t].shape = swf_ShapeDrawerToShape(&draw);
2726
2727         SRECT bbox = swf_ShapeDrawerGetBBox(&draw);
2728         swf_ExpandRect2(&max, &bbox);
2729
2730         swffont->layout->bounds[t] = bbox;
2731             
2732         if(advance<32768.0/20) {
2733             swffont->glyph[t].advance = (int)(advance*20);
2734         } else {
2735             //msg("<warning> Advance value overflow in glyph %d", t);
2736             swffont->glyph[t].advance = 32767;
2737         }
2738
2739         draw.dealloc(&draw);
2740
2741         swf_ExpandRect2(&bounds, &swffont->layout->bounds[t]);
2742     }
2743     for(t=0;t<font->num_glyphs;t++) {
2744         SRECT bbox = swffont->layout->bounds[t];
2745
2746         /* if the glyph doesn't have a bounding box, use the
2747            combined bounding box (necessary e.g. for space characters) */
2748         if(!(bbox.xmin|bbox.ymin|bbox.xmax|bbox.ymax)) {
2749             swffont->layout->bounds[t] = bbox = max;
2750         }
2751         
2752         /* check that the advance value is reasonable, by comparing it
2753            with the bounding box */
2754         if(bbox.xmax>0 && (bbox.xmax*10 < swffont->glyph[t].advance || !swffont->glyph[t].advance)) {
2755             if(swffont->glyph[t].advance)
2756                 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);
2757             swffont->glyph[t].advance = bbox.xmax;
2758         }
2759         //swffont->glyph[t].advance = bbox.xmax - bbox.xmin;
2760     }
2761
2762
2763     /* Flash player will use the advance value from the char, and the ascent/descent values
2764        from the layout for text selection.
2765        ascent will extend the char into negative y direction, from the baseline, while descent
2766        will extend in positive y direction, also from the baseline.
2767        The baseline is defined as the y-position zero 
2768      */
2769
2770     swffont->layout->ascent = -bounds.ymin;
2771     if(swffont->layout->ascent < 0)
2772         swffont->layout->ascent = 0;
2773     swffont->layout->descent = bounds.ymax;
2774     if(swffont->layout->descent < 0)
2775         swffont->layout->descent = 0;
2776     swffont->layout->leading = bounds.ymax - bounds.ymin;
2777
2778     /* if the font has proper ascent/descent values (>0) and those define
2779        greater line spacing that what we estimated from the bounding boxes,
2780        use the font's parameters */
2781     if(font->ascent*20 > swffont->layout->ascent)
2782         swffont->layout->ascent = font->ascent*20;
2783     if(font->descent*20 > swffont->layout->descent)
2784         swffont->layout->descent = font->descent*20;
2785
2786     return swffont;
2787 }
2788
2789 static void swf_addfont(gfxdevice_t*dev, gfxfont_t*font)
2790 {
2791     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2792
2793     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,font->id))
2794         return; // the requested font is the current font
2795     
2796     fontlist_t*last=0,*l = i->fontlist;
2797     while(l) {
2798         last = l;
2799         if(!strcmp((char*)l->swffont->name, font->id)) {
2800             return; // we already know this font
2801         }
2802         l = l->next;
2803     }
2804     l = (fontlist_t*)rfx_calloc(sizeof(fontlist_t));
2805     l->swffont = gfxfont_to_swffont(font, font->id);
2806     l->next = 0;
2807     if(last) {
2808         last->next = l;
2809     } else {
2810         i->fontlist = l;
2811     }
2812     swf_FontSetID(l->swffont, getNewID(i->dev));
2813
2814     if(getScreenLogLevel() >= LOGLEVEL_DEBUG)  {
2815         int iii;
2816         // print font information
2817         msg("<debug> Font %s",font->id);
2818         msg("<debug> |   ID: %d", l->swffont->id);
2819         msg("<debug> |   Version: %d", l->swffont->version);
2820         msg("<debug> |   Name: %s", l->swffont->name);
2821         msg("<debug> |   Numchars: %d", l->swffont->numchars);
2822         msg("<debug> |   Maxascii: %d", l->swffont->maxascii);
2823         msg("<debug> |   Style: %d", l->swffont->style);
2824         msg("<debug> |   Encoding: %d", l->swffont->encoding);
2825         for(iii=0; iii<l->swffont->numchars;iii++) {
2826             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, 
2827                     l->swffont->layout->bounds[iii].xmin/20.0,
2828                     l->swffont->layout->bounds[iii].ymin/20.0,
2829                     l->swffont->layout->bounds[iii].xmax/20.0,
2830                     l->swffont->layout->bounds[iii].ymax/20.0
2831                     );
2832             int t;
2833             for(t=0;t<l->swffont->maxascii;t++) {
2834                 if(l->swffont->ascii2glyph[t] == iii)
2835                     msg("<debug> | - maps to %d",t);
2836             }
2837         }
2838     }
2839 }
2840
2841 static void swf_switchfont(gfxdevice_t*dev, const char*fontid)
2842 {
2843     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2844
2845     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,fontid))
2846         return; // the requested font is the current font
2847     
2848     fontlist_t*l = i->fontlist;
2849     while(l) {
2850         if(!strcmp((char*)l->swffont->name, fontid)) {
2851             i->swffont = l->swffont;
2852             return; //done!
2853         }
2854         l = l->next;
2855     }
2856     msg("<error> Unknown font id: %s", fontid);
2857     return;
2858 }
2859
2860 static void swf_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyph, gfxcolor_t*color, gfxmatrix_t*matrix)
2861 {
2862     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2863     if(!font) {
2864         msg("<error> swf_drawchar called (glyph %d) without font", glyph);
2865         return;
2866     }
2867
2868     if(i->config_drawonlyshapes) {
2869         gfxglyph_t*g = &font->glyphs[glyph];
2870         gfxline_t*line2 = gfxline_clone(g->line);
2871         gfxline_transform(line2, matrix);
2872         dev->fill(dev, line2, color);
2873         gfxline_free(line2);
2874         return;
2875     }
2876
2877     if(!i->swffont || !i->swffont->name || strcmp((char*)i->swffont->name,font->id)) // not equal to current font
2878     {
2879         /* TODO: remove the need for this (enhance getcharacterbbox so that it can cope
2880                  with multiple fonts */
2881         endtext(dev);
2882         swf_switchfont(dev, font->id); // set the current font
2883     }
2884     if(!i->swffont) {
2885         msg("<warning> swf_drawchar: Font is NULL");
2886         return;
2887     }
2888     if(glyph<0 || glyph>=i->swffont->numchars) {
2889         msg("<warning> No character %d in font %s (%d chars)", glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2890         return;
2891     }
2892     
2893     setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 0);
2894     
2895     double det = i->fontmatrix.sx/65536.0 * i->fontmatrix.sy/65536.0 - 
2896                  i->fontmatrix.r0/65536.0 * i->fontmatrix.r1/65536.0;
2897     if(fabs(det) < 0.0005) { 
2898         /* x direction equals y direction- the text is invisible */
2899         msg("<verbose> Not drawing invisible character character %d (det=%f, m=[%f %f;%f %f]\n", glyph, 
2900                 det,
2901                 i->fontmatrix.sx/65536.0, i->fontmatrix.r1/65536.0, 
2902                 i->fontmatrix.r0/65536.0, i->fontmatrix.sy/65536.0);
2903         return;
2904     }
2905
2906     /*if(i->swffont->glyph[glyph].shape->bitlen <= 16) {
2907         msg("<warning> Glyph %d in current charset (%s, %d characters) is empty", 
2908                 glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2909         return 1;
2910     }*/
2911
2912     /* calculate character position with respect to the current font matrix */
2913     double s = 20 * GLYPH_SCALE / det;
2914     double px = matrix->tx - i->fontmatrix.tx/20.0;
2915     double py = matrix->ty - i->fontmatrix.ty/20.0;
2916     int x = (SCOORD)((  px * i->fontmatrix.sy/65536.0 - py * i->fontmatrix.r1/65536.0)*s);
2917     int y = (SCOORD)((- px * i->fontmatrix.r0/65536.0 + py * i->fontmatrix.sx/65536.0)*s);
2918     if(x>32767 || x<-32768 || y>32767 || y<-32768) {
2919         msg("<verbose> Moving character origin to %f %f\n", matrix->tx, matrix->ty);
2920         endtext(dev);
2921         setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 1);
2922         /* since we just moved the char origin to the current char's position, 
2923            it now has the relative position (0,0) */
2924         x = y = 0;
2925     }
2926     
2927     if(i->shapeid>=0)
2928         endshape(dev);
2929     if(i->textid<0)
2930         starttext(dev);
2931
2932     msg("<trace> Drawing char %d in font %d at %d,%d in color %02x%02x%02x%02x", 
2933             glyph, i->swffont->id, x, y, color->r, color->g, color->b, color->a);
2934
2935     putcharacter(dev, i->swffont->id, glyph, x, y, i->current_font_size, *(RGBA*)color);
2936     swf_FontUseGlyph(i->swffont, glyph);
2937     return;
2938 }