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