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