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