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