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