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