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