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