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