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