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