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