fixed drawonlyshapes implementation
[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                 i->config_internallinkfunction||i->config_externallinkfunction);
1411     }
1412 //    if(i->config_reordertags)
1413 //      swf_Optimize(i->swf);
1414 }
1415
1416 int swfresult_save(gfxresult_t*gfx, const char*filename)
1417 {
1418     SWF*swf = (SWF*)gfx->internal;
1419     int fi;
1420     if(filename)
1421      fi = open(filename, O_BINARY|O_CREAT|O_TRUNC|O_WRONLY, 0777);
1422     else
1423      fi = 1; // stdout
1424     
1425     if(fi<=0) {
1426         msg("<fatal> Could not create \"%s\". ", FIXNULL(filename));
1427         return -1;
1428     }
1429     
1430     if FAILED(swf_WriteSWF(fi,swf)) 
1431         msg("<error> WriteSWF() failed.\n");
1432
1433     if(filename)
1434      close(fi);
1435     return 0;
1436 }
1437 void* swfresult_get(gfxresult_t*gfx, const char*name)
1438 {
1439     SWF*swf = (SWF*)gfx->internal;
1440     if(!strcmp(name, "swf")) {
1441         return (void*)swf_CopySWF(swf);
1442     } else if(!strcmp(name, "xmin")) {
1443         return (void*)(swf->movieSize.xmin/20);
1444     } else if(!strcmp(name, "ymin")) {
1445         return (void*)(swf->movieSize.ymin/20);
1446     } else if(!strcmp(name, "xmax")) {
1447         return (void*)(swf->movieSize.xmax/20);
1448     } else if(!strcmp(name, "ymax")) {
1449         return (void*)(swf->movieSize.ymax/20);
1450     } else if(!strcmp(name, "width")) {
1451         return (void*)((swf->movieSize.xmax - swf->movieSize.xmin)/20);
1452     } else if(!strcmp(name, "height")) {
1453         return (void*)((swf->movieSize.ymax - swf->movieSize.ymin)/20);
1454     }
1455     return 0;
1456 }
1457 void swfresult_destroy(gfxresult_t*gfx)
1458 {
1459     if(gfx->internal) {
1460         swf_FreeTags((SWF*)gfx->internal);
1461         free(gfx->internal);
1462         gfx->internal = 0;
1463     }
1464     memset(gfx, 0, sizeof(gfxresult_t));
1465     free(gfx);
1466 }
1467
1468 static void swfoutput_destroy(gfxdevice_t* dev);
1469
1470 gfxresult_t* swf_finish(gfxdevice_t* dev)
1471 {
1472     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1473     gfxresult_t*result;
1474
1475     if(i->config_linktarget) {
1476         free(i->config_linktarget);
1477         i->config_linktarget = 0;
1478     }
1479
1480     swfoutput_finalize(dev);
1481     SWF* swf = i->swf;i->swf = 0;
1482     swfoutput_destroy(dev);
1483
1484     result = (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
1485     result->internal = swf;
1486     result->save = swfresult_save;
1487     result->write = 0;
1488     result->get = swfresult_get;
1489     result->destroy = swfresult_destroy;
1490     return result;
1491 }
1492
1493 /* Perform cleaning up */
1494 static void swfoutput_destroy(gfxdevice_t* dev) 
1495 {
1496     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1497     if(!i) {
1498         /* not initialized yet- nothing to destroy */
1499         return;
1500     }
1501
1502     fontlist_t *tmp,*iterator = i->fontlist;
1503     while(iterator) {
1504         if(iterator->swffont) {
1505             swf_FontFree(iterator->swffont);iterator->swffont=0;
1506         }
1507         tmp = iterator;
1508         iterator = iterator->next;
1509         free(tmp);
1510     }
1511     if(i->swf) {swf_FreeTags(i->swf);free(i->swf);i->swf = 0;}
1512
1513     free(i);i=0;
1514     memset(dev, 0, sizeof(gfxdevice_t));
1515 }
1516
1517 static void swfoutput_setstrokecolor(gfxdevice_t* dev, U8 r, U8 g, U8 b, U8 a)
1518 {
1519     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1520     if(i->strokergb.r == r &&
1521        i->strokergb.g == g &&
1522        i->strokergb.b == b &&
1523        i->strokergb.a == a) return;
1524
1525     if(i->shapeid>=0)
1526      endshape(dev);
1527     i->strokergb.r = r;
1528     i->strokergb.g = g;
1529     i->strokergb.b = b;
1530     i->strokergb.a = a;
1531 }
1532
1533 //#define ROUND_UP 19
1534 //#define ROUND_UP 10
1535
1536 static void swfoutput_setlinewidth(gfxdevice_t*dev, double _linewidth)
1537 {
1538     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1539     if(i->linewidth == (U16)(_linewidth*20+19.0/20.0))
1540         return;
1541     if(i->shapeid>=0)
1542         endshape(dev);
1543     i->linewidth = (U16)(_linewidth*20+19.0/20.0);
1544 }
1545
1546
1547 static void drawlink(gfxdevice_t*dev, ActionTAG*,ActionTAG*, gfxline_t*points, char mouseover, const char*url);
1548 static void swfoutput_namedlink(gfxdevice_t*dev, char*name, gfxline_t*points);
1549 static void swfoutput_linktopage(gfxdevice_t*dev, int page, gfxline_t*points);
1550 static void swfoutput_linktourl(gfxdevice_t*dev, const char*url, gfxline_t*points);
1551
1552 /*void swfoutput_drawlink(gfxdevice_t*dev, char*url, gfxline_t*points)
1553 {
1554     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1555     dev->drawlink(dev, points, url);
1556 }*/
1557
1558 void swf_drawlink(gfxdevice_t*dev, gfxline_t*points, const char*url)
1559 {
1560     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1561
1562     if(!strncmp("http://pdf2swf:", url, 15)) {
1563         char*tmp = strdup(url);
1564         int l = strlen(tmp);
1565         if(tmp[l-1] == '/')
1566            tmp[l-1] = 0;
1567         swfoutput_namedlink(dev, tmp+15, points);
1568         free(tmp);
1569         return;
1570     } else if(!strncmp("page", url, 4)) {
1571         int t, nodigit=0;
1572         for(t=4;url[t];t++)
1573             if(url[t]<'0' || url[t]>'9')
1574                 nodigit = 1;
1575         if(!nodigit) {
1576             int page = atoi(&url[4]);
1577             if(page<0) page = 0;
1578             swfoutput_linktopage(dev, page, points);
1579         }
1580     } else {
1581         swfoutput_linktourl(dev, url, points);
1582     }
1583 }
1584 void swfoutput_linktourl(gfxdevice_t*dev, const char*url, gfxline_t*points)
1585 {
1586     ActionTAG* actions = 0;
1587     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1588     if(i->shapeid>=0)
1589         endshape(dev);
1590     if(i->textid>=0)
1591         endtext(dev);
1592
1593     /* TODO: escape special characters in url */
1594     
1595     if(i->config_externallinkfunction && i->config_flashversion<=8) {
1596         actions = action_PushString(actions, url); //parameter
1597         actions = action_PushInt(actions, 1); //number of parameters (1)
1598         actions = action_PushString(actions, i->config_externallinkfunction); //function name
1599         actions = action_CallFunction(actions);
1600     } else if(!i->config_linktarget) {
1601         if(!i->config_opennewwindow)
1602           actions = action_GetUrl(actions, url, "_parent");
1603         else
1604           actions = action_GetUrl(actions, url, "_this");
1605     } else {
1606         actions = action_GetUrl(actions, url, i->config_linktarget);
1607     }
1608     actions = action_End(actions);
1609    
1610     drawlink(dev, actions, 0, points, 0, url);
1611 }
1612 void swfoutput_linktopage(gfxdevice_t*dev, int page, gfxline_t*points)
1613 {
1614     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1615     ActionTAG* actions = 0;
1616
1617     if(i->shapeid>=0)
1618         endshape(dev);
1619     if(i->textid>=0)
1620         endtext(dev);
1621   
1622     if(!i->config_internallinkfunction || i->config_flashversion>=9) {
1623         actions = action_GotoFrame(actions, page-1);
1624         actions = action_End(actions);
1625     } else {
1626         actions = action_PushInt(actions, page); //parameter
1627         actions = action_PushInt(actions, 1); //number of parameters (1)
1628         actions = action_PushString(actions, i->config_internallinkfunction); //function name
1629         actions = action_CallFunction(actions);
1630         actions = action_End(actions);
1631     }
1632
1633     char name[80];
1634     sprintf(name, "page%d", page);
1635
1636     drawlink(dev, actions, 0, points, 0, name);
1637 }
1638
1639 /* Named Links (a.k.a. Acrobatmenu) are used to implement various gadgets
1640    of the viewer objects, like subtitles, index elements etc.
1641 */
1642 void swfoutput_namedlink(gfxdevice_t*dev, char*name, gfxline_t*points)
1643 {
1644     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1645     ActionTAG *actions1,*actions2;
1646     char*tmp = strdup(name);
1647     char mouseover = 1;
1648
1649     if(i->shapeid>=0)
1650         endshape(dev);
1651     if(i->textid>=0)
1652         endtext(dev);
1653
1654     if(!strncmp(tmp, "call:", 5))
1655     {
1656         char*x = strchr(&tmp[5], ':');
1657         if(!x) {
1658             actions1 = action_PushInt(0, 0); //number of parameters (0)
1659             actions1 = action_PushString(actions1, &tmp[5]); //function name
1660             actions1 = action_CallFunction(actions1);
1661             actions1 = action_End(actions1);
1662         } else {
1663             *x = 0;
1664             actions1 = action_PushString(0, x+1); //parameter
1665             actions1 = action_PushInt(actions1, 1); //number of parameters (1)
1666             actions1 = action_PushString(actions1, &tmp[5]); //function name
1667             actions1 = action_CallFunction(actions1);
1668             actions1 = action_End(actions1);
1669         }
1670         actions2 = action_End(0);
1671         mouseover = 0;
1672     }
1673     else
1674     {
1675         actions1 = action_PushString(0, "/:subtitle");
1676         actions1 = action_PushString(actions1, name);
1677         actions1 = action_SetVariable(actions1);
1678         actions1 = action_End(actions1);
1679
1680         actions2 = action_PushString(0, "/:subtitle");
1681         actions2 = action_PushString(actions2, "");
1682         actions2 = action_SetVariable(actions2);
1683         actions2 = action_End(actions2);
1684     }
1685
1686     drawlink(dev, actions1, actions2, points, mouseover, name);
1687
1688     swf_ActionFree(actions1);
1689     swf_ActionFree(actions2);
1690     free(tmp);
1691 }
1692
1693 static void drawgfxline(gfxdevice_t*dev, gfxline_t*line, int fill)
1694 {
1695     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1696     gfxcoord_t lastx=0,lasty=0,px=0,py=0;
1697     char lastwasmoveto;
1698     int lines= 0, splines=0;
1699
1700     i->fill = fill;
1701
1702     while(1) {
1703         if(!line)
1704             break;
1705         /* check whether the next segment is zero */
1706         if(line->type == gfx_moveTo) {
1707             moveto(dev, i->tag, line->x, line->y);
1708             px = lastx = line->x;
1709             py = lasty = line->y;
1710             lastwasmoveto = 1;
1711         } if(line->type == gfx_lineTo) {
1712             lineto(dev, i->tag, line->x, line->y);
1713             px = line->x;
1714             py = line->y;
1715             lastwasmoveto = 0;
1716             lines++;
1717         } else if(line->type == gfx_splineTo) {
1718             plotxy_t s,p;
1719             s.x = line->sx;p.x = line->x;
1720             s.y = line->sy;p.y = line->y;
1721             splineto(dev, i->tag, s, p);
1722             px = line->x;
1723             py = line->y;
1724             lastwasmoveto = 0;
1725             splines++;
1726         }
1727         line = line->next;
1728     }
1729     msg("<trace> drawgfxline, %d lines, %d splines", lines, splines);
1730 }
1731
1732
1733 static void drawlink(gfxdevice_t*dev, ActionTAG*actions1, ActionTAG*actions2, gfxline_t*points, char mouseover, const char*url)
1734 {
1735     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1736     RGBA rgb;
1737     SRECT r;
1738     int lsid=0;
1739     int fsid;
1740     int myshapeid;
1741     int myshapeid2;
1742     double posx = 0;
1743     double posy = 0;
1744     int buttonid = getNewID(dev);
1745     gfxbbox_t bbox = gfxline_getbbox(points);
1746     
1747     i->hasbuttons = 1;
1748
1749     /* shape */
1750     myshapeid = getNewID(dev);
1751     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1752     swf_ShapeNew(&i->shape);
1753     rgb.r = rgb.b = rgb.a = rgb.g = 0; 
1754     fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
1755     swf_SetU16(i->tag, myshapeid);
1756     r.xmin = (int)(bbox.xmin*20);
1757     r.ymin = (int)(bbox.ymin*20);
1758     r.xmax = (int)(bbox.xmax*20);
1759     r.ymax = (int)(bbox.ymax*20);
1760     r = swf_ClipRect(i->pagebbox, r);
1761     swf_SetRect(i->tag,&r);
1762     swf_SetShapeStyles(i->tag,i->shape);
1763     swf_ShapeCountBits(i->shape,NULL,NULL);
1764     swf_SetShapeBits(i->tag,i->shape);
1765     swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
1766     i->swflastx = i->swflasty = 0;
1767     drawgfxline(dev, points, 1);
1768     swf_ShapeSetEnd(i->tag);
1769
1770     /* shape2 */
1771     myshapeid2 = getNewID(dev);
1772     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1773     swf_ShapeNew(&i->shape);
1774     
1775     rgb = i->config_linkcolor;
1776
1777     fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
1778     swf_SetU16(i->tag, myshapeid2);
1779     r.xmin = (int)(bbox.xmin*20);
1780     r.ymin = (int)(bbox.ymin*20);
1781     r.xmax = (int)(bbox.xmax*20);
1782     r.ymax = (int)(bbox.ymax*20);
1783     r = swf_ClipRect(i->pagebbox, r);
1784     swf_SetRect(i->tag,&r);
1785     swf_SetShapeStyles(i->tag,i->shape);
1786     swf_ShapeCountBits(i->shape,NULL,NULL);
1787     swf_SetShapeBits(i->tag,i->shape);
1788     swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
1789     i->swflastx = i->swflasty = 0;
1790     drawgfxline(dev, points, 1);
1791     swf_ShapeSetEnd(i->tag);
1792
1793     if(!mouseover)
1794     {
1795         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON);
1796         swf_SetU16(i->tag,buttonid); //id
1797         swf_ButtonSetFlags(i->tag, 0); //menu=no
1798         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1799         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1800         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1801         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1802         swf_SetU8(i->tag,0);
1803         swf_ActionSet(i->tag,actions1);
1804         swf_SetU8(i->tag,0);
1805     }
1806     else
1807     {
1808         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON2);
1809         swf_SetU16(i->tag,buttonid); //id
1810         swf_ButtonSetFlags(i->tag, 0); //menu=no
1811         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1812         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1813         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1814         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1815         swf_SetU8(i->tag,0); // end of button records
1816         swf_ButtonSetCondition(i->tag, BC_IDLE_OVERUP);
1817         swf_ActionSet(i->tag,actions1);
1818         if(actions2) {
1819             swf_ButtonSetCondition(i->tag, BC_OVERUP_IDLE);
1820             swf_ActionSet(i->tag,actions2);
1821             swf_SetU8(i->tag,0);
1822             swf_ButtonPostProcess(i->tag, 2);
1823         } else {
1824             swf_SetU8(i->tag,0);
1825             swf_ButtonPostProcess(i->tag, 1);
1826         }
1827     }
1828     char buf[80];
1829     const char* name = 0;
1830     if(i->config_linknameurl) {
1831         name = url;
1832     } else {
1833         name = buf;
1834         sprintf(buf, "button%d", buttonid);
1835     }
1836     
1837     msg("<trace> Placing link ID %d", buttonid);
1838     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
1839
1840     if(posx!=0 || posy!=0) {
1841         SPOINT p;
1842         p.x = (int)(posx*20);
1843         p.y = (int)(posy*20);
1844         p = swf_TurnPoint(p, &i->page_matrix);
1845         MATRIX m;
1846         m = i->page_matrix;
1847         m.tx = p.x;
1848         m.ty = p.y;
1849         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&m,0,name);
1850     } else {
1851         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&i->page_matrix,0,name);
1852     }
1853 }
1854
1855       
1856 ///////////
1857 /*
1858 for(t=0;t<picpos;t++)
1859       {
1860           if(pic_xids[t] == xid &&
1861              pic_yids[t] == yid) {
1862               width = pic_width[t];
1863               height = pic_height[t];
1864               found = t;break;
1865           }
1866       }
1867           pic_ids[picpos] = swfoutput_drawimagelosslessN(&output, pic, pal, width, height, x1,y1,x2,y2,x3,y3,x4,y4, numpalette);
1868           pic_xids[picpos] = xid;
1869           pic_yids[picpos] = yid;
1870           pic_width[picpos] = width;
1871           pic_height[picpos] = height;
1872           if(picpos<1024)
1873               picpos++;
1874             pic[width*y+x] = buf[0];
1875             xid+=x*buf[0]+1;
1876             yid+=y*buf[0]*3+1;
1877       
1878             xid += pal[1].r*3 + pal[1].g*11 + pal[1].b*17;
1879       yid += pal[1].r*7 + pal[1].g*5 + pal[1].b*23;
1880       
1881       int xid = 0;
1882       int yid = 0;
1883           xid += x*r+x*b*3+x*g*7+x*a*11;
1884           yid += y*r*3+y*b*17+y*g*19+y*a*11;
1885       int t,found = -1;
1886       for(t=0;t<picpos;t++)
1887       {
1888           if(pic_xids[t] == xid &&
1889              pic_yids[t] == yid) {
1890               found = t;break;
1891           }
1892       }
1893       if(found<0) {
1894 */
1895 ///////////
1896
1897
1898 int swf_setparameter(gfxdevice_t*dev, const char*name, const char*value)
1899 {
1900     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1901
1902     msg("<trace> swfdevice: %s=%s", name, value);
1903     if(!strcmp(name, "jpegsubpixels")) {
1904         i->config_jpegsubpixels = atof(value);
1905     } else if(!strcmp(name, "ppmsubpixels")) {
1906         i->config_ppmsubpixels = atof(value);
1907     } else if(!strcmp(name, "subpixels")) {
1908         i->config_ppmsubpixels = i->config_jpegsubpixels = atof(value);
1909     } else if(!strcmp(name, "drawonlyshapes")) {
1910         i->config_drawonlyshapes = atoi(value);
1911     } else if(!strcmp(name, "ignoredraworder")) {
1912         i->config_ignoredraworder = atoi(value);
1913     } else if(!strcmp(name, "mark")) {
1914         if(!value || !value[0]) {
1915             if(i->mark) free(i->mark);
1916             i->mark = 0;
1917         } else {
1918             int t;
1919             i->mark = strdup("...");
1920             for(t=0;t<3;t++) if(value[t]) i->mark[t] = value[t];
1921         }
1922     } else if(!strcmp(name, "filloverlap")) {
1923         i->config_filloverlap = atoi(value);
1924     } else if(!strcmp(name, "linksopennewwindow")) {
1925         i->config_opennewwindow = atoi(value);
1926     } else if(!strcmp(name, "opennewwindow")) {
1927         i->config_opennewwindow = atoi(value);
1928     } else if(!strcmp(name, "storeallcharacters")) {
1929         i->config_storeallcharacters = atoi(value);
1930     } else if(!strcmp(name, "enablezlib")) {
1931         i->config_enablezlib = atoi(value);
1932     } else if(!strcmp(name, "bboxvars")) {
1933         i->config_bboxvars = atoi(value);
1934     } else if(!strcmp(name, "frameresets")) {
1935         i->config_frameresets = atoi(value);
1936     } else if(!strcmp(name, "showclipshapes")) {
1937         i->config_showclipshapes = atoi(value);
1938     } else if(!strcmp(name, "reordertags")) {
1939         i->config_reordertags = atoi(value);
1940     } else if(!strcmp(name, "internallinkfunction")) {
1941         i->config_internallinkfunction = strdup(value);
1942     } else if(!strcmp(name, "externallinkfunction")) {
1943         i->config_externallinkfunction = strdup(value);
1944     } else if(!strcmp(name, "linkfunction")) { //sets both internallinkfunction and externallinkfunction
1945         i->config_internallinkfunction = strdup(value);
1946         i->config_externallinkfunction = strdup(value);
1947     } else if(!strcmp(name, "disable_polygon_conversion")) {
1948         i->config_disable_polygon_conversion = atoi(value);
1949     } else if(!strcmp(name, "normalize_polygon_positions")) {
1950         i->config_normalize_polygon_positions = atoi(value);
1951     } else if(!strcmp(name, "wxwindowparams")) {
1952         i->config_watermark = atoi(value);
1953     } else if(!strcmp(name, "insertstop")) {
1954         i->config_insertstoptag = atoi(value);
1955     } else if(!strcmp(name, "protect")) {
1956         i->config_protect = atoi(value);
1957         if(i->config_protect && i->tag) {
1958             i->tag = swf_InsertTag(i->tag, ST_PROTECT);
1959         }
1960     } else if(!strcmp(name, "flashversion")) {
1961         i->config_flashversion = atoi(value);
1962         if(i->swf) {
1963             i->swf->fileVersion = i->config_flashversion;
1964         }
1965     } else if(!strcmp(name, "framerate")) {
1966         i->config_framerate = atof(value);
1967         if(i->swf) {
1968             i->swf->frameRate = i->config_framerate*0x100;
1969         }
1970     } else if(!strcmp(name, "minlinewidth")) {
1971         i->config_minlinewidth = atof(value);
1972     } else if(!strcmp(name, "caplinewidth")) {
1973         i->config_caplinewidth = atof(value);
1974     } else if(!strcmp(name, "linktarget")) {
1975         i->config_linktarget = strdup(value);
1976     } else if(!strcmp(name, "dumpfonts")) {
1977         i->config_dumpfonts = atoi(value);
1978     } else if(!strcmp(name, "animate")) {
1979         i->config_animate = atoi(value);
1980     } else if(!strcmp(name, "simpleviewer")) {
1981         i->config_simpleviewer = atoi(value);
1982     } else if(!strcmp(name, "next_bitmap_is_jpeg")) {
1983         i->jpeg = 1;
1984     } else if(!strcmp(name, "jpegquality")) {
1985         int val = atoi(value);
1986         if(val<0) val=0;
1987         if(val>101) val=101;
1988         i->config_jpegquality = val;
1989     } else if(!strcmp(name, "splinequality")) {
1990         int v = atoi(value);
1991         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
1992         if(v<1) v = 1;
1993         i->config_splinemaxerror = v;
1994     } else if(!strcmp(name, "fontquality")) {
1995         int v = atoi(value);
1996         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
1997         if(v<1) v = 1;
1998         i->config_fontsplinemaxerror = v;
1999     } else if(!strcmp(name, "linkcolor")) {
2000         if(strlen(value)!=8) {
2001             fprintf(stderr, "Unknown format for option 'linkcolor'. (%s <-> RRGGBBAA)\n", value);
2002             return 1;
2003         }
2004 #       define NIBBLE(s) (((s)>='0' && (s)<='9')?((s)-'0'):((s)&0x0f)+9)
2005         i->config_linkcolor.r = NIBBLE(value[0])<<4 | NIBBLE(value[1]);
2006         i->config_linkcolor.g = NIBBLE(value[2])<<4 | NIBBLE(value[3]);
2007         i->config_linkcolor.b = NIBBLE(value[4])<<4 | NIBBLE(value[5]);
2008         i->config_linkcolor.a = NIBBLE(value[6])<<4 | NIBBLE(value[7]);
2009     } else if(!strcmp(name, "help")) {
2010         printf("\nSWF layer options:\n");
2011         printf("jpegsubpixels=<pixels>      resolution adjustment for jpeg images (same as jpegdpi, but in pixels)\n");
2012         printf("ppmsubpixels=<pixels        resolution adjustment for  lossless images (same as ppmdpi, but in pixels)\n");
2013         printf("subpixels=<pixels>          shortcut for setting both jpegsubpixels and ppmsubpixels\n");
2014         printf("drawonlyshapes              convert everything to shapes (currently broken)\n");
2015         printf("ignoredraworder             allow to perform a few optimizations for creating smaller SWFs\n");
2016         printf("linksopennewwindow          make links open a new browser window\n");
2017         printf("linktarget                  target window name of new links\n");
2018         printf("linkcolor=<color)           color of links (format: RRGGBBAA)\n");
2019         printf("linknameurl                 Link buttons will be named like the URL they refer to (handy for iterating through links with actionscript)\n");
2020         printf("storeallcharacters          don't reduce the fonts to used characters in the output file\n");
2021         printf("enablezlib                  switch on zlib compression (also done if flashversion>=7)\n");
2022         printf("bboxvars                    store the bounding box of the SWF file in actionscript variables\n");
2023         printf("reordertags=0/1             (default: 1) perform some tag optimizations\n");
2024         printf("internallinkfunction=<name> when the user clicks a internal link (to a different page) in the converted file, this actionscript function is called\n");
2025         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");
2026         printf("disable_polygon_conversion  never convert strokes to polygons (will remove capstyles and joint styles)\n");
2027         printf("caplinewidth=<width>        the minimum thichness a line needs to have so that capstyles become visible (and are converted)\n");
2028         printf("insertstop                  put an ActionScript \"STOP\" tag in every frame\n");
2029         printf("protect                     add a \"protect\" tag to the file, to prevent loading in the Flash editor\n");
2030         printf("flashversion=<version>      the SWF fileversion (6)\n");
2031         printf("framerate=<fps>             SWF framerate\n");
2032         printf("minlinewidth=<width>        convert horizontal/vertical boxes smaller than this width to lines (0.05) \n");
2033         printf("simpleviewer                Add next/previous buttons to the SWF\n");
2034         printf("animate                     insert a showframe tag after each placeobject (animate draw order of PDF files)\n");
2035         printf("jpegquality=<quality>       set compression quality of jpeg images\n");
2036         printf("splinequality=<value>       Set the quality of spline convertion to value (0-100, default: 100).\n");
2037     } else {
2038         return 0;
2039     }
2040     return 1;
2041 }
2042
2043 // --------------------------------------------------------------------
2044
2045 static CXFORM gfxcxform_to_cxform(gfxcxform_t* c)
2046 {
2047     CXFORM cx;
2048     swf_GetCXForm(0, &cx, 1);
2049     if(!c)
2050         return cx;
2051     if(c->rg!=0 || c->rb!=0 || c->ra!=0 ||
2052        c->gr!=0 || c->gb!=0 || c->ga!=0 ||
2053        c->br!=0 || c->bg!=0 || c->ba!=0 ||
2054        c->ar!=0 || c->ag!=0 || c->ab!=0)
2055         msg("<warning> CXForm not SWF-compatible");
2056
2057     cx.a0 = (S16)(c->aa*256);
2058     cx.r0 = (S16)(c->rr*256);
2059     cx.g0 = (S16)(c->gg*256);
2060     cx.b0 = (S16)(c->bb*256);
2061     cx.a1 = c->ta;
2062     cx.r1 = c->tr;
2063     cx.g1 = c->tg;
2064     cx.b1 = c->tb;
2065     return cx;
2066 }
2067
2068 /* TODO */
2069 static int imageInCache(gfxdevice_t*dev, void*data, int width, int height)
2070 {
2071     return -1;
2072 }
2073 static void addImageToCache(gfxdevice_t*dev, void*data, int width, int height)
2074 {
2075 }
2076     
2077 static int add_image(swfoutput_internal*i, gfximage_t*img, int targetwidth, int targetheight, int* newwidth, int* newheight)
2078 {
2079     gfxdevice_t*dev = i->dev;
2080     RGBA*newpic = 0;
2081     RGBA*mem = (RGBA*)img->data;
2082     
2083     int sizex = img->width;
2084     int sizey = img->height;
2085     int is_jpeg = i->jpeg;
2086     i->jpeg = 0;
2087
2088     int newsizex=sizex, newsizey=sizey;
2089
2090     /// {
2091     if(is_jpeg && i->config_jpegsubpixels) {
2092         newsizex = (int)(targetwidth*i->config_jpegsubpixels + 0.5);
2093         newsizey = (int)(targetheight*i->config_jpegsubpixels + 0.5);
2094     } else if(!is_jpeg && i->config_ppmsubpixels) {
2095         newsizex = (int)(targetwidth*i->config_ppmsubpixels + 0.5);
2096         newsizey = (int)(targetheight*i->config_ppmsubpixels + 0.5);
2097     }
2098     /// }
2099
2100     if(sizex<=0 || sizey<=0)
2101         return -1;
2102     if(newsizex<=0)
2103         newsizex = 1;
2104     if(newsizey<=0)
2105         newsizey = 1;
2106
2107     /* TODO: cache images */
2108     
2109     if(newsizex<sizex || newsizey<sizey) {
2110         msg("<verbose> Scaling %dx%d image to %dx%d", sizex, sizey, newsizex, newsizey);
2111         newpic = swf_ImageScale(mem, sizex, sizey, newsizex, newsizey);
2112         *newwidth = sizex = newsizex;
2113         *newheight  = sizey = newsizey;
2114         mem = newpic;
2115     } else {
2116         *newwidth = newsizex = sizex;
2117         *newheight = newsizey  = sizey;
2118     }
2119
2120     int num_colors = swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,0);
2121     int has_alpha = swf_ImageHasAlpha(mem,sizex,sizey);
2122     
2123     msg("<verbose> Drawing %dx%d %s%simage (id %d) at size %dx%d (%dx%d), %s%d colors",
2124             sizex, sizey, 
2125             has_alpha?(has_alpha==2?"semi-transparent ":"transparent "):"", 
2126             is_jpeg?"jpeg-":"", i->currentswfid+1,
2127             newsizex, newsizey,
2128             targetwidth, targetheight,
2129             /*newsizex, newsizey,*/
2130             num_colors>256?">":"", num_colors>256?256:num_colors);
2131
2132     /*RGBA* pal = (RGBA*)rfx_alloc(sizeof(RGBA)*num_colors);
2133     swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,pal);
2134     int t;
2135     for(t=0;t<num_colors;t++) {
2136         printf("%02x%02x%02x%02x ",
2137                 pal[t].r, pal[t].g, pal[t].b, pal[t].a);
2138         if((t&7)==7)
2139             printf("\n");
2140     }
2141     printf("\n");*/
2142
2143     int bitid = -1;
2144     int cacheid = imageInCache(dev, mem, sizex, sizey);
2145
2146     if(cacheid<=0) {
2147         bitid = getNewID(dev);
2148
2149         i->tag = swf_AddImage(i->tag, bitid, mem, sizex, sizey, i->config_jpegquality);
2150         addImageToCache(dev, mem, sizex, sizey);
2151     } else {
2152         bitid = cacheid;
2153     }
2154
2155     if(newpic)
2156         free(newpic);
2157     return bitid;
2158 }
2159
2160 static SRECT gfxline_getSWFbbox(gfxline_t*line)
2161 {
2162     gfxbbox_t bbox = gfxline_getbbox(line);
2163     SRECT r;
2164     r.xmin = (int)(bbox.xmin*20);
2165     r.ymin = (int)(bbox.ymin*20);
2166     r.xmax = (int)(bbox.xmax*20);
2167     r.ymax = (int)(bbox.ymax*20);
2168     return r;
2169 }
2170
2171 int line_is_empty(gfxline_t*line)
2172 {
2173     while(line) {
2174         if(line->type != gfx_moveTo)
2175             return 0;
2176         line = line->next;
2177     }
2178     return 1;
2179 }
2180
2181 static void swf_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
2182 {
2183     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2184     
2185     if(line_is_empty(line))
2186         return;
2187
2188     endshape(dev);
2189     endtext(dev);
2190
2191     int targetx = (int)(sqrt(matrix->m00*matrix->m00 + matrix->m01*matrix->m01)*img->width);
2192     int targety = (int)(sqrt(matrix->m10*matrix->m10 + matrix->m11*matrix->m11)*img->height);
2193
2194     int newwidth=0,newheight=0;
2195     int bitid = add_image(i, img, targetx, targety, &newwidth, &newheight);
2196     if(bitid<0)
2197         return;
2198     double fx = (double)img->width / (double)newwidth;
2199     double fy = (double)img->height / (double)newheight;
2200
2201     MATRIX m;
2202     m.sx = (int)(65536*20*matrix->m00*fx); m.r1 = (int)(65536*20*matrix->m10*fy);
2203     m.r0 = (int)(65536*20*matrix->m01*fx); m.sy = (int)(65536*20*matrix->m11*fy);
2204     m.tx = (int)(matrix->tx*20);
2205     m.ty = (int)(matrix->ty*20);
2206   
2207     /* shape */
2208     int myshapeid = getNewID(dev);
2209     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE);
2210     SHAPE*shape;
2211     swf_ShapeNew(&shape);
2212     int fsid = swf_ShapeAddBitmapFillStyle(shape,&m,bitid,1);
2213     swf_SetU16(i->tag, myshapeid);
2214     SRECT r = gfxline_getSWFbbox(line);
2215     r = swf_ClipRect(i->pagebbox, r);
2216     swf_SetRect(i->tag,&r);
2217     swf_SetShapeStyles(i->tag,shape);
2218     swf_ShapeCountBits(shape,NULL,NULL);
2219     swf_SetShapeBits(i->tag,shape);
2220     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2221     i->swflastx = i->swflasty = UNDEFINED_COORD;
2222     drawgfxline(dev, line, 1);
2223     swf_ShapeSetEnd(i->tag);
2224     swf_ShapeFree(shape);
2225
2226     msg("<trace> Placing image, shape ID %d, bitmap ID %d", myshapeid, bitid);
2227     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2228     CXFORM cxform2 = gfxcxform_to_cxform(cxform);
2229     swf_ObjectPlace(i->tag,myshapeid,getNewDepth(dev),&i->page_matrix,&cxform2,NULL);
2230 }
2231
2232 static RGBA col_black = {255,0,0,0};
2233
2234 static void drawoutline(gfxdevice_t*dev, gfxline_t*line)
2235 {
2236     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2237
2238     int myshapeid = getNewID(dev);
2239     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2240
2241     SHAPE*shape;
2242     swf_ShapeNew(&shape);
2243     int lsid = swf_ShapeAddLineStyle(shape,1,&col_black);
2244
2245     swf_SetU16(i->tag,myshapeid);
2246     SRECT r = gfxline_getSWFbbox(line);
2247     r = swf_ClipRect(i->pagebbox, r);
2248     swf_SetRect(i->tag,&r);
2249     swf_SetShapeStyles(i->tag,shape);
2250     swf_ShapeCountBits(shape,NULL,NULL);
2251     swf_SetShapeBits(i->tag,shape);
2252     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,lsid,0,0);
2253     drawgfxline(dev, line, 1);
2254     swf_ShapeSetEnd(i->tag);
2255     swf_ShapeFree(shape);
2256         
2257     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2258     swf_ObjectPlace(i->tag, myshapeid, getNewDepth(dev), 0,0,0);
2259 }
2260
2261 static void swf_startclip(gfxdevice_t*dev, gfxline_t*line)
2262 {
2263     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2264
2265     endtext(dev);
2266     endshape(dev);
2267
2268     if(i->clippos >= 127)
2269     {
2270         msg("<warning> Too many clip levels.");
2271         i->clippos --;
2272     } 
2273
2274     if(i->config_showclipshapes)
2275         drawoutline(dev, line);
2276
2277     int myshapeid = getNewID(dev);
2278     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2279     RGBA col;
2280     memset(&col, 0, sizeof(RGBA));
2281     col.a = 255;
2282     SHAPE*shape;
2283     swf_ShapeNew(&shape);
2284     int fsid = swf_ShapeAddSolidFillStyle(shape,&col);
2285     if(i->mark) {
2286         RGBA markcol = {0,i->mark[0],i->mark[1],i->mark[2]};
2287         swf_ShapeAddSolidFillStyle(shape,&markcol);
2288     }
2289     swf_SetU16(i->tag,myshapeid);
2290     SRECT r = gfxline_getSWFbbox(line);
2291     r = swf_ClipRect(i->pagebbox, r);
2292     swf_SetRect(i->tag,&r);
2293     swf_SetShapeStyles(i->tag,shape);
2294     swf_ShapeCountBits(shape,NULL,NULL);
2295     swf_SetShapeBits(i->tag,shape);
2296     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2297     i->swflastx = i->swflasty = UNDEFINED_COORD;
2298     i->shapeisempty = 1;
2299     drawgfxline(dev, line, 1);
2300     if(i->shapeisempty) {
2301         /* an empty clip shape is equivalent to a shape with no area */
2302         int x = line?line->x:0;
2303         int y = line?line->y:0;
2304         moveto(dev, i->tag, x,y);
2305         lineto(dev, i->tag, x,y);
2306         lineto(dev, i->tag, x,y);
2307     }
2308     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)) {
2309         if(i->config_watermark) {
2310             gfxbbox_t r; r.xmin = r.ymin = 0;r.xmax = i->max_x;r.ymax = i->max_y;
2311             draw_watermark(dev, r, 1);
2312         }
2313     }
2314     swf_ShapeSetEnd(i->tag);
2315     swf_ShapeFree(shape);
2316
2317     /* TODO: remember the bbox, and check all shapes against it */
2318     
2319     msg("<trace> Placing clip ID %d", myshapeid);
2320     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2321     i->cliptags[i->clippos] = i->tag;
2322     i->clipshapes[i->clippos] = myshapeid;
2323     i->clipdepths[i->clippos] = getNewDepth(dev);
2324     i->clippos++;
2325 }
2326
2327 static void swf_endclip(gfxdevice_t*dev)
2328 {
2329     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2330     if(i->textid>=0)
2331         endtext(dev);
2332     if(i->shapeid>=0)
2333         endshape(dev);
2334
2335     if(!i->clippos) {
2336         msg("<error> Invalid end of clipping region");
2337         return;
2338     }
2339     i->clippos--;
2340     /*swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,
2341             / * clip to depth: * / i->depth <= i->clipdepths[i->clippos]? i->depth : i->depth - 1);
2342     i->depth ++;*/
2343     swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,i->depth);
2344 }
2345 static int gfxline_type(gfxline_t*line)
2346 {
2347     int tmplines=0;
2348     int tmpsplines=0;
2349     int lines=0;
2350     int splines=0;
2351     int haszerosegments=0;
2352     int length=0;
2353     while(line) {
2354         if(line->type == gfx_moveTo) {
2355             tmplines=0;
2356             tmpsplines=0;
2357         } else if(line->type == gfx_lineTo) {
2358             tmplines++;
2359             if(tmplines>lines)
2360                 lines=tmplines;
2361         } else if(line->type == gfx_splineTo) {
2362             tmpsplines++;
2363             if(tmpsplines>lines)
2364                 splines=tmpsplines;
2365         }
2366         length++;
2367         line = line->next;
2368     }
2369     if(length>400)
2370         return 5;
2371     if(lines==0 && splines==0) return 0;
2372     else if(lines==1 && splines==0) return 1;
2373     else if(lines==0 && splines==1) return 2;
2374     else if(splines==0) return 3;
2375     else return 4;
2376 }
2377
2378 static int gfxline_has_dots(gfxline_t*line)
2379 {
2380     int tmplines=0;
2381     double x=0,y=0;
2382     double dist = 0;
2383     int isline = 0;
2384     int short_gap = 0;
2385     while(line) {
2386         if(line->type == gfx_moveTo) {
2387             /* test the length of the preceding line, and assume it is a dot if
2388                it's length is less than 1.0. But *only* if there's a noticable 
2389                gap between the previous line and the next moveTo. (I've come
2390                across a PDF where thousands of "dots" were stringed together,
2391                forming a line) */
2392             int last_short_gap = short_gap;
2393             if((fabs(line->x - x) + fabs(line->y - y)) < 1.0) {
2394                 short_gap = 1;
2395             } else {
2396                 short_gap = 0;
2397             }
2398             if(isline && dist < 1 && !short_gap && !last_short_gap) {
2399                 return 1;
2400             }
2401             dist = 0;
2402             isline = 0;
2403         } else if(line->type == gfx_lineTo) {
2404             dist += fabs(line->x - x) + fabs(line->y - y);
2405             isline = 1;
2406         } else if(line->type == gfx_splineTo) {
2407             dist += fabs(line->sx - x) + fabs(line->sy - y) + 
2408                     fabs(line->x - line->sx) + fabs(line->y - line->sy);
2409             isline = 1;
2410         }
2411         x = line->x;
2412         y = line->y;
2413         line = line->next;
2414     }
2415     if(isline && dist < 1 && !short_gap) {
2416         return 1;
2417     }
2418     return 0;
2419 }
2420
2421 static int gfxline_fix_short_edges(gfxline_t*line)
2422 {
2423     double x,y;
2424     while(line) {
2425         if(line->type == gfx_lineTo) {
2426             if(fabs(line->x - x) + fabs(line->y - y) < 0.01) {
2427                 line->x += 0.01;
2428             }
2429         } else if(line->type == gfx_splineTo) {
2430             if(fabs(line->sx - x) + fabs(line->sy - y) + 
2431                fabs(line->x - line->sx) + fabs(line->y - line->sy) < 0.01) {
2432                 line->x += 0.01;
2433             }
2434         }
2435         x = line->x;
2436         y = line->y;
2437         line = line->next;
2438     }
2439     return 0;
2440 }
2441
2442 static char is_inside_page(gfxdevice_t*dev, gfxcoord_t x, gfxcoord_t y)
2443 {
2444     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2445     if(x<i->min_x || x>i->max_x) return 0;
2446     if(y<i->min_y || y>i->max_y) return 0;
2447     return 1;
2448 }
2449
2450 gfxline_t* gfxline_move(gfxline_t*line, double x, double y)
2451 {
2452     gfxline_t*l = line = gfxline_clone(line);
2453
2454     while(l) {
2455         l->x += x;
2456         l->y += y;
2457         l->sx += x;
2458         l->sy += y;
2459         l = l->next;
2460     }
2461     return line;
2462 }
2463
2464 //#define NORMALIZE_POLYGON_POSITIONS
2465
2466 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)
2467 {
2468     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2469     if(line_is_empty(line))
2470         return;
2471     int type = gfxline_type(line);
2472     int has_dots = gfxline_has_dots(line);
2473     gfxbbox_t r = gfxline_getbbox(line);
2474     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2475
2476     /* TODO: * split line into segments, and perform this check for all segments */
2477
2478     if(i->config_disable_polygon_conversion || type>=5 ||
2479        (!has_dots &&
2480         (width <= i->config_caplinewidth 
2481         || (cap_style == gfx_capRound && joint_style == gfx_joinRound)
2482         || (cap_style == gfx_capRound && type<=2)))) {} else 
2483     {
2484         /* convert line to polygon */
2485         msg("<trace> draw as polygon, type=%d dots=%d", type, has_dots);
2486         if(has_dots)
2487             gfxline_fix_short_edges(line);
2488         /* we need to convert the line into a polygon */
2489         gfxpoly_t* poly = gfxpoly_strokeToPoly(line, width, cap_style, joint_style, miterLimit);
2490         gfxline_t*gfxline = gfxpoly_to_gfxline(poly);
2491         dev->fill(dev, gfxline, color);
2492         gfxline_free(gfxline);
2493         gfxpoly_free(poly);
2494         return;
2495     }
2496
2497     msg("<trace> draw as stroke, type=%d dots=%d", type, has_dots);
2498     endtext(dev);
2499
2500     if(i->config_normalize_polygon_positions) {
2501         endshape(dev);
2502         double startx = 0, starty = 0;
2503         if(line && line->type == gfx_moveTo) {
2504             startx = line->x;
2505             starty = line->y;
2506         }
2507         line = gfxline_move(line, -startx, -starty);
2508         i->shapeposx = (int)(startx*20);
2509         i->shapeposy = (int)(starty*20);
2510     }
2511
2512     swfoutput_setstrokecolor(dev, color->r, color->g, color->b, color->a);
2513     swfoutput_setlinewidth(dev, width);
2514     startshape(dev);
2515     stopFill(dev);
2516     drawgfxline(dev, line, 0);
2517
2518     if(i->config_normalize_polygon_positions) {
2519         free(line); //account for _move
2520     }
2521
2522 }
2523
2524 static void swf_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color)
2525 {
2526     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2527     if(line_is_empty(line))
2528         return;
2529     if(!color->a)
2530         return;
2531     gfxbbox_t r = gfxline_getbbox(line);
2532     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2533
2534     //if(i->chardatapos && i->chardata[i->chardatapos-1].color.a) {
2535     endtext(dev);
2536
2537     if(!i->config_ignoredraworder)
2538         endshape(dev);
2539
2540     if(i->config_normalize_polygon_positions) {
2541         endshape(dev);
2542         double startx = 0, starty = 0;
2543         if(line && line->type == gfx_moveTo) {
2544             startx = line->x;
2545             starty = line->y;
2546         }
2547         line = gfxline_move(line, -startx, -starty);
2548         i->shapeposx = (int)(startx*20);
2549         i->shapeposy = (int)(starty*20);
2550     }
2551
2552     swfoutput_setfillcolor(dev, color->r, color->g, color->b, color->a);
2553     startshape(dev);
2554     startFill(dev);
2555     drawgfxline(dev, line, 1);
2556     
2557     if(i->currentswfid==2 && r.xmin==0 && r.ymin==0 && r.xmax==i->max_x && r.ymax==i->max_y) {
2558         if(i->config_watermark) {
2559             draw_watermark(dev, r, 1);
2560         }
2561     }
2562
2563     msg("<trace> end of swf_fill (shapeid=%d)", i->shapeid);
2564
2565     if(i->config_normalize_polygon_positions) {
2566         free(line); //account for _move
2567     }
2568 }
2569
2570 static GRADIENT* gfxgradient_to_GRADIENT(gfxgradient_t*gradient)
2571 {
2572     int num = 0;
2573     gfxgradient_t*g = gradient;
2574     while(g) {
2575         num++;
2576         g = g->next;
2577     }
2578     GRADIENT* swfgradient = malloc(sizeof(GRADIENT));
2579     swfgradient->num = num;
2580     swfgradient->rgba = malloc(sizeof(swfgradient->rgba[0])*num);
2581     swfgradient->ratios = malloc(sizeof(swfgradient->ratios[0])*num);
2582
2583     g = gradient;
2584     num = 0;
2585     while(g) {
2586         swfgradient->ratios[num] = g->pos*255;
2587         swfgradient->rgba[num] = *(RGBA*)&g->color;
2588         num++;
2589         g = g->next;
2590     }
2591     return swfgradient;
2592 }
2593
2594 static void swf_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
2595 {
2596     if(line_is_empty(line))
2597         return;
2598     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2599     
2600     if(line_is_empty(line))
2601         return;
2602
2603     GRADIENT* swfgradient = gfxgradient_to_GRADIENT(gradient);
2604     if(!swfgradient)
2605         return;
2606   
2607     endshape(dev);
2608     endtext(dev);
2609
2610     double f = type==gfxgradient_radial?4:4;
2611     MATRIX m;
2612     m.sx = (int)(matrix->m00*20*f); m.r1 = (int)(matrix->m10*20*f);
2613     m.r0 = (int)(matrix->m01*20*f); m.sy = (int)(matrix->m11*20*f);
2614     m.tx = (int)(matrix->tx*20);
2615     m.ty = (int)(matrix->ty*20);
2616
2617     /* shape */
2618     int myshapeid = getNewID(dev);
2619     i->tag = swf_InsertTag(i->tag, ST_DEFINESHAPE2);
2620     SHAPE*shape;
2621     swf_ShapeNew(&shape);
2622     int fsid = swf_ShapeAddGradientFillStyle(shape,&m,swfgradient,type==gfxgradient_radial);
2623     swf_SetU16(i->tag, myshapeid);
2624     SRECT r = gfxline_getSWFbbox(line);
2625     r = swf_ClipRect(i->pagebbox, r);
2626     swf_SetRect(i->tag,&r);
2627     swf_SetShapeStyles(i->tag,shape);
2628     swf_ShapeCountBits(shape,NULL,NULL);
2629     swf_SetShapeBits(i->tag,shape);
2630     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2631     i->swflastx = i->swflasty = UNDEFINED_COORD;
2632     drawgfxline(dev, line, 1);
2633     swf_ShapeSetEnd(i->tag);
2634     swf_ShapeFree(shape);
2635
2636     int depth = getNewDepth(dev);
2637     msg("<trace> Placing gradient, shape ID %d, depth %d", myshapeid, depth);
2638     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2639     swf_ObjectPlace(i->tag,myshapeid,depth,&i->page_matrix,NULL,NULL);
2640
2641     swf_FreeGradient(swfgradient);free(swfgradient);
2642 }
2643
2644 static SWFFONT* gfxfont_to_swffont(gfxfont_t*font, const char* id)
2645 {
2646     SWFFONT*swffont = (SWFFONT*)rfx_calloc(sizeof(SWFFONT));
2647     int t;
2648     SRECT bounds = {0,0,0,0};
2649     swffont->id = -1;
2650     swffont->version = 2;
2651     swffont->name = (U8*)strdup(id);
2652     swffont->layout = (SWFLAYOUT*)rfx_calloc(sizeof(SWFLAYOUT));
2653     swffont->layout->ascent = 0;
2654     swffont->layout->descent = 0;
2655     swffont->layout->leading = 0;
2656     swffont->layout->bounds = (SRECT*)rfx_calloc(sizeof(SRECT)*font->num_glyphs);
2657     swffont->encoding = FONT_ENCODING_UNICODE;
2658     swffont->numchars = font->num_glyphs;
2659     swffont->maxascii = font->max_unicode;
2660     swffont->ascii2glyph = (int*)rfx_calloc(sizeof(int)*swffont->maxascii);
2661     swffont->glyph2ascii = (U16*)rfx_calloc(sizeof(U16)*swffont->numchars);
2662     swffont->glyph = (SWFGLYPH*)rfx_calloc(sizeof(SWFGLYPH)*swffont->numchars);
2663     swffont->glyphnames = (char**)rfx_calloc(sizeof(char*)*swffont->numchars);
2664     for(t=0;t<font->max_unicode;t++) {
2665         swffont->ascii2glyph[t] = font->unicode2glyph[t];
2666     }
2667     SRECT max = {0,0,0,0};
2668     for(t=0;t<font->num_glyphs;t++) {
2669         drawer_t draw;
2670         gfxline_t*line;
2671         double advance = 0;
2672         swffont->glyph2ascii[t] = font->glyphs[t].unicode;
2673         if(swffont->glyph2ascii[t] == 0xffff || swffont->glyph2ascii[t] == 0x0000) {
2674             /* flash 8 flashtype requires unique unicode IDs for each character.
2675                We use the Unicode private user area to assign characters, hoping that
2676                the font doesn't contain more than 2048 glyphs */
2677             swffont->glyph2ascii[t] = 0xe000 + (t&0x1fff);
2678         }
2679
2680         if(font->glyphs[t].name) {
2681             swffont->glyphnames[t] = strdup(font->glyphs[t].name);
2682         } else {
2683             swffont->glyphnames[t] = 0;
2684         }
2685         advance = font->glyphs[t].advance;
2686
2687         swf_Shape01DrawerInit(&draw, 0);
2688         line = font->glyphs[t].line;
2689         while(line) {
2690             FPOINT c,to;
2691             c.x = line->sx * GLYPH_SCALE; c.y = line->sy * GLYPH_SCALE;
2692             to.x = line->x * GLYPH_SCALE; to.y = line->y * GLYPH_SCALE;
2693             if(line->type == gfx_moveTo) {
2694                 draw.moveTo(&draw, &to);
2695             } else if(line->type == gfx_lineTo) {
2696                 draw.lineTo(&draw, &to);
2697             } else if(line->type == gfx_splineTo) {
2698                 draw.splineTo(&draw, &c, &to);
2699             }
2700             line = line->next;
2701         }
2702         draw.finish(&draw);
2703         swffont->glyph[t].shape = swf_ShapeDrawerToShape(&draw);
2704
2705         SRECT bbox = swf_ShapeDrawerGetBBox(&draw);
2706         swf_ExpandRect2(&max, &bbox);
2707
2708         swffont->layout->bounds[t] = bbox;
2709             
2710         if(advance<32768.0/20) {
2711             swffont->glyph[t].advance = (int)(advance*20);
2712         } else {
2713             //msg("<warning> Advance value overflow in glyph %d", t);
2714             swffont->glyph[t].advance = 32767;
2715         }
2716
2717         draw.dealloc(&draw);
2718
2719         swf_ExpandRect2(&bounds, &swffont->layout->bounds[t]);
2720     }
2721     for(t=0;t<font->num_glyphs;t++) {
2722         SRECT bbox = swffont->layout->bounds[t];
2723
2724         /* if the glyph doesn't have a bounding box, use the
2725            combined bounding box (necessary e.g. for space characters) */
2726         if(!(bbox.xmin|bbox.ymin|bbox.xmax|bbox.ymax)) {
2727             swffont->layout->bounds[t] = bbox = max;
2728         }
2729         
2730         /* check that the advance value is reasonable, by comparing it
2731            with the bounding box */
2732         if(bbox.xmax>0 && (bbox.xmax*10 < swffont->glyph[t].advance || !swffont->glyph[t].advance)) {
2733             if(swffont->glyph[t].advance)
2734                 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);
2735             swffont->glyph[t].advance = bbox.xmax;
2736         }
2737         //swffont->glyph[t].advance = bbox.xmax - bbox.xmin;
2738     }
2739
2740
2741     /* Flash player will use the advance value from the char, and the ascent/descent values
2742        from the layout for text selection.
2743        ascent will extend the char into negative y direction, from the baseline, while descent
2744        will extend in positive y direction, also from the baseline.
2745        The baseline is defined as the y-position zero 
2746      */
2747
2748     swffont->layout->ascent = -bounds.ymin;
2749     if(swffont->layout->ascent < 0)
2750         swffont->layout->ascent = 0;
2751     swffont->layout->descent = bounds.ymax;
2752     if(swffont->layout->descent < 0)
2753         swffont->layout->descent = 0;
2754     swffont->layout->leading = bounds.ymax - bounds.ymin;
2755
2756     /* if the font has proper ascent/descent values (>0) and those define
2757        greater line spacing that what we estimated from the bounding boxes,
2758        use the font's parameters */
2759     if(font->ascent*20 > swffont->layout->ascent)
2760         swffont->layout->ascent = font->ascent*20;
2761     if(font->descent*20 > swffont->layout->descent)
2762         swffont->layout->descent = font->descent*20;
2763
2764     return swffont;
2765 }
2766
2767 static void swf_addfont(gfxdevice_t*dev, gfxfont_t*font)
2768 {
2769     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2770
2771     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,font->id))
2772         return; // the requested font is the current font
2773     
2774     fontlist_t*last=0,*l = i->fontlist;
2775     while(l) {
2776         last = l;
2777         if(!strcmp((char*)l->swffont->name, font->id)) {
2778             return; // we already know this font
2779         }
2780         l = l->next;
2781     }
2782     l = (fontlist_t*)rfx_calloc(sizeof(fontlist_t));
2783     l->swffont = gfxfont_to_swffont(font, font->id);
2784     l->next = 0;
2785     if(last) {
2786         last->next = l;
2787     } else {
2788         i->fontlist = l;
2789     }
2790     swf_FontSetID(l->swffont, getNewID(i->dev));
2791
2792     if(getScreenLogLevel() >= LOGLEVEL_DEBUG)  {
2793         int iii;
2794         // print font information
2795         msg("<debug> Font %s",font->id);
2796         msg("<debug> |   ID: %d", l->swffont->id);
2797         msg("<debug> |   Version: %d", l->swffont->version);
2798         msg("<debug> |   Name: %s", l->swffont->name);
2799         msg("<debug> |   Numchars: %d", l->swffont->numchars);
2800         msg("<debug> |   Maxascii: %d", l->swffont->maxascii);
2801         msg("<debug> |   Style: %d", l->swffont->style);
2802         msg("<debug> |   Encoding: %d", l->swffont->encoding);
2803         for(iii=0; iii<l->swffont->numchars;iii++) {
2804             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, 
2805                     l->swffont->layout->bounds[iii].xmin/20.0,
2806                     l->swffont->layout->bounds[iii].ymin/20.0,
2807                     l->swffont->layout->bounds[iii].xmax/20.0,
2808                     l->swffont->layout->bounds[iii].ymax/20.0
2809                     );
2810             int t;
2811             for(t=0;t<l->swffont->maxascii;t++) {
2812                 if(l->swffont->ascii2glyph[t] == iii)
2813                     msg("<debug> | - maps to %d",t);
2814             }
2815         }
2816     }
2817 }
2818
2819 static void swf_switchfont(gfxdevice_t*dev, const char*fontid)
2820 {
2821     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2822
2823     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,fontid))
2824         return; // the requested font is the current font
2825     
2826     fontlist_t*l = i->fontlist;
2827     while(l) {
2828         if(!strcmp((char*)l->swffont->name, fontid)) {
2829             i->swffont = l->swffont;
2830             return; //done!
2831         }
2832         l = l->next;
2833     }
2834     msg("<error> Unknown font id: %s", fontid);
2835     return;
2836 }
2837
2838 static void swf_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyph, gfxcolor_t*color, gfxmatrix_t*matrix)
2839 {
2840     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2841     if(!font) {
2842         msg("<error> swf_drawchar called (glyph %d) without font", glyph);
2843         return;
2844     }
2845
2846     if(i->config_drawonlyshapes) {
2847         gfxglyph_t*g = &font->glyphs[glyph];
2848         gfxline_t*line2 = gfxline_clone(g->line);
2849         gfxline_transform(line2, matrix);
2850         dev->fill(dev, line2, color);
2851         gfxline_free(line2);
2852         return;
2853     }
2854
2855     if(!i->swffont || !i->swffont->name || strcmp((char*)i->swffont->name,font->id)) // not equal to current font
2856     {
2857         /* TODO: remove the need for this (enhance getcharacterbbox so that it can cope
2858                  with multiple fonts */
2859         endtext(dev);
2860         swf_switchfont(dev, font->id); // set the current font
2861     }
2862     if(!i->swffont) {
2863         msg("<warning> swf_drawchar: Font is NULL");
2864         return;
2865     }
2866     if(glyph<0 || glyph>=i->swffont->numchars) {
2867         msg("<warning> No character %d in font %s (%d chars)", glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2868         return;
2869     }
2870     
2871     setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 0);
2872     
2873     double det = i->fontmatrix.sx/65536.0 * i->fontmatrix.sy/65536.0 - 
2874                  i->fontmatrix.r0/65536.0 * i->fontmatrix.r1/65536.0;
2875     if(fabs(det) < 0.0005) { 
2876         /* x direction equals y direction- the text is invisible */
2877         msg("<verbose> Not drawing invisible character character %d (det=%f, m=[%f %f;%f %f]\n", glyph, 
2878                 det,
2879                 i->fontmatrix.sx/65536.0, i->fontmatrix.r1/65536.0, 
2880                 i->fontmatrix.r0/65536.0, i->fontmatrix.sy/65536.0);
2881         return;
2882     }
2883
2884     /*if(i->swffont->glyph[glyph].shape->bitlen <= 16) {
2885         msg("<warning> Glyph %d in current charset (%s, %d characters) is empty", 
2886                 glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2887         return 1;
2888     }*/
2889
2890     /* calculate character position with respect to the current font matrix */
2891     double s = 20 * GLYPH_SCALE / det;
2892     double px = matrix->tx - i->fontmatrix.tx/20.0;
2893     double py = matrix->ty - i->fontmatrix.ty/20.0;
2894     int x = (SCOORD)((  px * i->fontmatrix.sy/65536.0 - py * i->fontmatrix.r1/65536.0)*s);
2895     int y = (SCOORD)((- px * i->fontmatrix.r0/65536.0 + py * i->fontmatrix.sx/65536.0)*s);
2896     if(x>32767 || x<-32768 || y>32767 || y<-32768) {
2897         msg("<verbose> Moving character origin to %f %f\n", matrix->tx, matrix->ty);
2898         endtext(dev);
2899         setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 1);
2900         /* since we just moved the char origin to the current char's position, 
2901            it now has the relative position (0,0) */
2902         x = y = 0;
2903     }
2904     
2905     if(i->shapeid>=0)
2906         endshape(dev);
2907     if(i->textid<0)
2908         starttext(dev);
2909
2910     msg("<trace> Drawing char %d in font %d at %d,%d in color %02x%02x%02x%02x", 
2911             glyph, i->swffont->id, x, y, color->r, color->g, color->b, color->a);
2912
2913     putcharacter(dev, i->swffont->id, glyph, x, y, i->current_font_size, *(RGBA*)color);
2914     swf_FontUseGlyph(i->swffont, glyph);
2915     return;
2916 }