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