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