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