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