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