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