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