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