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