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