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