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