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