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